GeezerVison is showing github.com — fix(language-service): do not treat file URIs as general URLs (#39917) · angular/angular@829988b
File treeExpand file treeCollapse file tree Open diff view settings packages/language-service
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: packages/language-service/src/typescript_host.ts +15-5Lines changed: 15 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 6 | 6 | * found in the LICENSE file at https://angular.io/license | 7 | 7 | */ | 8 | 8 |
| 9 | | -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'; | | 9 | +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'; | 10 | 10 | import {SchemaMetadata, ViewEncapsulation, ɵConsole as Console} from '@angular/core'; | | 11 | +import * as path from 'path'; | 11 | 12 | import * as tss from 'typescript/lib/tsserverlibrary'; | 12 | 13 |
| 13 | 14 | import {createLanguageService} from './language_service'; | @@ -64,6 +65,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { | 64 | 65 | private readonly fileToComponent = new Map<string, StaticSymbol>(); | 65 | 66 | private readonly collectedErrors = new Map<string, any[]>(); | 66 | 67 | private readonly fileVersions = new Map<string, string>(); | | 68 | + private readonly urlResolver: UrlResolver; | 67 | 69 |
| 68 | 70 | private lastProgram: tss.Program|undefined = undefined; | 69 | 71 | private analyzedModules: NgAnalyzedModules = { | @@ -93,6 +95,16 @@ export class TypeScriptServiceHost implements LanguageServiceHost { | 93 | 95 | this.staticSymbolResolver = new StaticSymbolResolver( | 94 | 96 | this.reflectorHost, this.staticSymbolCache, this.summaryResolver, | 95 | 97 | (e, filePath) => this.collectError(e, filePath)); | | 98 | + this.urlResolver = { | | 99 | + resolve: (baseUrl: string, url: string) => { | | 100 | + // In practice, `directoryExists` is always defined. | | 101 | + // https://github.com/microsoft/TypeScript/blob/0b6c9254a850dd07056259d4eefca7721745af75/src/server/project.ts#L1608-L1614 | | 102 | + if (tsLsHost.directoryExists!(baseUrl)) { | | 103 | + return path.resolve(baseUrl, url); | | 104 | + } | | 105 | + return path.resolve(path.dirname(baseUrl), url); | | 106 | + } | | 107 | + }; | 96 | 108 | } | 97 | 109 |
| 98 | 110 | // The resolver is instantiated lazily and should not be accessed directly. | @@ -125,7 +137,6 @@ export class TypeScriptServiceHost implements LanguageServiceHost { | 125 | 137 | const pipeResolver = new PipeResolver(staticReflector); | 126 | 138 | const elementSchemaRegistry = new DomElementSchemaRegistry(); | 127 | 139 | const resourceLoader = new DummyResourceLoader(); | 128 | | - const urlResolver = createOfflineCompileUrlResolver(); | 129 | 140 | const htmlParser = new DummyHtmlParser(); | 130 | 141 | // This tracks the CompileConfig in codegen.ts. Currently these options | 131 | 142 | // are hard-coded. | @@ -134,7 +145,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { | 134 | 145 | useJit: false, | 135 | 146 | }); | 136 | 147 | const directiveNormalizer = | 137 | | - new DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config); | | 148 | + new DirectiveNormalizer(resourceLoader, this.urlResolver, htmlParser, config); | 138 | 149 | this._resolver = new CompileMetadataResolver( | 139 | 150 | config, htmlParser, moduleResolver, directiveResolver, pipeResolver, | 140 | 151 | new JitSummaryResolver(), elementSchemaRegistry, directiveNormalizer, new Console(), | @@ -192,12 +203,11 @@ export class TypeScriptServiceHost implements LanguageServiceHost { | 192 | 203 | } | 193 | 204 |
| 194 | 205 | // update template references and fileToComponent | 195 | | - const urlResolver = createOfflineCompileUrlResolver(); | 196 | 206 | for (const ngModule of this.analyzedModules.ngModules) { | 197 | 207 | for (const directive of ngModule.declaredDirectives) { | 198 | 208 | const {metadata} = this.resolver.getNonNormalizedDirectiveMetadata(directive.reference)!; | 199 | 209 | if (metadata.isComponent && metadata.template && metadata.template.templateUrl) { | 200 | | - const templateName = urlResolver.resolve( | | 210 | + const templateName = this.urlResolver.resolve( | 201 | 211 | this.reflector.componentModuleUrl(directive.reference), | 202 | 212 | metadata.template.templateUrl); | 203 | 213 | this.fileToComponent.set(templateName, directive.reference); | |
Collapse file Copy file name to clipboard +16Lines changed: 16 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +/** | | 2 | + * @license | | 3 | + * Copyright Google LLC 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 {Component} from '@angular/core'; | | 10 | + | | 11 | +@Component({ | | 12 | + selector: 'inner', | | 13 | + templateUrl: './inner.html', | | 14 | +}) | | 15 | +export class InnerComponent { | | 16 | +} |
Collapse file Copy file name to clipboard +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +<div>Hello</div> |
Collapse file Copy file name to clipboardExpand all lines: packages/language-service/test/project/app/main.ts +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 9 | 9 | import {CommonModule} from '@angular/common'; | 10 | 10 | import {NgModule} from '@angular/core'; | 11 | 11 | import {FormsModule} from '@angular/forms'; | | 12 | +import {InnerComponent} from './#inner/component'; | 12 | 13 | import {AppComponent} from './app.component'; | 13 | 14 | import * as ParsingCases from './parsing-cases'; | 14 | 15 |
| 15 | 16 | @NgModule({ | 16 | 17 | imports: [CommonModule, FormsModule], | 17 | 18 | declarations: [ | 18 | 19 | AppComponent, | | 20 | + InnerComponent, | 19 | 21 | ParsingCases.CounterDirective, | 20 | 22 | ParsingCases.HintModel, | 21 | 23 | ParsingCases.NumberModel, | |
Collapse file Copy file name to clipboardExpand all lines: packages/language-service/test/test_utils.ts +2-2Lines changed: 2 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -69,7 +69,7 @@ function loadTourOfHeroes(): ReadonlyMap<string, string> { | 69 | 69 | const value = fs.readFileSync(absPath, 'utf8'); | 70 | 70 | files.set(key, value); | 71 | 71 | } else { | 72 | | - const key = path.join('/', filePath); | | 72 | + const key = path.join('/', path.relative(root, absPath)); | 73 | 73 | files.set(key, '[[directory]]'); | 74 | 74 | dirs.push(absPath); | 75 | 75 | } | @@ -189,7 +189,7 @@ export class MockTypescriptHost implements ts.LanguageServiceHost { | 189 | 189 | if (this.overrideDirectory.has(directoryName)) return true; | 190 | 190 | const effectiveName = this.getEffectiveName(directoryName); | 191 | 191 | if (effectiveName === directoryName) { | 192 | | - return TOH.has(directoryName); | | 192 | + return TOH.get(directoryName) === '[[directory]]'; | 193 | 193 | } | 194 | 194 | if (effectiveName === '/' + this.node_modules) { | 195 | 195 | return true; | |
Collapse file Copy file name to clipboardExpand all lines: packages/language-service/test/ts_plugin_spec.ts +5-4Lines changed: 5 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -58,8 +58,8 @@ describe('plugin', () => { | 58 | 58 | const compilerDiags = tsLS.getCompilerOptionsDiagnostics(); | 59 | 59 | expect(compilerDiags).toEqual([]); | 60 | 60 | const sourceFiles = program.getSourceFiles().filter(f => !f.fileName.endsWith('.d.ts')); | 61 | | - // there are three .ts files in the test project | 62 | | - expect(sourceFiles.length).toBe(3); | | 61 | + // there are four .ts files in the test project | | 62 | + expect(sourceFiles.length).toBe(4); | 63 | 63 | for (const {fileName} of sourceFiles) { | 64 | 64 | const syntacticDiags = tsLS.getSyntacticDiagnostics(fileName); | 65 | 65 | expect(syntacticDiags).toEqual([]); | @@ -133,9 +133,10 @@ describe('plugin', () => { | 133 | 133 |
| 134 | 134 | it('should return external templates when getExternalFiles() is called', () => { | 135 | 135 | const externalTemplates = getExternalFiles(mockProject); | 136 | | - expect(externalTemplates).toEqual([ | | 136 | + expect(new Set(externalTemplates)).toEqual(new Set([ | 137 | 137 | '/app/test.ng', | 138 | | - ]); | | 138 | + '/app/#inner/inner.html', | | 139 | + ])); | 139 | 140 | }); | 140 | 141 |
| 141 | 142 | it('should not return external template that does not exist', () => { | |
Collapse file Copy file name to clipboardExpand all lines: packages/language-service/test/typescript_host_spec.ts +9-1Lines changed: 9 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 6 | 6 | * found in the LICENSE file at https://angular.io/license | 7 | 7 | */ | 8 | 8 |
| 9 | | -import * as ngc from '@angular/compiler'; | 10 | 9 | import * as ts from 'typescript'; | 11 | 10 |
| 12 | 11 | import {TypeScriptServiceHost} from '../src/typescript_host'; | @@ -109,6 +108,15 @@ describe('TypeScriptServiceHost', () => { | 109 | 108 | expect(template.source).toContain('<h2>{{hero.name}} details!</h2>'); | 110 | 109 | }); | 111 | 110 |
| | 111 | + // https://github.com/angular/vscode-ng-language-service/issues/892 | | 112 | + it('should resolve external templates with `#` in the path', () => { | | 113 | + const tsLSHost = new MockTypescriptHost(['/app/main.ts']); | | 114 | + const tsLS = ts.createLanguageService(tsLSHost); | | 115 | + const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS); | | 116 | + ngLSHost.getAnalyzedModules(); | | 117 | + expect(ngLSHost.getExternalTemplates()).toContain('/app/#inner/inner.html'); | | 118 | + }); | | 119 | + | 112 | 120 | // https://github.com/angular/angular/issues/32301 | 113 | 121 | it('should clear caches when program changes', () => { | 114 | 122 | const tsLSHost = new MockTypescriptHost(['/app/main.ts']); | |
|