Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
nkohari opened this issue Mar 18, 2016 · 1 comment · Fixed by #7584
Labels
API Relates to the public API for TypeScript Breaking Change Would introduce errors in existing code Bug A bug in TypeScript

Comments

@nkohari
Copy link

nkohari commented Mar 18, 2016

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!

@vladima
Copy link
Contributor

vladima commented Mar 18, 2016

I don't see any reasons why LanguageService.getSourceFile is exposed - it should be internal method. The reason why behavior is different: LanguageService.getSourceFile returns a SourceFile that was not bound and it is only usable for syntactic analysis.

@vladima vladima added Bug A bug in TypeScript API Relates to the public API for TypeScript Breaking Change Would introduce errors in existing code labels Mar 18, 2016
@mhegazy mhegazy added this to the TypeScript 2.0 milestone Mar 24, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
API Relates to the public API for TypeScript Breaking Change Would introduce errors in existing code Bug A bug in TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants