-
Notifications
You must be signed in to change notification settings - Fork 13k
Description
TypeScript Version: 2.7.0-dev.20171101
This program uses the TypeScript compiler's API to walk the AST (abstract syntax tree) of some parsed TypeScript source code. Compilation works fine; when you run the program, it should display the node type (SyntaxKind
) and text of every node in the AST.
Code
import * as fs from "fs";
import * as ts from "typescript";
const source = 'console.log("hello, world")';
const host: ts.LanguageServiceHost = {
getCompilationSettings: () => ({}),
getScriptFileNames: () => ["example.ts"],
getScriptVersion: (filename: string) => "helloworld",
getScriptSnapshot: (filename: string): ts.IScriptSnapshot => {
return {
getText: (start: number, end: number) => source.slice(start, end),
getLength: () => source.length,
getChangeRange: (oldSnapshot: ts.IScriptSnapshot) => undefined
};
},
getCurrentDirectory: process.cwd,
getDefaultLibFileName: (options: ts.CompilerOptions) => "node_modules/typescript/lib/lib.d.ts",
getNewLine: () => "\n"
};
const languageService = ts.createLanguageService(host);
const sourceFile = ts.createSourceFile("example.ts", source, ts.ScriptTarget.ES2015);
function visit(parent: ts.Node, node: ts.Node, ind: string) {
// This next line will crash when it reaches an Identifier node for the identifier 'console':
console.log(ind + ts.SyntaxKind[node.kind] + ":" + node.getText(sourceFile).replace(/\n/g, "\\n"));
// If you use this version instead, it will not crash, because sourceFile() is passed in to node.getStart():
//console.log(ind + ts.SyntaxKind[node.kind] + ":" + source.substring(node.getStart(sourceFile), node.getEnd()).replace(/\n/g, "\\n"));
ts.forEachChild(node, (child: ts.Node) => {
visit(node, child, ind + " ");
});
}
visit(null, sourceFile, "");
Expected behavior:
This program uses the TypeScript compiler's API. Compilation works fine; when you run the program, it should display the node type (SyntaxKind
) and text of every node in the AST.
Actual behavior:
It displays the first several nodes of the AST correctly. But when it reaches the node for the console
identifier, it crashes:
SourceFile:console.log("hello, world")
ExpressionStatement:console.log("hello, world")
CallExpression:console.log("hello, world")
PropertyAccessExpression:console.log
/Users/mikemorearty/src/typescript/crashdemo/node_modules/typescript/lib/typescript.js:7479
return ts.skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos);
^
TypeError: Cannot read property 'text' of undefined
at Object.getTokenPosOfNode (/Users/mikemorearty/src/typescript/crashdemo/node_modules/typescript/lib/typescript.js:7479:72)
at IdentifierObject.TokenOrIdentifierObject.getStart (/Users/mikemorearty/src/typescript/crashdemo/node_modules/typescript/lib/typescript.js:95162:23)
at IdentifierObject.TokenOrIdentifierObject.getText (/Users/mikemorearty/src/typescript/crashdemo/node_modules/typescript/lib/typescript.js:95183:77)
at visit (/Users/mikemorearty/src/typescript/crashdemo/index.js:24:61)
at /Users/mikemorearty/src/typescript/crashdemo/index.js:28:9
at visitNode (/Users/mikemorearty/src/typescript/crashdemo/node_modules/typescript/lib/typescript.js:12655:24)
at Object.forEachChild (/Users/mikemorearty/src/typescript/crashdemo/node_modules/typescript/lib/typescript.js:12786:24)
at visit (/Users/mikemorearty/src/typescript/crashdemo/index.js:27:8)
at /Users/mikemorearty/src/typescript/crashdemo/index.js:28:9
at visitNode (/Users/mikemorearty/src/typescript/crashdemo/node_modules/typescript/lib/typescript.js:12655:24)
The fix is easy: In src/services/services.ts
line 279, sourceFile
needs to be passed in to this.getStart()
. In other words:
diff --git a/src/services/services.ts b/src/services/services.ts
index 06edd621e9..cd6fe86771 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -276,7 +276,10 @@ namespace ts {
}
public getText(sourceFile?: SourceFile): string {
- return (sourceFile || this.getSourceFile()).text.substring(this.getStart(), this.getEnd());
+ if (!sourceFile) {
+ sourceFile = this.getSourceFile();
+ }
+ return sourceFile.text.substring(this.getStart(sourceFile), this.getEnd());
}
public getChildCount(): number {
In fact, this same change already exists farther up in the same file, in class NodeObject
.