diff --git a/packages/language-service/src/ts_plugin.ts b/packages/language-service/src/ts_plugin.ts index d0a3a5005f870..05ad3c79c1936 100644 --- a/packages/language-service/src/ts_plugin.ts +++ b/packages/language-service/src/ts_plugin.ts @@ -11,8 +11,30 @@ import * as tss from 'typescript/lib/tsserverlibrary'; import {createLanguageService} from './language_service'; import {TypeScriptServiceHost} from './typescript_host'; +// Use a WeakMap to keep track of Project to Host mapping so that when Project +// is deleted Host could be garbage collected. +const PROJECT_MAP = new WeakMap(); + +/** + * This function is called by tsserver to retrieve the external (non-TS) files + * that should belong to the specified `project`. For Angular, these files are + * external templates. This is called once when the project is loaded, then + * every time when the program is updated. + * @param project Project for which external files should be retrieved. + */ +export function getExternalFiles(project: tss.server.Project): string[] { + if (!project.hasRoots()) { + // During project initialization where there is no root files yet we should + // not do any work. + return []; + } + const ngLsHost = PROJECT_MAP.get(project); + ngLsHost?.getAnalyzedModules(); + return ngLsHost?.getExternalTemplates() || []; +} + export function create(info: tss.server.PluginCreateInfo): tss.LanguageService { - const {languageService: tsLS, languageServiceHost: tsLSHost, config} = info; + const {languageService: tsLS, languageServiceHost: tsLSHost, config, project} = info; // This plugin could operate under two different modes: // 1. TS + Angular // Plugin augments TS language service to provide additional Angular @@ -25,6 +47,7 @@ export function create(info: tss.server.PluginCreateInfo): tss.LanguageService { const angularOnly = config ? config.angularOnly === true : false; const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); const ngLS = createLanguageService(ngLSHost); + PROJECT_MAP.set(project, ngLSHost); function getCompletionsAtPosition( fileName: string, position: number, options: tss.GetCompletionsAtPositionOptions|undefined) { diff --git a/packages/language-service/src/typescript_host.ts b/packages/language-service/src/typescript_host.ts index cf4bf437b0506..ae927fd77d66e 100644 --- a/packages/language-service/src/typescript_host.ts +++ b/packages/language-service/src/typescript_host.ts @@ -151,6 +151,13 @@ export class TypeScriptServiceHost implements LanguageServiceHost { return this.resolver.getReflector() as StaticReflector; } + /** + * Return all known external templates. + */ + getExternalTemplates(): string[] { + return [...this.fileToComponent.keys()]; + } + /** * Checks whether the program has changed and returns all analyzed modules. * If program has changed, invalidate all caches and update fileToComponent diff --git a/packages/language-service/test/ts_plugin_spec.ts b/packages/language-service/test/ts_plugin_spec.ts index eecf10125741e..dc82a9cf58523 100644 --- a/packages/language-service/test/ts_plugin_spec.ts +++ b/packages/language-service/test/ts_plugin_spec.ts @@ -8,7 +8,7 @@ import * as ts from 'typescript'; -import {create} from '../src/ts_plugin'; +import {create, getExternalFiles} from '../src/ts_plugin'; import {CompletionKind} from '../src/types'; import {MockTypescriptHost} from './test_utils'; @@ -129,6 +129,13 @@ describe('plugin', () => { }, ]); }); + + it('should return external templates when getExternalFiles() is called', () => { + const externalTemplates = getExternalFiles(mockProject); + expect(externalTemplates).toEqual([ + '/app/test.ng', + ]); + }); }); describe(`with config 'angularOnly = true`, () => {