Skip to content

Symbols missing from nodes when SourceFile requested via LanguageService, but not via Program #7581

Closed
@nkohari

Description

@nkohari

I got tripped up when using the compiler API, and I wanted to see if it was just me being confused or an actual bug. :) It seems like SourceFiles that are returned from LanguageService.getSourceFile() don't include symbols, whereas those loaded via Program.getSourceFile() do.

If I request a file via:

languageService.getSourceFile("example.ts");

...it will be returned without symbols, so if I try calling any of TypeChecker's methods on it, it will either throw errors or returned undefined. If instead I request files via:

languageService.getProgram().getSourceFile("example.ts");

..the returned file has the symbols and works fine with TypeChecker.

I understand that mixing these two APIs is probably not a normal use case. I'm working on a tool that alters the source of a program as it's being compiled, so having access to LanguageService is really helpful, but I also need the TypeChecker.

(Also, if this is just a problem with my implementation of LanguageServiceHost, please let me know!)

TypeScript Version: Behavior appears on 1.8.7, 1.8.9, and 1.9.0-dev.20160318

Test Case

test.ts

import * as ts from 'typescript';
import * as fs from 'fs';

interface ScriptInfo {
  text: string
  version: number
}

export class LanguageServiceHost implements ts.LanguageServiceHost {

  files: { [name: string]: ScriptInfo } = {};

  constructor(protected rootFileNames: string[], protected options: ts.CompilerOptions) {
    for (const fileName of rootFileNames) {
      this.getScriptInfo(fileName);
    }
  }

  getCompilationSettings() {
    return this.options;
  }

  getScriptFileNames() {
    return this.rootFileNames;
  }

  getScriptVersion(fileName: string) {
    let info = this.getScriptInfo(fileName);
    return info.version.toString();
  }

  getScriptSnapshot(fileName: string) {
    let info = this.getScriptInfo(fileName);
    return ts.ScriptSnapshot.fromString(info.text);
  }

  getCurrentDirectory() {
    return process.cwd();
  }

  getDefaultLibFileName(options: ts.CompilerOptions) {
    return ts.getDefaultLibFilePath(options);
  }

  getScriptInfo(fileName: string): ScriptInfo {
    let info = this.files[fileName];
    if (!info) {
      this.files[fileName] = info = {
        text: fs.readFileSync(fileName).toString(),
        version: 1
      };
    }
    return info;
  }

}

const languageServiceHost = new LanguageServiceHost(['case.ts'], {
  target: ts.ScriptTarget.ES5,
  module: ts.ModuleKind.CommonJS
});

const languageService = ts.createLanguageService(languageServiceHost, ts.createDocumentRegistry());
const program = languageService.getProgram();
const checker = program.getTypeChecker();

function listClassMethods(file: ts.SourceFile) {
  let visit = (node: ts.Node) => {
    if (node.kind == ts.SyntaxKind.ClassDeclaration) {
      const decl = <ts.ClassDeclaration> node;
      for (const member of decl.members) {
        if (member.kind == ts.SyntaxKind.MethodDeclaration) {
          const method = <ts.MethodDeclaration> member;
          const symbol = checker.getSymbolAtLocation(method.name);
          if (symbol) {
            console.log(symbol.getName());
          }
          else {
            console.log("Couldn't resolve symbol")
          }
        }
      }
    }
    ts.forEachChild(node, visit);
  }
  ts.forEachChild(file, visit);
}

listClassMethods(languageService.getSourceFile('case.ts'));
listClassMethods(program.getSourceFile('case.ts'));

case.ts

interface IExample {
  getName: () => string
}

class A implements IExample {
  getName() {
    return 'A';
  }
}

class B implements IExample {
  getName() {
    return 'B';
  }
}

Expected behavior:

$ node test.js
getName
getName
getName
getName

Actual behavior:

$ node test.js
Couldn't resolve symbol
Couldn't resolve symbol
getName
getName

Thanks for your help, and for the great work on TypeScript and the compiler API!

Metadata

Metadata

Assignees

No one assigned

    Labels

    APIRelates to the public API for TypeScriptBreaking ChangeWould introduce errors in existing codeBugA bug in TypeScript

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions