diff --git a/packages/language-service/src/typescript_host.ts b/packages/language-service/src/typescript_host.ts index 5bc657e0cc..934501daf9 100644 --- a/packages/language-service/src/typescript_host.ts +++ b/packages/language-service/src/typescript_host.ts @@ -62,7 +62,12 @@ export class TypeScriptServiceHost implements LanguageServiceHost { private readonly staticSymbolResolver: StaticSymbolResolver; private readonly staticSymbolCache = new StaticSymbolCache(); - private readonly fileToComponent = new Map(); + /** + * Key of the `fileToComponent` map must be TS internal normalized path (path + * separator must be `/`), value of the map is the StaticSymbol for the + * Component class declaration. + */ + private readonly fileToComponent = new Map(); private readonly collectedErrors = new Map(); private readonly fileVersions = new Map(); private readonly urlResolver: UrlResolver; @@ -165,7 +170,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { /** * Return all known external templates. */ - getExternalTemplates(): string[] { + getExternalTemplates(): ts.server.NormalizedPath[] { return [...this.fileToComponent.keys()]; } @@ -210,7 +215,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { const templateName = this.urlResolver.resolve( this.reflector.componentModuleUrl(directive.reference), metadata.template.templateUrl); - this.fileToComponent.set(templateName, directive.reference); + this.fileToComponent.set(tss.server.toNormalizedPath(templateName), directive.reference); } } } @@ -417,7 +422,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { } const source = snapshot.getText(0, snapshot.getLength()); // Next find the component class symbol - const classSymbol = this.fileToComponent.get(fileName); + const classSymbol = this.fileToComponent.get(tss.server.toNormalizedPath(fileName)); if (!classSymbol) { return; } diff --git a/packages/language-service/test/typescript_host_spec.ts b/packages/language-service/test/typescript_host_spec.ts index f1bab4d1bc..ee4b7ed69c 100644 --- a/packages/language-service/test/typescript_host_spec.ts +++ b/packages/language-service/test/typescript_host_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import * as path from 'path'; import * as ts from 'typescript'; import {TypeScriptServiceHost} from '../src/typescript_host'; @@ -114,7 +115,7 @@ describe('TypeScriptServiceHost', () => { const tsLS = ts.createLanguageService(tsLSHost); const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); ngLSHost.getAnalyzedModules(); - expect(ngLSHost.getExternalTemplates()).toContain('/app/#inner/inner.html'); + expect(ngLSHost.getExternalTemplates() as string[]).toContain('/app/#inner/inner.html'); }); // https://github.com/angular/angular/issues/32301 @@ -238,7 +239,7 @@ describe('TypeScriptServiceHost', () => { let content = ` import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; - + @NgModule({ entryComponents: [CommonModule], }) @@ -256,7 +257,7 @@ describe('TypeScriptServiceHost', () => { content = ` import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; - + @NgModule({}) export class AppModule {} `; @@ -265,4 +266,19 @@ describe('TypeScriptServiceHost', () => { newModules = ngLSHost.getAnalyzedModules(); expect(newModules.ngModules.length).toBeGreaterThan(0); }); + + it('should normalize path on Windows', () => { + // Spy on the `path.resolve()` method called by the URL resolver and mimic + // behavior on Windows. + spyOn(path, 'resolve').and.callFake((...pathSegments: string[]) => { + return path.win32.resolve(...pathSegments); + }); + const tsLSHost = new MockTypescriptHost(['/app/main.ts']); + const tsLS = ts.createLanguageService(tsLSHost); + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); + ngLSHost.getAnalyzedModules(); + const externalTemplates: string[] = ngLSHost.getExternalTemplates(); + // External templates should be normalized. + expect(externalTemplates).toContain('/app/test.ng'); + }); });