The current "go to definition" is broken for template variables and references when a template is overridden. This is because we get the file url from the source span, which uses the overridden name 'override.html'. Instead, we can retrieve the template file from the compiler in the same manner that is done for references. Another way to fix this would have been to use the real template file path when overriding a template, but this was the more straightforward fix since the strategy was already used in find references and rename locations. fixes https://github.com/angular/vscode-ng-language-service/issues/1054 PR Close #40455
89 lines
3.5 KiB
TypeScript
89 lines
3.5 KiB
TypeScript
/**
|
|
* @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 {absoluteFrom as _} from '@angular/compiler-cli/src/ngtsc/file_system';
|
|
import {TestFile} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
|
|
import {LanguageServiceTestEnvironment, TestableOptions} from '@angular/language-service/ivy/test/env';
|
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
|
|
|
|
|
export function getText(contents: string, textSpan: ts.TextSpan) {
|
|
return contents.substr(textSpan.start, textSpan.length);
|
|
}
|
|
|
|
function last<T>(array: T[]): T {
|
|
return array[array.length - 1];
|
|
}
|
|
|
|
function getFirstClassDeclaration(declaration: string) {
|
|
const matches = declaration.match(/(?:export class )(\w+)(?:\s|\{)/);
|
|
if (matches === null || matches.length !== 2) {
|
|
throw new Error(`Did not find exactly one exported class in: ${declaration}`);
|
|
}
|
|
return matches[1].trim();
|
|
}
|
|
|
|
export function createModuleWithDeclarations(
|
|
filesWithClassDeclarations: TestFile[], externalResourceFiles: TestFile[] = [],
|
|
options: TestableOptions = {}): LanguageServiceTestEnvironment {
|
|
const externalClasses =
|
|
filesWithClassDeclarations.map(file => getFirstClassDeclaration(file.contents));
|
|
const externalImports = filesWithClassDeclarations.map(file => {
|
|
const className = getFirstClassDeclaration(file.contents);
|
|
const fileName = last(file.name.split('/')).replace('.ts', '');
|
|
return `import {${className}} from './${fileName}';`;
|
|
});
|
|
const contents = `
|
|
import {NgModule} from '@angular/core';
|
|
import {CommonModule} from '@angular/common';
|
|
${externalImports.join('\n')}
|
|
|
|
@NgModule({
|
|
declarations: [${externalClasses.join(',')}],
|
|
imports: [CommonModule],
|
|
})
|
|
export class AppModule {}
|
|
`;
|
|
const moduleFile = {name: _('/app-module.ts'), contents, isRoot: true};
|
|
return LanguageServiceTestEnvironment.setup(
|
|
[moduleFile, ...filesWithClassDeclarations, ...externalResourceFiles], options);
|
|
}
|
|
|
|
export function humanizeDocumentSpanLike<T extends ts.DocumentSpan>(
|
|
item: T, env: LanguageServiceTestEnvironment, overrides: Map<string, string> = new Map()): T&
|
|
Stringy<ts.DocumentSpan> {
|
|
const fileContents = (overrides.has(item.fileName) ? overrides.get(item.fileName) :
|
|
env.host.readFile(item.fileName)) ??
|
|
'';
|
|
if (!fileContents) {
|
|
throw new Error(`Could not read file ${item.fileName}`);
|
|
}
|
|
return {
|
|
...item,
|
|
textSpan: getText(fileContents, item.textSpan),
|
|
contextSpan: item.contextSpan ? getText(fileContents, item.contextSpan) : undefined,
|
|
originalTextSpan: item.originalTextSpan ? getText(fileContents, item.originalTextSpan) :
|
|
undefined,
|
|
originalContextSpan:
|
|
item.originalContextSpan ? getText(fileContents, item.originalContextSpan) : undefined,
|
|
};
|
|
}
|
|
type Stringy<T> = {
|
|
[P in keyof T]: string;
|
|
};
|
|
|
|
export function assertFileNames(refs: Array<{fileName: string}>, expectedFileNames: string[]) {
|
|
const actualPaths = refs.map(r => r.fileName);
|
|
const actualFileNames = actualPaths.map(p => last(p.split('/')));
|
|
expect(new Set(actualFileNames)).toEqual(new Set(expectedFileNames));
|
|
}
|
|
|
|
export function assertTextSpans(items: Array<{textSpan: string}>, expectedTextSpans: string[]) {
|
|
const actualSpans = items.map(item => item.textSpan);
|
|
expect(new Set(actualSpans)).toEqual(new Set(expectedTextSpans));
|
|
}
|