Skip to content

node.getText(sourceFile) crashes when node.kind == Identifier #19670

@mmorearty

Description

@mmorearty

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    APIRelates to the public API for TypeScriptBugA bug in TypeScriptFixedA PR has been merged for this issueHelp WantedYou can do this

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions