TypeScript 编译器 API:ts.TransformerFactory 替换节点时出现问题

TypeScript compiler API: problem with ts.TransformerFactory replacing nodes

我正在创建一个转换器,需要用代码 __REPLACED__ 替换每个调用表达式 placeholder,所以我写了这段代码:

compiler.ts

import * as ts from "typescript"

const filePath = "source.ts"

const programOptions = {
    rootNames: [filePath],
    options: {
        target: ts.ScriptTarget.ES2020,
        outDir: "outdir"
    }
}

const transformerFactory: ts.TransformerFactory<ts.SourceFile> = context => {
    return node => {
        const visitor: ts.Visitor = rootNode => {
            const node = ts.visitEachChild(rootNode, visitor, context)
            if (ts.isCallExpression(node)) {
                if (node.expression.getText() !== "placeholder") {
                    return node
                }
                const subsNode = ts.factory.createIdentifier("__REPLACED__")
                return subsNode
            }
            return node
        }
        return ts.visitNode(node, visitor)
    }
}

const program = ts.createIncrementalProgram(programOptions)
const entrySourceFile = program.getSourceFile(filePath)
const customTransformers: ts.CustomTransformers = { before: [transformerFactory] }
const emitResults = program.emit(entrySourceFile, undefined, undefined, undefined, customTransformers)

如果 source.ts 文件在匿名函数调用中没有任何 placeholder,它工作正常,下面的代码一切正常:

source.ts

placeholder("param") 
void function() {
    placeholder("param")
}

使用上面描述的 source.tsTypeScript Compiler API 发出我所期望的:

outdir\source.js

__REPLACED__;
void async function () {
    __REPLACED__;
};

但是如果我在创建它之后立即调用这个匿名函数,如果我使用下面的代码作为source.ts:

,我会得到一个错误

source.ts

placeholder("param") 
void function() {
    placeholder("param")
}() // <---- HERE'S THE PROBLEM

跟踪错误如下:

(node:9204) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'text' of undefined
    at NodeObject.getText (project_dir\node_modules\typescript\lib\typescript.js:156127:31)
    at visitor (project_dir\typescriptCompiler.ts:22:41)
    at visitNode (project_dir\node_modules\typescript\lib\typescript.js:85158:23)
    at Object.visitEachChild (project_dir\node_modules\typescript\lib\typescript.js:85565:59)
    at visitor (project_dir\typescriptCompiler.ts:20:33)
    at visitNode (project_dir\node_modules\typescript\lib\typescript.js:85158:23)
    at Object.visitEachChild (project_dir\node_modules\typescript\lib\typescript.js:85622:64)
    at visitor (project_dir\typescriptCompiler.ts:20:33)
    at visitNodes (project_dir\node_modules\typescript\lib\typescript.js:85211:48)
    at visitLexicalEnvironment (project_dir\node_modules\typescript\lib\typescript.js:85251:22)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:9204) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:9204) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

我怀疑问题出在递归上,我想我在访问 placehorlders.

之前以某种方式删除了匿名函数节点

预先感谢您的帮助。

谢谢。

在转换器中使用 getText() 方法不可靠,我建议永远不要使用它或其他在转换器中查询源文件文本的方法。

getText()方法通过父节点上树获取根源文件节点。到达那里后,它会查看源文件的 text 属性 然后获取节点开始和结束位置之间的字符串。

对于在转换时创建的任何节点,它们将没有父集,其 posend 将是 -1-1:

> ts.factory.createIdentifier("__REPLACED__")
Identifier {
  pos: -1,
  end: -1,
  parent: undefined,
  // etc...
}

所以出现错误是因为parentundefined

的确,您可以通过执行 node.expression.getText(sourceFile) 将源文件提供给 getText 来绕过您看到的错误;但是,我仍然不推荐这样做,因为它不能在转换后的节点上工作,因为它们的范围是 [-1, -1].

解决方案:留在 AST

相反,我建议检查表达式是否为标识符并查看其 escapedText 属性。大致是这样的(未经测试,您需要根据您的代码进行调整):

ts.isIdentifier(node.expression) && node.expression.escapedText === "placeholder"