Skip to content

Commit cdda02d

Browse files
committed
feat(language-service): provide external files to tsserver.
Official angular language service removed GetExternalFiles() method that is necessary to support external templates from typescript plugin. This commit reverts f05e171. So I can use angular language service without duplicated tsserver instance.
1 parent dcb0dda commit cdda02d

File tree

6 files changed

+114
-2
lines changed

6 files changed

+114
-2
lines changed

packages/language-service/src/ts_plugin.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,50 @@ import * as tss from 'typescript/lib/tsserverlibrary';
1111
import {createLanguageService} from './language_service';
1212
import {TypeScriptServiceHost} from './typescript_host';
1313

14+
/**
15+
* A note about importing TypeScript module.
16+
* The TypeScript module is supplied by tsserver at runtime to ensure version
17+
* compatibility. In Angular language service, the rollup output is augmented
18+
* with a "banner" shim that overwrites 'typescript' and
19+
* 'typescript/lib/tsserverlibrary' imports with the value supplied by tsserver.
20+
* This means import of either modules will not be "required", but they'll work
21+
* just like regular imports.
22+
*/
23+
24+
const projectHostMap = new WeakMap<tss.server.Project, TypeScriptServiceHost>();
25+
26+
/**
27+
* Return the external templates discovered through processing all NgModules in
28+
* the specified `project`.
29+
* This function is called in a few situations:
30+
* 1. When a ConfiguredProject is created
31+
* https://github.com/microsoft/TypeScript/blob/c26c44d5fceb04ea14da20b6ed23449df777ff34/src/server/editorServices.ts#L1755
32+
* 2. When updateGraph() is called on a Project
33+
* https://github.com/microsoft/TypeScript/blob/c26c44d5fceb04ea14da20b6ed23449df777ff34/src/server/project.ts#L915
34+
* @param project Most likely a ConfiguredProject
35+
*/
36+
export function getExternalFiles(project: tss.server.Project): string[] {
37+
if (!project.hasRoots()) {
38+
// During project initialization where there is no root files yet we should
39+
// not do any work.
40+
return [];
41+
}
42+
const ngLSHost = projectHostMap.get(project);
43+
if (!ngLSHost) {
44+
// Without an Angular host there is no way to get template references.
45+
return [];
46+
}
47+
const templates = ngLSHost.getTemplateReferences();
48+
const logger = project.projectService.logger;
49+
if (logger.hasLevel(tss.server.LogLevel.verbose)) {
50+
// Log external files to help debugging.
51+
logger.info(`External files in ${project.projectName}: ${JSON.stringify(templates)}`);
52+
}
53+
return templates;
54+
}
55+
1456
export function create(info: tss.server.PluginCreateInfo): tss.LanguageService {
15-
const {languageService: tsLS, languageServiceHost: tsLSHost, config} = info;
57+
const {project, languageService: tsLS, languageServiceHost: tsLSHost, config} = info;
1658
// This plugin could operate under two different modes:
1759
// 1. TS + Angular
1860
// Plugin augments TS language service to provide additional Angular
@@ -25,6 +67,7 @@ export function create(info: tss.server.PluginCreateInfo): tss.LanguageService {
2567
const angularOnly = config ? config.angularOnly === true : false;
2668
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
2769
const ngLS = createLanguageService(ngLSHost);
70+
projectHostMap.set(project, ngLSHost);
2871

2972
function getCompletionsAtPosition(
3073
fileName: string, position: number, options: tss.GetCompletionsAtPositionOptions|undefined) {

packages/language-service/src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ export interface LanguageServiceHost {
154154
*/
155155
getAnalyzedModules(): NgAnalyzedModules;
156156

157+
/**
158+
* Return a list all the template files referenced by the project.
159+
*/
160+
getTemplateReferences(): string[];
161+
157162
/**
158163
* Return the AST for both HTML and template for the contextFile.
159164
*/

packages/language-service/src/typescript_host.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {analyzeNgModules, AotSummaryResolver, CompileDirectiveSummary, CompileMetadataResolver, CompileNgModuleMetadata, CompilePipeSummary, CompilerConfig, createOfflineCompileUrlResolver, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, FormattedError, FormattedMessageChain, HtmlParser, isFormattedError, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, Parser, ParseTreeResult, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser} from '@angular/compiler';
1010
import {SchemaMetadata, ViewEncapsulation, ɵConsole as Console} from '@angular/core';
11+
import * as ts from 'typescript';
1112
import * as tss from 'typescript/lib/tsserverlibrary';
1213

1314
import {createLanguageService} from './language_service';
@@ -66,6 +67,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
6667
private readonly fileVersions = new Map<string, string>();
6768

6869
private lastProgram: tss.Program|undefined = undefined;
70+
private templateReferences: string[] = [];
6971
private analyzedModules: NgAnalyzedModules = {
7072
files: [],
7173
ngModuleByPipeOrDirective: new Map(),
@@ -151,6 +153,11 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
151153
return this.resolver.getReflector() as StaticReflector;
152154
}
153155

156+
getTemplateReferences(): string[] {
157+
this.getAnalyzedModules();
158+
return [...this.templateReferences];
159+
}
160+
154161
/**
155162
* Checks whether the program has changed and returns all analyzed modules.
156163
* If program has changed, invalidate all caches and update fileToComponent
@@ -164,6 +171,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
164171
}
165172

166173
// Invalidate caches
174+
this.templateReferences = [];
167175
this.fileToComponent.clear();
168176
this.collectedErrors.clear();
169177
this.resolver.clearCache();
@@ -194,6 +202,11 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
194202
this.reflector.componentModuleUrl(directive.reference),
195203
metadata.template.templateUrl);
196204
this.fileToComponent.set(templateName, directive.reference);
205+
if (this.tsLsHost.fileExists && this.tsLsHost.fileExists(templateName)) {
206+
this.templateReferences.push(templateName);
207+
} else if (!!this.tsLsHost.getScriptSnapshot(templateName)) {
208+
this.templateReferences.push(templateName);
209+
}
197210
}
198211
}
199212
}

packages/language-service/test/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ ts_library(
4848
"html_info_spec.ts",
4949
"language_service_spec.ts",
5050
"reflector_host_spec.ts",
51+
"template_references_spec.ts",
5152
"ts_plugin_spec.ts",
5253
"typescript_host_spec.ts",
5354
"utils_spec.ts",
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import * as ts from 'typescript';
10+
11+
import {TypeScriptServiceHost} from '../src/typescript_host';
12+
13+
import {MockTypescriptHost} from './test_utils';
14+
15+
describe('references', () => {
16+
const mockHost = new MockTypescriptHost(['/app/main.ts']);
17+
const service = ts.createLanguageService(mockHost);
18+
const ngHost = new TypeScriptServiceHost(mockHost, service);
19+
20+
beforeEach(() => { mockHost.reset(); });
21+
22+
it('should be able to determine that test.ng is a template reference', () => {
23+
const templates = ngHost.getTemplateReferences();
24+
expect(templates).toEqual(['/app/test.ng']);
25+
});
26+
27+
it('should be able to get template references for an invalid project', () => {
28+
const moduleCode = `
29+
import {NgModule} from '@angular/core';
30+
import {NewClass} from './test.component';
31+
32+
@NgModule({declarations: [NewClass]}) export class TestModule {}`;
33+
const classCode = `
34+
export class NewClass {}
35+
36+
@Component({})
37+
export class SomeComponent {}
38+
`;
39+
mockHost.addScript('/app/test.module.ts', moduleCode);
40+
mockHost.addScript('/app/test.component.ts', classCode);
41+
const templates = ngHost.getTemplateReferences();
42+
expect(templates).toEqual(['/app/test.ng']);
43+
});
44+
45+
});

packages/language-service/test/ts_plugin_spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import * as ts from 'typescript';
1010

11-
import {create} from '../src/ts_plugin';
11+
import {create, getExternalFiles} from '../src/ts_plugin';
1212
import {CompletionKind} from '../src/types';
1313

1414
import {MockTypescriptHost} from './test_utils';
@@ -67,6 +67,11 @@ describe('plugin', () => {
6767
}
6868
});
6969

70+
it('should return external templates as external files', () => {
71+
const externalFiles = getExternalFiles(mockProject);
72+
expect(externalFiles).toEqual(['/app/test.ng']);
73+
});
74+
7075
it('should not report template errors on tour of heroes', () => {
7176
const filesWithTemplates = [
7277
// Ignore all '*-cases.ts' files as they intentionally contain errors.

0 commit comments

Comments
 (0)