From 183fb7e7b92c361e324dbbea73af435bebe9a0d9 Mon Sep 17 00:00:00 2001 From: Keen Yee Liau Date: Wed, 16 Dec 2020 11:15:14 -0800 Subject: [PATCH] fix(language-service): return all typecheck files via getExternalFiles (#40162) We need a means to preserve typecheck files when a project is reloaded, otherwise the Ivy compiler will throw an error when it's unable to find them. This commit implements `getExternalFiles()` called by the langauge server to achieve this goal. For more info see https://github.com/angular/vscode-ng-language-service/issues/1030 PR Close #40162 --- .../language-service/ivy/language_service.ts | 8 +++-- .../ivy/test/legacy/mock_host.ts | 6 ++-- .../ivy/test/legacy/ts_plugin_spec.ts | 29 +++++++++++++++++++ packages/language-service/ivy/ts_plugin.ts | 16 ++++++++++ 4 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 packages/language-service/ivy/test/legacy/ts_plugin_spec.ts diff --git a/packages/language-service/ivy/language_service.ts b/packages/language-service/ivy/language_service.ts index 2a6903bf65..8d8989f958 100644 --- a/packages/language-service/ivy/language_service.ts +++ b/packages/language-service/ivy/language_service.ts @@ -242,9 +242,11 @@ function getOrCreateTypeCheckScriptInfo( // attempt to fetch the content from disk and fail. scriptInfo = projectService.getOrCreateScriptInfoForNormalizedPath( ts.server.toNormalizedPath(tcf), - true, // openedByClient - '', // fileContent - ts.ScriptKind.TS, // scriptKind + true, // openedByClient + '', // fileContent + // script info added by plugins should be marked as external, see + // https://github.com/microsoft/TypeScript/blob/b217f22e798c781f55d17da72ed099a9dee5c650/src/compiler/program.ts#L1897-L1899 + ts.ScriptKind.External, // scriptKind ); if (!scriptInfo) { throw new Error(`Failed to create script info for ${tcf}`); diff --git a/packages/language-service/ivy/test/legacy/mock_host.ts b/packages/language-service/ivy/test/legacy/mock_host.ts index f1b6f3f4e0..d825907e41 100644 --- a/packages/language-service/ivy/test/legacy/mock_host.ts +++ b/packages/language-service/ivy/test/legacy/mock_host.ts @@ -243,9 +243,9 @@ export class MockService { } const newScriptInfo = this.ps.getOrCreateScriptInfoForNormalizedPath( ts.server.toNormalizedPath(fileName), - true, // openedByClient - '', // fileContent - ts.ScriptKind.External, // scriptKind + true, // openedByClient + '', // fileContent + ts.ScriptKind.Unknown, // scriptKind ); if (!newScriptInfo) { throw new Error(`Failed to create new script info for ${fileName}`); diff --git a/packages/language-service/ivy/test/legacy/ts_plugin_spec.ts b/packages/language-service/ivy/test/legacy/ts_plugin_spec.ts new file mode 100644 index 0000000000..ce6f75fc75 --- /dev/null +++ b/packages/language-service/ivy/test/legacy/ts_plugin_spec.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {LanguageService} from '../../language_service'; +import {getExternalFiles} from '../../ts_plugin'; + +import {APP_COMPONENT, setup} from './mock_host'; + +describe('getExternalFiles()', () => { + it('should return all typecheck files', () => { + const {project, tsLS} = setup(); + let externalFiles = getExternalFiles(project); + // Initially there are no external files because Ivy compiler hasn't done + // a global analysis + expect(externalFiles).toEqual([]); + // Trigger global analysis + const ngLS = new LanguageService(project, tsLS); + ngLS.getSemanticDiagnostics(APP_COMPONENT); + // Now that global analysis is run, we should have all the typecheck files + externalFiles = getExternalFiles(project); + expect(externalFiles.length).toBe(1); + expect(externalFiles[0].endsWith('app.component.ngtypecheck.ts')).toBeTrue(); + }); +}); diff --git a/packages/language-service/ivy/ts_plugin.ts b/packages/language-service/ivy/ts_plugin.ts index c21a04da47..b48adc87bd 100644 --- a/packages/language-service/ivy/ts_plugin.ts +++ b/packages/language-service/ivy/ts_plugin.ts @@ -120,3 +120,19 @@ export function create(info: ts.server.PluginCreateInfo): ts.LanguageService { getCompletionEntrySymbol, }; } + +export function getExternalFiles(project: ts.server.Project): string[] { + if (!project.hasRoots()) { + return []; // project has not been initialized + } + const typecheckFiles: string[] = []; + for (const scriptInfo of project.getScriptInfos()) { + if (scriptInfo.scriptKind === ts.ScriptKind.External) { + // script info for typecheck file is marked as external, see + // getOrCreateTypeCheckScriptInfo() in + // packages/language-service/ivy/language_service.ts + typecheckFiles.push(scriptInfo.fileName); + } + } + return typecheckFiles; +}