diff --git a/packages/language-service/src/typescript_host.ts b/packages/language-service/src/typescript_host.ts index bec46d2623..1844fd99c2 100644 --- a/packages/language-service/src/typescript_host.ts +++ b/packages/language-service/src/typescript_host.ts @@ -117,22 +117,29 @@ export class TypeScriptServiceHost implements LanguageServiceHost { return this.templateReferences || []; } + /** + * Get the Angular template in the file, if any. If TS file is provided then + * return the inline template, otherwise return the external template. + * @param fileName Either TS or HTML file + * @param position Only used if file is TS + */ getTemplateAt(fileName: string, position: number): TemplateSource|undefined { - let sourceFile = this.getSourceFile(fileName); - if (sourceFile) { - this.context = sourceFile.fileName; - let node = this.findNode(sourceFile, position); - if (node) { - return this.getSourceFromNode( - fileName, this.host.getScriptVersion(sourceFile.fileName), node); + if (fileName.endsWith('.ts')) { + const sourceFile = this.getSourceFile(fileName); + if (sourceFile) { + this.context = sourceFile.fileName; + const node = this.findNode(sourceFile, position); + if (node) { + return this.getSourceFromNode( + fileName, this.host.getScriptVersion(sourceFile.fileName), node); + } } } else { this.ensureTemplateMap(); - // TODO: Cannocalize the file? - const componentType = this.fileToComponent.get(fileName); - if (componentType) { + const componentSymbol = this.fileToComponent.get(fileName); + if (componentSymbol) { return this.getSourceFromType( - fileName, this.host.getScriptVersion(fileName), componentType); + fileName, this.host.getScriptVersion(fileName), componentSymbol); } } return undefined; @@ -164,14 +171,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { } getTemplates(fileName: string): TemplateSources { - this.ensureTemplateMap(); - const componentType = this.fileToComponent.get(fileName); - if (componentType) { - const templateSource = this.getTemplateAt(fileName, 0); - if (templateSource) { - return [templateSource]; - } - } else { + if (fileName.endsWith('.ts')) { let version = this.host.getScriptVersion(fileName); let result: TemplateSource[] = []; @@ -191,10 +191,22 @@ export class TypeScriptServiceHost implements LanguageServiceHost { ts.forEachChild(sourceFile, visit); } return result.length ? result : undefined; + } else { + this.ensureTemplateMap(); + const componentSymbol = this.fileToComponent.get(fileName); + if (componentSymbol) { + const templateSource = this.getTemplateAt(fileName, 0); + if (templateSource) { + return [templateSource]; + } + } } } getDeclarations(fileName: string): Declarations { + if (!fileName.endsWith('.ts')) { + return []; + } const result: Declarations = []; const sourceFile = this.getSourceFile(fileName); if (sourceFile) { @@ -212,6 +224,9 @@ export class TypeScriptServiceHost implements LanguageServiceHost { } getSourceFile(fileName: string): ts.SourceFile|undefined { + if (!fileName.endsWith('.ts')) { + throw new Error(`Non-TS source file requested: ${fileName}`); + } return this.tsService.getProgram() !.getSourceFile(fileName); } @@ -383,7 +398,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { // The host's getCurrentDirectory() is not reliable as it is always "" in // tsserver. We don't need the exact base directory, just one that contains // a source file. - const source = this.tsService.getProgram() !.getSourceFile(this.context); + const source = this.getSourceFile(this.context); if (!source) { throw new Error('Internal error: no context could be determined'); } diff --git a/packages/language-service/test/typescript_host_spec.ts b/packages/language-service/test/typescript_host_spec.ts index 8f492234ee..0282b19bd5 100644 --- a/packages/language-service/test/typescript_host_spec.ts +++ b/packages/language-service/test/typescript_host_spec.ts @@ -15,51 +15,55 @@ import {toh} from './test_data'; import {MockTypescriptHost} from './test_utils'; -describe('completions', () => { - let host: ts.LanguageServiceHost; - let service: ts.LanguageService; - let ngHost: TypeScriptServiceHost; - - beforeEach(() => { - host = new MockTypescriptHost(['/app/main.ts'], toh); - service = ts.createLanguageService(host); +describe('TypeScriptServiceHost', () => { + it('should be able to create a typescript host', () => { + const tsLSHost = new MockTypescriptHost(['/app/main.ts'], toh); + const tsLS = ts.createLanguageService(tsLSHost); + expect(() => new TypeScriptServiceHost(tsLSHost, tsLS)).not.toThrow(); }); - it('should be able to create a typescript host', - () => { expect(() => new TypeScriptServiceHost(host, service)).not.toThrow(); }); + it('should be able to analyze modules', () => { + const tsLSHost = new MockTypescriptHost(['/app/main.ts'], toh); + const tsLS = ts.createLanguageService(tsLSHost); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + expect(ngLSHost.getAnalyzedModules()).toBeDefined(); + }); - beforeEach(() => { ngHost = new TypeScriptServiceHost(host, service); }); - - it('should be able to analyze modules', - () => { expect(ngHost.getAnalyzedModules()).toBeDefined(); }); - - it('should be able to analyze modules in without a tsconfig.json file', () => { - host = new MockTypescriptHost(['foo.ts'], toh); - service = ts.createLanguageService(host); - ngHost = new TypeScriptServiceHost(host, service); - expect(ngHost.getAnalyzedModules()).toBeDefined(); + it('should be able to analyze modules without a tsconfig.json file', () => { + const tsLSHost = new MockTypescriptHost(['foo.ts'], toh); + const tsLS = ts.createLanguageService(tsLSHost); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + expect(ngLSHost.getAnalyzedModules()).toBeDefined(); }); it('should not throw if there is no script names', () => { - host = new MockTypescriptHost([], toh); - service = ts.createLanguageService(host); - ngHost = new TypeScriptServiceHost(host, service); - expect(() => ngHost.getAnalyzedModules()).not.toThrow(); + const tsLSHost = new MockTypescriptHost([], toh); + const tsLS = ts.createLanguageService(tsLSHost); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + expect(() => ngLSHost.getAnalyzedModules()).not.toThrow(); }); it('should clear the caches if program changes', () => { // First create a TypescriptHost with empty script names - host = new MockTypescriptHost([], toh); - service = ts.createLanguageService(host); - ngHost = new TypeScriptServiceHost(host, service); - expect(ngHost.getAnalyzedModules().ngModules).toEqual([]); + const tsLSHost = new MockTypescriptHost([], toh); + const tsLS = ts.createLanguageService(tsLSHost); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + expect(ngLSHost.getAnalyzedModules().ngModules).toEqual([]); // Now add a script, this would change the program const fileName = '/app/main.ts'; - const content = (host as MockTypescriptHost).getFileContent(fileName) !; - (host as MockTypescriptHost).addScript(fileName, content); + const content = (tsLSHost as MockTypescriptHost).getFileContent(fileName) !; + (tsLSHost as MockTypescriptHost).addScript(fileName, content); // If the caches are not cleared, we would get back an empty array. // But if the caches are cleared then the analyzed modules will be non-empty. - expect(ngHost.getAnalyzedModules().ngModules.length).not.toEqual(0); + expect(ngLSHost.getAnalyzedModules().ngModules.length).not.toEqual(0); }); + it('should throw if getSourceFile is called on non-TS file', () => { + const tsLSHost = new MockTypescriptHost([], toh); + const tsLS = ts.createLanguageService(tsLSHost); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + expect(() => { + ngLSHost.getSourceFile('/src/test.ng'); + }).toThrowError('Non-TS source file requested: /src/test.ng'); + }); });