Repository navigation
Symbols missing from nodes when SourceFile requested via LanguageService, but not via Program #7581
Description
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!
Activity
vladima commentedon Mar 18, 2016
I don't see any reasons why
LanguageService.getSourceFileis exposed - it should be internal method. The reason why behavior is different:LanguageService.getSourceFilereturns aSourceFilethat was not bound and it is only usable for syntactic analysis.