From d41c53dcfe1472cd2d9fb3ae7b30691aa76405f9 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Tue, 1 Dec 2020 13:19:24 -0600 Subject: [PATCH] fix(language-service): do not treat file URIs as general URLs In the past, the legacy (VE-based) language service would use a `UrlResolver` instance to resolve file paths, primarily for compiler resources like external templates. The problem with this is that the UrlResolver is designed to resolve URLs in general, and so for a path like `/a/b/#c`, `#c` is treated as hash/fragment rather than as part of the path, which can lead to unexpected path resolution (f.x., `resolve('a/b/#c/d.ts', './d.html')` would produce `'a/b/d.html'` rather than the expected `'a/b/#c/d.html'`). This commit resolves the issue by using Node's `path` module to resolve file paths directly, which aligns more with how resources are resolved in the Ivy compiler. The testing story here is not great, and the API for validating a file path could be a little bit prettier/robust. However, since the VE-based language service is going into more of a "maintenance mode" now that there is a clear path for the Ivy-based LS moving forward, I think it is okay not to spend too much time here. Closes https://github.com/angular/vscode-ng-language-service/issues/892 Closes https://github.com/angular/vscode-ng-language-service/issues/1001 --- packages/language-service/src/ts_plugin.ts | 4 +-- .../language-service/src/typescript_host.ts | 31 +++++++++++----- .../language-service/test/completions_spec.ts | 2 +- .../language-service/test/definitions_spec.ts | 2 +- .../language-service/test/diagnostics_spec.ts | 2 +- packages/language-service/test/hover_spec.ts | 2 +- .../test/language_service_spec.ts | 2 +- .../test/project/app/#inner/component.ts | 16 +++++++++ .../test/project/app/#inner/inner.html | 1 + .../language-service/test/project/app/main.ts | 2 ++ .../language-service/test/references_spec.ts | 2 +- .../test/reflector_host_spec.ts | 2 +- packages/language-service/test/test_utils.ts | 4 +-- .../language-service/test/ts_plugin_spec.ts | 21 +++++++---- .../test/typescript_host_spec.ts | 36 +++++++++++-------- 15 files changed, 90 insertions(+), 39 deletions(-) create mode 100644 packages/language-service/test/project/app/#inner/component.ts create mode 100644 packages/language-service/test/project/app/#inner/inner.html diff --git a/packages/language-service/src/ts_plugin.ts b/packages/language-service/src/ts_plugin.ts index a8da1e47a881b..db66483b67962 100644 --- a/packages/language-service/src/ts_plugin.ts +++ b/packages/language-service/src/ts_plugin.ts @@ -34,7 +34,7 @@ export function getExternalFiles(project: tss.server.Project): string[] { } export function create(info: tss.server.PluginCreateInfo): tss.LanguageService { - const {languageService: tsLS, languageServiceHost: tsLSHost, config, project} = info; + const {languageService: tsLS, languageServiceHost: tsLSHost, serverHost, config, project} = info; // This plugin could operate under two different modes: // 1. TS + Angular // Plugin augments TS language service to provide additional Angular @@ -45,7 +45,7 @@ export function create(info: tss.server.PluginCreateInfo): tss.LanguageService { // 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 ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, serverHost); const ngLS = createLanguageService(ngLSHost); PROJECT_MAP.set(project, ngLSHost); diff --git a/packages/language-service/src/typescript_host.ts b/packages/language-service/src/typescript_host.ts index ae927fd77d66e..2c9edf6d8b3fa 100644 --- a/packages/language-service/src/typescript_host.ts +++ b/packages/language-service/src/typescript_host.ts @@ -6,8 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -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'; +import {analyzeNgModules, AotSummaryResolver, CompileDirectiveSummary, CompileMetadataResolver, CompileNgModuleMetadata, CompilePipeSummary, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, FormattedError, FormattedMessageChain, HtmlParser, isFormattedError, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, Parser, ParseTreeResult, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, UrlResolver} from '@angular/compiler'; import {SchemaMetadata, ViewEncapsulation, ɵConsole as Console} from '@angular/core'; +import * as path from 'path'; import * as tss from 'typescript/lib/tsserverlibrary'; import {createLanguageService} from './language_service'; @@ -16,12 +17,15 @@ import {ExternalTemplate, InlineTemplate} from './template'; import {findTightestNode, getClassDeclFromDecoratorProp, getDirectiveClassLike, getPropertyAssignmentFromValue} from './ts_utils'; import {AstResult, Declaration, DeclarationError, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types'; +export type FileResolutionHost = Required>; + /** * Create a `LanguageServiceHost` */ export function createLanguageServiceFromTypescript( - host: tss.LanguageServiceHost, service: tss.LanguageService): LanguageService { - const ngHost = new TypeScriptServiceHost(host, service); + lsHost: tss.LanguageServiceHost, service: tss.LanguageService, + fileResolutionHost: FileResolutionHost): LanguageService { + const ngHost = new TypeScriptServiceHost(lsHost, service, fileResolutionHost); const ngServer = createLanguageService(ngHost); return ngServer; } @@ -64,6 +68,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { private readonly fileToComponent = new Map(); private readonly collectedErrors = new Map(); private readonly fileVersions = new Map(); + private readonly urlResolver: UrlResolver; private lastProgram: tss.Program|undefined = undefined; private analyzedModules: NgAnalyzedModules = { @@ -72,7 +77,11 @@ export class TypeScriptServiceHost implements LanguageServiceHost { ngModules: [], }; - constructor(readonly tsLsHost: tss.LanguageServiceHost, readonly tsLS: tss.LanguageService) { + constructor( + readonly tsLsHost: tss.LanguageServiceHost, + readonly tsLS: tss.LanguageService, + fileResolutionHost: FileResolutionHost, + ) { this.summaryResolver = new AotSummaryResolver( { loadSummary(_filePath: string) { @@ -93,6 +102,14 @@ export class TypeScriptServiceHost implements LanguageServiceHost { this.staticSymbolResolver = new StaticSymbolResolver( this.reflectorHost, this.staticSymbolCache, this.summaryResolver, (e, filePath) => this.collectError(e, filePath)); + this.urlResolver = { + resolve: (baseUrl: string, url: string) => { + if (fileResolutionHost.directoryExists(baseUrl)) { + return path.resolve(baseUrl, url); + } + return path.resolve(path.dirname(baseUrl), url); + } + }; } // The resolver is instantiated lazily and should not be accessed directly. @@ -125,7 +142,6 @@ export class TypeScriptServiceHost implements LanguageServiceHost { const pipeResolver = new PipeResolver(staticReflector); const elementSchemaRegistry = new DomElementSchemaRegistry(); const resourceLoader = new DummyResourceLoader(); - const urlResolver = createOfflineCompileUrlResolver(); const htmlParser = new DummyHtmlParser(); // This tracks the CompileConfig in codegen.ts. Currently these options // are hard-coded. @@ -134,7 +150,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { useJit: false, }); const directiveNormalizer = - new DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config); + new DirectiveNormalizer(resourceLoader, this.urlResolver, htmlParser, config); this._resolver = new CompileMetadataResolver( config, htmlParser, moduleResolver, directiveResolver, pipeResolver, new JitSummaryResolver(), elementSchemaRegistry, directiveNormalizer, new Console(), @@ -192,12 +208,11 @@ export class TypeScriptServiceHost implements LanguageServiceHost { } // update template references and fileToComponent - const urlResolver = createOfflineCompileUrlResolver(); for (const ngModule of this.analyzedModules.ngModules) { for (const directive of ngModule.declaredDirectives) { const {metadata} = this.resolver.getNonNormalizedDirectiveMetadata(directive.reference)!; if (metadata.isComponent && metadata.template && metadata.template.templateUrl) { - const templateName = urlResolver.resolve( + const templateName = this.urlResolver.resolve( this.reflector.componentModuleUrl(directive.reference), metadata.template.templateUrl); this.fileToComponent.set(templateName, directive.reference); diff --git a/packages/language-service/test/completions_spec.ts b/packages/language-service/test/completions_spec.ts index d39324426fdfe..b17e0dbdd0aa6 100644 --- a/packages/language-service/test/completions_spec.ts +++ b/packages/language-service/test/completions_spec.ts @@ -20,7 +20,7 @@ const TEST_TEMPLATE = '/app/test.ng'; describe('completions', () => { const mockHost = new MockTypescriptHost(['/app/main.ts']); const tsLS = ts.createLanguageService(mockHost); - const ngHost = new TypeScriptServiceHost(mockHost, tsLS); + const ngHost = new TypeScriptServiceHost(mockHost, tsLS, mockHost); const ngLS = createLanguageService(ngHost); beforeEach(() => { diff --git a/packages/language-service/test/definitions_spec.ts b/packages/language-service/test/definitions_spec.ts index 2bb0dcd5f89e5..582215849306d 100644 --- a/packages/language-service/test/definitions_spec.ts +++ b/packages/language-service/test/definitions_spec.ts @@ -19,7 +19,7 @@ const PARSING_CASES = '/app/parsing-cases.ts'; describe('definitions', () => { const mockHost = new MockTypescriptHost(['/app/main.ts']); const service = ts.createLanguageService(mockHost); - const ngHost = new TypeScriptServiceHost(mockHost, service); + const ngHost = new TypeScriptServiceHost(mockHost, service, mockHost); const ngService = createLanguageService(ngHost); beforeEach(() => { diff --git a/packages/language-service/test/diagnostics_spec.ts b/packages/language-service/test/diagnostics_spec.ts index 09fea059b4b27..37a8db5c4265c 100644 --- a/packages/language-service/test/diagnostics_spec.ts +++ b/packages/language-service/test/diagnostics_spec.ts @@ -29,7 +29,7 @@ const APP_COMPONENT = '/app/app.component.ts'; describe('diagnostics', () => { const mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts']); const tsLS = ts.createLanguageService(mockHost); - const ngHost = new TypeScriptServiceHost(mockHost, tsLS); + const ngHost = new TypeScriptServiceHost(mockHost, tsLS, mockHost); const ngLS = createLanguageService(ngHost); beforeEach(() => { diff --git a/packages/language-service/test/hover_spec.ts b/packages/language-service/test/hover_spec.ts index 5116c42f09acc..8941124a61423 100644 --- a/packages/language-service/test/hover_spec.ts +++ b/packages/language-service/test/hover_spec.ts @@ -19,7 +19,7 @@ const PARSING_CASES = '/app/parsing-cases.ts'; describe('hover', () => { const mockHost = new MockTypescriptHost(['/app/main.ts']); const tsLS = ts.createLanguageService(mockHost); - const ngLSHost = new TypeScriptServiceHost(mockHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(mockHost, tsLS, mockHost); const ngLS = createLanguageService(ngLSHost); beforeEach(() => { diff --git a/packages/language-service/test/language_service_spec.ts b/packages/language-service/test/language_service_spec.ts index 46742f7cce803..a168a67c58aef 100644 --- a/packages/language-service/test/language_service_spec.ts +++ b/packages/language-service/test/language_service_spec.ts @@ -16,7 +16,7 @@ import {MockTypescriptHost} from './test_utils'; describe('service without angular', () => { const mockHost = new MockTypescriptHost(['/app/main.ts']); const service = ts.createLanguageService(mockHost); - const ngHost = new TypeScriptServiceHost(mockHost, service); + const ngHost = new TypeScriptServiceHost(mockHost, service, mockHost); const ngService = createLanguageService(ngHost); const TEST_TEMPLATE = '/app/test.ng'; mockHost.override(TEST_TEMPLATE, '

~{cursor}

'); diff --git a/packages/language-service/test/project/app/#inner/component.ts b/packages/language-service/test/project/app/#inner/component.ts new file mode 100644 index 0000000000000..59a256721bd80 --- /dev/null +++ b/packages/language-service/test/project/app/#inner/component.ts @@ -0,0 +1,16 @@ +/** + * @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 {Component} from '@angular/core'; + +@Component({ + selector: 'inner', + templateUrl: './inner.html', +}) +export class InnerComponent { +} diff --git a/packages/language-service/test/project/app/#inner/inner.html b/packages/language-service/test/project/app/#inner/inner.html new file mode 100644 index 0000000000000..0fd9beb678ff4 --- /dev/null +++ b/packages/language-service/test/project/app/#inner/inner.html @@ -0,0 +1 @@ +
Hello
diff --git a/packages/language-service/test/project/app/main.ts b/packages/language-service/test/project/app/main.ts index 4860cc3930c91..0599c9a523c6b 100644 --- a/packages/language-service/test/project/app/main.ts +++ b/packages/language-service/test/project/app/main.ts @@ -9,6 +9,7 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {FormsModule} from '@angular/forms'; +import {InnerComponent} from './#inner/component'; import {AppComponent} from './app.component'; import * as ParsingCases from './parsing-cases'; @@ -16,6 +17,7 @@ import * as ParsingCases from './parsing-cases'; imports: [CommonModule, FormsModule], declarations: [ AppComponent, + InnerComponent, ParsingCases.CounterDirective, ParsingCases.HintModel, ParsingCases.NumberModel, diff --git a/packages/language-service/test/references_spec.ts b/packages/language-service/test/references_spec.ts index 0d9ab36100705..0e85b2c79b638 100644 --- a/packages/language-service/test/references_spec.ts +++ b/packages/language-service/test/references_spec.ts @@ -19,7 +19,7 @@ const TEST_TEMPLATE = '/app/test.ng'; describe('references', () => { const mockHost = new MockTypescriptHost(['/app/main.ts']); const tsLS = ts.createLanguageService(mockHost); - const ngHost = new TypeScriptServiceHost(mockHost, tsLS); + const ngHost = new TypeScriptServiceHost(mockHost, tsLS, mockHost); const ngLS = createLanguageService(ngHost); beforeEach(() => { diff --git a/packages/language-service/test/reflector_host_spec.ts b/packages/language-service/test/reflector_host_spec.ts index efc682f01104f..45ee85c4f4241 100644 --- a/packages/language-service/test/reflector_host_spec.ts +++ b/packages/language-service/test/reflector_host_spec.ts @@ -50,7 +50,7 @@ describe('reflector_host_spec', () => { // First count is zero due to lazy instantiation of the StaticReflector // and MetadataResolver. - const ngLSHost = new TypeScriptServiceHost(mockHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(mockHost, tsLS, mockHost); const firstCount = spy.calls.count(); expect(firstCount).toBe(0); spy.calls.reset(); diff --git a/packages/language-service/test/test_utils.ts b/packages/language-service/test/test_utils.ts index 0be4ff12cfa4c..39e7497e0a2fc 100644 --- a/packages/language-service/test/test_utils.ts +++ b/packages/language-service/test/test_utils.ts @@ -69,7 +69,7 @@ function loadTourOfHeroes(): ReadonlyMap { const value = fs.readFileSync(absPath, 'utf8'); files.set(key, value); } else { - const key = path.join('/', filePath); + const key = path.join('/', path.relative(root, absPath)); files.set(key, '[[directory]]'); dirs.push(absPath); } @@ -189,7 +189,7 @@ export class MockTypescriptHost implements ts.LanguageServiceHost { if (this.overrideDirectory.has(directoryName)) return true; const effectiveName = this.getEffectiveName(directoryName); if (effectiveName === directoryName) { - return TOH.has(directoryName); + return TOH.get(directoryName) === '[[directory]]'; } if (effectiveName === '/' + this.node_modules) { return true; diff --git a/packages/language-service/test/ts_plugin_spec.ts b/packages/language-service/test/ts_plugin_spec.ts index a57b9af930924..bf6395beda295 100644 --- a/packages/language-service/test/ts_plugin_spec.ts +++ b/packages/language-service/test/ts_plugin_spec.ts @@ -31,7 +31,11 @@ describe('plugin', () => { languageService: tsLS, languageServiceHost: mockHost, project: mockProject, - serverHost: {} as any, + serverHost: { + directoryExists(file: string) { + return mockHost.directoryExists(file); + }, + } as any, config: {}, }); @@ -57,8 +61,8 @@ describe('plugin', () => { const compilerDiags = tsLS.getCompilerOptionsDiagnostics(); expect(compilerDiags).toEqual([]); const sourceFiles = program.getSourceFiles().filter(f => !f.fileName.endsWith('.d.ts')); - // there are three .ts files in the test project - expect(sourceFiles.length).toBe(3); + // there are four .ts files in the test project + expect(sourceFiles.length).toBe(4); for (const {fileName} of sourceFiles) { const syntacticDiags = tsLS.getSyntacticDiagnostics(fileName); expect(syntacticDiags).toEqual([]); @@ -132,9 +136,10 @@ describe('plugin', () => { it('should return external templates when getExternalFiles() is called', () => { const externalTemplates = getExternalFiles(mockProject); - expect(externalTemplates).toEqual([ + expect(new Set(externalTemplates)).toEqual(new Set([ '/app/test.ng', - ]); + '/app/#inner/inner.html', + ])); }); }); @@ -145,7 +150,11 @@ describe(`with config 'angularOnly = true`, () => { languageService: tsLS, languageServiceHost: mockHost, project: mockProject, - serverHost: {} as any, + serverHost: { + directoryExists(file: string) { + return mockHost.directoryExists(file); + }, + } as any, config: { angularOnly: true, }, diff --git a/packages/language-service/test/typescript_host_spec.ts b/packages/language-service/test/typescript_host_spec.ts index 4719150fdbb95..ac03e7f27abd6 100644 --- a/packages/language-service/test/typescript_host_spec.ts +++ b/packages/language-service/test/typescript_host_spec.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import * as ngc from '@angular/compiler'; import * as ts from 'typescript'; import {TypeScriptServiceHost} from '../src/typescript_host'; @@ -18,7 +17,7 @@ describe('TypeScriptServiceHost', () => { it('should be able to create a typescript host and analyze modules', () => { const tsLSHost = new MockTypescriptHost(['/app/main.ts']); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const analyzedModules = ngLSHost.getAnalyzedModules(); expect(analyzedModules.files.length).toBeGreaterThan(0); expect(analyzedModules.ngModules.length).toBeGreaterThan(0); @@ -35,7 +34,7 @@ describe('TypeScriptServiceHost', () => { it('should be able to analyze modules without a tsconfig.json file', () => { const tsLSHost = new MockTypescriptHost(['foo.ts']); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const analyzedModules = ngLSHost.getAnalyzedModules(); expect(analyzedModules.files.length).toBeGreaterThan(0); expect(analyzedModules.ngModules.length).toBe(0); @@ -46,7 +45,7 @@ describe('TypeScriptServiceHost', () => { it('should not throw if there is no script names', () => { const tsLSHost = new MockTypescriptHost([]); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const analyzedModules = ngLSHost.getAnalyzedModules(); expect(analyzedModules.files.length).toBe(0); expect(analyzedModules.ngModules.length).toBe(0); @@ -58,7 +57,7 @@ describe('TypeScriptServiceHost', () => { // First create a TypescriptHost with empty script names const tsLSHost = new MockTypescriptHost([]); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const oldModules = ngLSHost.getAnalyzedModules(); expect(oldModules.ngModules).toEqual([]); // Now add a script, this would change the program @@ -74,7 +73,7 @@ describe('TypeScriptServiceHost', () => { it('should throw if getSourceFile is called on non-TS file', () => { const tsLSHost = new MockTypescriptHost([]); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); expect(() => { ngLSHost.getSourceFile('/src/test.ng'); }).toThrowError('Non-TS source file requested: /src/test.ng'); @@ -83,7 +82,7 @@ describe('TypeScriptServiceHost', () => { it('should be able to find a single inline template', () => { const tsLSHost = new MockTypescriptHost(['/app/app.component.ts']); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const templates = ngLSHost.getTemplates('/app/app.component.ts'); expect(templates.length).toBe(1); const template = templates[0]; @@ -93,7 +92,7 @@ describe('TypeScriptServiceHost', () => { it('should be able to find multiple inline templates', () => { const tsLSHost = new MockTypescriptHost(['/app/parsing-cases.ts']); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const templates = ngLSHost.getTemplates('/app/parsing-cases.ts'); expect(templates.length).toBe(1); }); @@ -101,7 +100,7 @@ describe('TypeScriptServiceHost', () => { it('should be able to find external template', () => { const tsLSHost = new MockTypescriptHost(['/app/main.ts']); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); ngLSHost.getAnalyzedModules(); const templates = ngLSHost.getTemplates('/app/test.ng'); expect(templates.length).toBe(1); @@ -109,11 +108,20 @@ describe('TypeScriptServiceHost', () => { expect(template.source).toContain('

{{hero.name}} details!

'); }); + // https://github.com/angular/vscode-ng-language-service/issues/892 + it('should resolve external templates with `#` in the path', () => { + const tsLSHost = new MockTypescriptHost(['/app/main.ts']); + const tsLS = ts.createLanguageService(tsLSHost); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); + ngLSHost.getAnalyzedModules(); + expect(ngLSHost.getExternalTemplates()).toContain('/app/#inner/inner.html'); + }); + // https://github.com/angular/angular/issues/32301 it('should clear caches when program changes', () => { const tsLSHost = new MockTypescriptHost(['/app/main.ts']); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const fileName = '/app/app.component.ts'; // Get initial state @@ -165,7 +173,7 @@ describe('TypeScriptServiceHost', () => { it('should not clear caches when external template changes', () => { const tsLSHost = new MockTypescriptHost(['/app/main.ts']); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const oldModules = ngLSHost.getAnalyzedModules(); const oldProgram = ngLSHost.program; tsLSHost.override('/app/test.ng', '
'); @@ -182,7 +190,7 @@ describe('TypeScriptServiceHost', () => { it('should not reload @angular/core on changes', () => { const tsLSHost = new MockTypescriptHost(['/app/main.ts']); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const oldModules = ngLSHost.getAnalyzedModules(); const ngCore = '/node_modules/@angular/core/core.d.ts'; const originalContent = tsLSHost.readFile(ngCore); @@ -203,7 +211,7 @@ describe('TypeScriptServiceHost', () => { it('should reload @angular/common on changes', () => { const tsLSHost = new MockTypescriptHost(['/app/main.ts']); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const oldModules = ngLSHost.getAnalyzedModules(); const ngCommon = '/node_modules/@angular/common/common.d.ts'; const originalContent = tsLSHost.readFile(ngCommon); @@ -222,7 +230,7 @@ describe('TypeScriptServiceHost', () => { // First create a TypescriptHost with empty script names const tsLSHost = new MockTypescriptHost([]); const tsLS = ts.createLanguageService(tsLSHost); - const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS, tsLSHost); const oldModules = ngLSHost.getAnalyzedModules(); expect(oldModules.ngModules).toEqual([]); // Now add a script, this would change the program