如何合并两个 Typescript AST
How to merge two Typescript ASTs
我正在尝试合并两个从字符串创建的打字稿 AST。
我都是用这个方法创建的
const sourceFile = ts.createSourceFile(
"file.ts", // filePath
"function myFunction() {}", // fileText
ts.ScriptTarget.Latest, // scriptTarget
true // setParentNodes -- sets the `parent` property
);
但是,这将创建 ts.SourceFile
类型的节点,如果我尝试合并这两个 AST,我在尝试打印最终源文件时会出错:"Unhandled SyntaxKind: SourceFile."
我应该如何正确合并两个 AST?
谢谢
编辑:这是完整的工作代码示例。当我 运行 这样做时,它会直接将我抛入调试器(在浏览器中)并出现上述错误。
我也尝试过有目的地提取ArrowFunction
节点(在ast2中它被包裹在SourceFile
和ExpressionStatement
中),然后程序运行s没有错误,但将内部模板文字完全更改为无效代码。
const createAST = (str: string) =>
ts.createSourceFile(
"file.ts", // filePath
str, // fileText
ts.ScriptTarget.Latest, // scriptTarget
true // setParentNodes -- sets the `parent` property
)
const template1 = `const arr = fields.map(() => {})`
const template2 = `(field) => <Toolbar id={\`${field.id}.${field.name}\`} />`
const ast1 = createAST(template1)
const ast2 = createAST(template2)
const replaceTransformer = <T extends ts.Node>(
newNode: T
): ts.TransformerFactory<T> => {
return context => {
const visit: ts.Visitor = node => {
if (ts.isArrowFunction(node)) return newNode
return ts.visitEachChild(node, child => visit(child), context)
}
return node => ts.visitNode(node, visit)
}
}
if (!ast1 || !ast2) return console.log("something went wrong")
const result = ts.transform(ast1, [replaceTransformer(ast2)])
const res = result.transformed[0]
const printer = ts.createPrinter()
const string = printer.printFile(res)
console.log(string)
第一个问题是代码在 .ts
文件中有 JSX(看看 ast2
的结果)。要解决此问题,请将文件扩展名更改为 .tsx
,以便它将解析为 JSX。
第二个问题是将源文件放在源文件中会导致该错误。相反,最好用另一个表达式替换一个表达式,而不是提供源文件——ast2
——提供 (ast2.statements[0] as ts.ExpressionStatement).expression
.
第三个问题是 TypeScript 并非旨在从一个源文件中获取节点并在另一个源文件中使用它们。例如,即使进行了上述修复,您仍然会得到以下输出:
const arr = fields.map((field) => <Toolbar id={) =field.idfield.name}/>);
...这是因为打印机正在使用ast2
的位置索引到ast1
的源文件文本中,所以它会吐出垃圾。我过去建议做的是将表达式中所有节点的 pos
和 end
从 ast2
设置为 -1
(默认用于工厂创建的节点)。不过,这是一个有点棘手的解决方法。
完整示例
const createAST = (str: string) =>
ts.createSourceFile(
"file.tsx", // filePath
str, // fileText
ts.ScriptTarget.Latest, // scriptTarget
true, // setParentNodes -- sets the `parent` property
);
const template1 = `const arr = fields.map(() => {})`;
const template2 = "(field) => <Toolbar id={`${field.id}.${field.name}`} />";
const ast1 = createAST(template1);
const ast2 = createAST(template2);
const replaceTransformer = (
newNode: ts.Node,
): ts.TransformerFactory<ts.SourceFile> => {
return (context) => {
const visit: ts.Visitor = (node) => {
if (ts.isArrowFunction(node)) {
stripRanges(newNode);
return newNode;
}
return ts.visitEachChild(node, (child) => visit(child), context);
};
return (node) => ts.visitNode(node, visit);
};
};
const newNode = (ast2.statements[0] as ts.ExpressionStatement).expression;
stripRanges(newNode);
const result = ts.transform(ast1, [
replaceTransformer(newNode),
]);
const res = result.transformed[0];
const printer = ts.createPrinter();
console.log(printer.printFile(res));
function stripRanges(node: ts.Node) {
(node as any).pos = -1;
(node as any).end = -1;
ts.forEachChild(node, stripRanges);
}
或者,除了删除范围之外,不设置父节点似乎也可以工作(为该参数提供 false
),因为在这种情况下打印机似乎不参考源文件文本……再次,考虑到打印机的当前实现,另一个 hacky 解决方法。
我正在尝试合并两个从字符串创建的打字稿 AST。
我都是用这个方法创建的
const sourceFile = ts.createSourceFile(
"file.ts", // filePath
"function myFunction() {}", // fileText
ts.ScriptTarget.Latest, // scriptTarget
true // setParentNodes -- sets the `parent` property
);
但是,这将创建 ts.SourceFile
类型的节点,如果我尝试合并这两个 AST,我在尝试打印最终源文件时会出错:"Unhandled SyntaxKind: SourceFile."
我应该如何正确合并两个 AST?
谢谢
编辑:这是完整的工作代码示例。当我 运行 这样做时,它会直接将我抛入调试器(在浏览器中)并出现上述错误。
我也尝试过有目的地提取ArrowFunction
节点(在ast2中它被包裹在SourceFile
和ExpressionStatement
中),然后程序运行s没有错误,但将内部模板文字完全更改为无效代码。
const createAST = (str: string) =>
ts.createSourceFile(
"file.ts", // filePath
str, // fileText
ts.ScriptTarget.Latest, // scriptTarget
true // setParentNodes -- sets the `parent` property
)
const template1 = `const arr = fields.map(() => {})`
const template2 = `(field) => <Toolbar id={\`${field.id}.${field.name}\`} />`
const ast1 = createAST(template1)
const ast2 = createAST(template2)
const replaceTransformer = <T extends ts.Node>(
newNode: T
): ts.TransformerFactory<T> => {
return context => {
const visit: ts.Visitor = node => {
if (ts.isArrowFunction(node)) return newNode
return ts.visitEachChild(node, child => visit(child), context)
}
return node => ts.visitNode(node, visit)
}
}
if (!ast1 || !ast2) return console.log("something went wrong")
const result = ts.transform(ast1, [replaceTransformer(ast2)])
const res = result.transformed[0]
const printer = ts.createPrinter()
const string = printer.printFile(res)
console.log(string)
第一个问题是代码在 .ts
文件中有 JSX(看看 ast2
的结果)。要解决此问题,请将文件扩展名更改为 .tsx
,以便它将解析为 JSX。
第二个问题是将源文件放在源文件中会导致该错误。相反,最好用另一个表达式替换一个表达式,而不是提供源文件——ast2
——提供 (ast2.statements[0] as ts.ExpressionStatement).expression
.
第三个问题是 TypeScript 并非旨在从一个源文件中获取节点并在另一个源文件中使用它们。例如,即使进行了上述修复,您仍然会得到以下输出:
const arr = fields.map((field) => <Toolbar id={) =field.idfield.name}/>);
...这是因为打印机正在使用ast2
的位置索引到ast1
的源文件文本中,所以它会吐出垃圾。我过去建议做的是将表达式中所有节点的 pos
和 end
从 ast2
设置为 -1
(默认用于工厂创建的节点)。不过,这是一个有点棘手的解决方法。
完整示例
const createAST = (str: string) =>
ts.createSourceFile(
"file.tsx", // filePath
str, // fileText
ts.ScriptTarget.Latest, // scriptTarget
true, // setParentNodes -- sets the `parent` property
);
const template1 = `const arr = fields.map(() => {})`;
const template2 = "(field) => <Toolbar id={`${field.id}.${field.name}`} />";
const ast1 = createAST(template1);
const ast2 = createAST(template2);
const replaceTransformer = (
newNode: ts.Node,
): ts.TransformerFactory<ts.SourceFile> => {
return (context) => {
const visit: ts.Visitor = (node) => {
if (ts.isArrowFunction(node)) {
stripRanges(newNode);
return newNode;
}
return ts.visitEachChild(node, (child) => visit(child), context);
};
return (node) => ts.visitNode(node, visit);
};
};
const newNode = (ast2.statements[0] as ts.ExpressionStatement).expression;
stripRanges(newNode);
const result = ts.transform(ast1, [
replaceTransformer(newNode),
]);
const res = result.transformed[0];
const printer = ts.createPrinter();
console.log(printer.printFile(res));
function stripRanges(node: ts.Node) {
(node as any).pos = -1;
(node as any).end = -1;
ts.forEachChild(node, stripRanges);
}
或者,除了删除范围之外,不设置父节点似乎也可以工作(为该参数提供 false
),因为在这种情况下打印机似乎不参考源文件文本……再次,考虑到打印机的当前实现,另一个 hacky 解决方法。