Reputation: 902
I'm creating a transformer that needs to replace each Call Expression placeholder
with the code __REPLACED__
, so I wrote this code:
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)
It works well if the source.ts
file doesn't have any placeholder
inside a anonymous function call, the code below everything works right:
source.ts
placeholder("param")
void function() {
placeholder("param")
}
Using thats source.ts
described above, TypeScript Compiler API
emits what I expected:
outdir\source.js
__REPLACED__;
void async function () {
__REPLACED__;
};
But if I call this anonymous function right after creating it, I get an error if I use the following code as source.ts
:
source.ts
placeholder("param")
void function() {
placeholder("param")
}() // <---- HERE'S THE PROBLEM
Here's the trace error:
(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.
I suspect the problem is with recursion, i think somehow I'm removing the anonymous function node before accessing the placehorlders
.
in advance thank you for your help.
Thanks.
Upvotes: 3
Views: 3279
Reputation: 106580
Using the getText()
method in a transformer is not reliable and I would recommend never using it or other methods that consult the source file text in a transformer.
The getText()
method goes up the tree via the parents to get the root source file node. Once there, it then looks at the source file's text
property then gets the string between the node's start and end positions.
In the case of any nodes created while transforming, they will not have a parent set and its pos
and end
will be -1
and -1
:
> ts.factory.createIdentifier("__REPLACED__")
Identifier {
pos: -1,
end: -1,
parent: undefined,
// etc...
}
So the error occurs because parent
is undefined
.
It is true, that you could bypass the error you are seeing by providing the source file to getText
by doing node.expression.getText(sourceFile)
; however, I still wouldn't recommend this because it won't work on transformed nodes due to their range being [-1, -1]
.
Solution: Stay in the AST
Instead, I would suggest to check if the expression is an identifier and look at its escapedText
property. Roughly something along these lines (untested and you will need to adapt this for your code):
ts.isIdentifier(node.expression) && node.expression.escapedText === "placeholder"
Upvotes: 3