fix(language-service): Paths on Windows should be normalized (#40492)
Many `ts.LanguageService` APIs accept a filename, for example
```ts
getQuickInfoAtPosition(fileName: string, position: number)
```
The requirement is that `fileName` is agnostic to the platform (Linux, Mac,
Windows, etc), and is always normalized to TypeScript's internal
`NormalizedPath`.
This is evident from the way these APIs are called from the language server:
```ts
private onHover(params: lsp.TextDocumentPositionParams) {
const lsInfo = this.getLSAndScriptInfo(params.textDocument);
if (lsInfo === undefined) {
return;
}
const {languageService, scriptInfo} = lsInfo;
const offset = lspPositionToTsPosition(scriptInfo, params.position);
const info = languageService.getQuickInfoAtPosition(scriptInfo.fileName, offset);
// ...
}
```
9fca9c6651/server/src/session.ts (L594)
Here `scriptInfo.fileName` is always a `ts.server.NormalizedPath`.
However, https://github.com/angular/angular/pull/39917 accidentally leaked
the platform-specific paths, and caused a mismatch between the incoming paths
and the paths stored in the internal data structure `fileToComponent`.
This PR fixes the bug by always normalizing the paths, and updating the
type to reflect the format of the underlying data.
Fixes https://github.com/angular/vscode-ng-language-service/issues/1063
PR Close #40492
This commit is contained in:
parent
402e2e6189
commit
e1e1db3c01
@ -62,7 +62,12 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
|||||||
private readonly staticSymbolResolver: StaticSymbolResolver;
|
private readonly staticSymbolResolver: StaticSymbolResolver;
|
||||||
|
|
||||||
private readonly staticSymbolCache = new StaticSymbolCache();
|
private readonly staticSymbolCache = new StaticSymbolCache();
|
||||||
private readonly fileToComponent = new Map<string, StaticSymbol>();
|
/**
|
||||||
|
* 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<ts.server.NormalizedPath, StaticSymbol>();
|
||||||
private readonly collectedErrors = new Map<string, any[]>();
|
private readonly collectedErrors = new Map<string, any[]>();
|
||||||
private readonly fileVersions = new Map<string, string>();
|
private readonly fileVersions = new Map<string, string>();
|
||||||
private readonly urlResolver: UrlResolver;
|
private readonly urlResolver: UrlResolver;
|
||||||
@ -165,7 +170,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
|||||||
/**
|
/**
|
||||||
* Return all known external templates.
|
* Return all known external templates.
|
||||||
*/
|
*/
|
||||||
getExternalTemplates(): string[] {
|
getExternalTemplates(): ts.server.NormalizedPath[] {
|
||||||
return [...this.fileToComponent.keys()];
|
return [...this.fileToComponent.keys()];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +215,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
|||||||
const templateName = this.urlResolver.resolve(
|
const templateName = this.urlResolver.resolve(
|
||||||
this.reflector.componentModuleUrl(directive.reference),
|
this.reflector.componentModuleUrl(directive.reference),
|
||||||
metadata.template.templateUrl);
|
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());
|
const source = snapshot.getText(0, snapshot.getLength());
|
||||||
// Next find the component class symbol
|
// Next find the component class symbol
|
||||||
const classSymbol = this.fileToComponent.get(fileName);
|
const classSymbol = this.fileToComponent.get(tss.server.toNormalizedPath(fileName));
|
||||||
if (!classSymbol) {
|
if (!classSymbol) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {TypeScriptServiceHost} from '../src/typescript_host';
|
import {TypeScriptServiceHost} from '../src/typescript_host';
|
||||||
@ -114,7 +115,7 @@ describe('TypeScriptServiceHost', () => {
|
|||||||
const tsLS = ts.createLanguageService(tsLSHost);
|
const tsLS = ts.createLanguageService(tsLSHost);
|
||||||
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
const ngLSHost = new TypeScriptServiceHost(tsLSHost, tsLS);
|
||||||
ngLSHost.getAnalyzedModules();
|
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
|
// https://github.com/angular/angular/issues/32301
|
||||||
@ -265,4 +266,19 @@ describe('TypeScriptServiceHost', () => {
|
|||||||
newModules = ngLSHost.getAnalyzedModules();
|
newModules = ngLSHost.getAnalyzedModules();
|
||||||
expect(newModules.ngModules.length).toBeGreaterThan(0);
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user