fix(language-service): reinstate getExternalFiles() (#37750)

`getExternalFiles()` is an API that could optionally be provided by a tsserver plugin
to notify the server of any additional files that should belong to a particular project.

This API was removed in https://github.com/angular/angular/pull/34260 mainly
due to performance reasons.

However, with the introduction of "solution-style" tsconfig in typescript 3.9,
the Angular extension could no longer reliably detect the owning Project solely
based on the ancestor tsconfig.json. In order to support this use case, we have
to reinstate `getExternalFiles()`.

Fixes https://github.com/angular/vscode-ng-language-service/issues/824

PR Close #37750
This commit is contained in:
Keen Yee Liau 2020-06-25 14:17:17 -07:00 committed by Andrew Kushnir
parent 6341a837c1
commit c942662d79
3 changed files with 39 additions and 2 deletions

View File

@ -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<tss.server.Project, TypeScriptServiceHost>();
/**
* 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) {

View File

@ -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

View File

@ -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`, () => {