
`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
126 lines
4.7 KiB
TypeScript
126 lines
4.7 KiB
TypeScript
/**
|
|
* @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 * 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, project} = info;
|
|
// This plugin could operate under two different modes:
|
|
// 1. TS + Angular
|
|
// Plugin augments TS language service to provide additional Angular
|
|
// information. This only works with inline templates and is meant to be
|
|
// used as a local plugin (configured via tsconfig.json)
|
|
// 2. Angular only
|
|
// Plugin only provides information on Angular templates, no TS info at all.
|
|
// This effectively disables native TS features and is meant for internal
|
|
// use only.
|
|
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) {
|
|
if (!angularOnly) {
|
|
const results = tsLS.getCompletionsAtPosition(fileName, position, options);
|
|
if (results && results.entries.length) {
|
|
// If TS could answer the query, then return results immediately.
|
|
return results;
|
|
}
|
|
}
|
|
return ngLS.getCompletionsAtPosition(fileName, position, options);
|
|
}
|
|
|
|
function getQuickInfoAtPosition(fileName: string, position: number): tss.QuickInfo|undefined {
|
|
if (!angularOnly) {
|
|
const result = tsLS.getQuickInfoAtPosition(fileName, position);
|
|
if (result) {
|
|
// If TS could answer the query, then return results immediately.
|
|
return result;
|
|
}
|
|
}
|
|
return ngLS.getQuickInfoAtPosition(fileName, position);
|
|
}
|
|
|
|
function getSemanticDiagnostics(fileName: string): tss.Diagnostic[] {
|
|
const results: tss.Diagnostic[] = [];
|
|
if (!angularOnly) {
|
|
results.push(...tsLS.getSemanticDiagnostics(fileName));
|
|
}
|
|
// For semantic diagnostics we need to combine both TS + Angular results
|
|
results.push(...ngLS.getSemanticDiagnostics(fileName));
|
|
return results;
|
|
}
|
|
|
|
function getDefinitionAtPosition(
|
|
fileName: string, position: number): ReadonlyArray<tss.DefinitionInfo>|undefined {
|
|
if (!angularOnly) {
|
|
const results = tsLS.getDefinitionAtPosition(fileName, position);
|
|
if (results) {
|
|
// If TS could answer the query, then return results immediately.
|
|
return results;
|
|
}
|
|
}
|
|
const result = ngLS.getDefinitionAndBoundSpan(fileName, position);
|
|
if (!result || !result.definitions || !result.definitions.length) {
|
|
return;
|
|
}
|
|
return result.definitions;
|
|
}
|
|
|
|
function getDefinitionAndBoundSpan(
|
|
fileName: string, position: number): tss.DefinitionInfoAndBoundSpan|undefined {
|
|
if (!angularOnly) {
|
|
const result = tsLS.getDefinitionAndBoundSpan(fileName, position);
|
|
if (result) {
|
|
// If TS could answer the query, then return results immediately.
|
|
return result;
|
|
}
|
|
}
|
|
return ngLS.getDefinitionAndBoundSpan(fileName, position);
|
|
}
|
|
|
|
const proxy: tss.LanguageService = Object.assign(
|
|
// First clone the original TS language service
|
|
{}, tsLS,
|
|
// Then override the methods supported by Angular language service
|
|
{
|
|
getCompletionsAtPosition,
|
|
getQuickInfoAtPosition,
|
|
getSemanticDiagnostics,
|
|
getDefinitionAtPosition,
|
|
getDefinitionAndBoundSpan,
|
|
});
|
|
return proxy;
|
|
}
|