如何合并两个 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中它被包裹在SourceFileExpressionStatement中),然后程序运行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的源文件文本中,所以它会吐出垃圾。我过去建议做的是将表达式中所有节点的 posendast2 设置为 -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 解决方法。