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 SourceFile
s 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!