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.ts
,TypeScript 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
属性 然后获取节点开始和结束位置之间的字符串。
对于在转换时创建的任何节点,它们将没有父集,其 pos
和 end
将是 -1
和 -1
:
> ts.factory.createIdentifier("__REPLACED__")
Identifier {
pos: -1,
end: -1,
parent: undefined,
// etc...
}
所以出现错误是因为parent
是undefined
。
的确,您可以通过执行 node.expression.getText(sourceFile)
将源文件提供给 getText
来绕过您看到的错误;但是,我仍然不推荐这样做,因为它不能在转换后的节点上工作,因为它们的范围是 [-1, -1]
.
解决方案:留在 AST
相反,我建议检查表达式是否为标识符并查看其 escapedText
属性。大致是这样的(未经测试,您需要根据您的代码进行调整):
ts.isIdentifier(node.expression) && node.expression.escapedText === "placeholder"
我正在创建一个转换器,需要用代码 __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.ts
,TypeScript 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
属性 然后获取节点开始和结束位置之间的字符串。
对于在转换时创建的任何节点,它们将没有父集,其 pos
和 end
将是 -1
和 -1
:
> ts.factory.createIdentifier("__REPLACED__")
Identifier {
pos: -1,
end: -1,
parent: undefined,
// etc...
}
所以出现错误是因为parent
是undefined
。
的确,您可以通过执行 node.expression.getText(sourceFile)
将源文件提供给 getText
来绕过您看到的错误;但是,我仍然不推荐这样做,因为它不能在转换后的节点上工作,因为它们的范围是 [-1, -1]
.
解决方案:留在 AST
相反,我建议检查表达式是否为标识符并查看其 escapedText
属性。大致是这样的(未经测试,您需要根据您的代码进行调整):
ts.isIdentifier(node.expression) && node.expression.escapedText === "placeholder"