fix(language-service): 'go to defininition' for objects defined in template (#42559)
Previously, the "go to definition" action did no account for the possibility that something may actually be defined in a template. This change updates the logic in the definition builder to convert any results that are locations in template typecheck files to their corresponding locations in the template. PR Close #42559
This commit is contained in:
parent
228beeabd1
commit
4001e9d808
@ -8,10 +8,13 @@
|
|||||||
|
|
||||||
import {AST, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstTemplate, TmplAstTextAttribute} from '@angular/compiler';
|
import {AST, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstTemplate, TmplAstTextAttribute} from '@angular/compiler';
|
||||||
import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core';
|
import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core';
|
||||||
|
import {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
|
||||||
import {isExternalResource} from '@angular/compiler-cli/src/ngtsc/metadata';
|
import {isExternalResource} from '@angular/compiler-cli/src/ngtsc/metadata';
|
||||||
|
import {ProgramDriver} from '@angular/compiler-cli/src/ngtsc/program_driver';
|
||||||
import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ShimLocation, Symbol, SymbolKind, TemplateSymbol} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ShimLocation, Symbol, SymbolKind, TemplateSymbol} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {convertToTemplateDocumentSpan} from './references_and_rename_utils';
|
||||||
import {getTargetAtPosition, TargetNodeKind} from './template_target';
|
import {getTargetAtPosition, TargetNodeKind} from './template_target';
|
||||||
import {findTightestNode, getParentClassDeclaration} from './ts_utils';
|
import {findTightestNode, getParentClassDeclaration} from './ts_utils';
|
||||||
import {flatMap, getDirectiveMatchesForAttribute, getDirectiveMatchesForElementTag, getTemplateInfoAtPosition, getTemplateLocationFromShimLocation, getTextSpanOfNode, isDollarEvent, isTypeScriptFile, TemplateInfo, toTextSpan} from './utils';
|
import {flatMap, getDirectiveMatchesForAttribute, getDirectiveMatchesForElementTag, getTemplateInfoAtPosition, getTemplateLocationFromShimLocation, getTextSpanOfNode, isDollarEvent, isTypeScriptFile, TemplateInfo, toTextSpan} from './utils';
|
||||||
@ -27,7 +30,11 @@ interface HasShimLocation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DefinitionBuilder {
|
export class DefinitionBuilder {
|
||||||
constructor(private readonly tsLS: ts.LanguageService, private readonly compiler: NgCompiler) {}
|
private readonly ttc = this.compiler.getTemplateTypeChecker();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly tsLS: ts.LanguageService, private readonly compiler: NgCompiler,
|
||||||
|
private readonly driver: ProgramDriver) {}
|
||||||
|
|
||||||
getDefinitionAndBoundSpan(fileName: string, position: number): ts.DefinitionInfoAndBoundSpan
|
getDefinitionAndBoundSpan(fileName: string, position: number): ts.DefinitionInfoAndBoundSpan
|
||||||
|undefined {
|
|undefined {
|
||||||
@ -132,10 +139,36 @@ export class DefinitionBuilder {
|
|||||||
private getDefinitionsForSymbols(...symbols: HasShimLocation[]): ts.DefinitionInfo[] {
|
private getDefinitionsForSymbols(...symbols: HasShimLocation[]): ts.DefinitionInfo[] {
|
||||||
return flatMap(symbols, ({shimLocation}) => {
|
return flatMap(symbols, ({shimLocation}) => {
|
||||||
const {shimPath, positionInShimFile} = shimLocation;
|
const {shimPath, positionInShimFile} = shimLocation;
|
||||||
return this.tsLS.getDefinitionAtPosition(shimPath, positionInShimFile) ?? [];
|
const definitionInfos = this.tsLS.getDefinitionAtPosition(shimPath, positionInShimFile);
|
||||||
|
if (definitionInfos === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.mapShimResultsToTemplates(definitionInfos);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts and definition info result that points to a template typecheck file to a reference to
|
||||||
|
* the corresponding location in the template.
|
||||||
|
*/
|
||||||
|
private mapShimResultsToTemplates(definitionInfos: readonly ts.DefinitionInfo[]):
|
||||||
|
readonly ts.DefinitionInfo[] {
|
||||||
|
const result: ts.DefinitionInfo[] = [];
|
||||||
|
for (const info of definitionInfos) {
|
||||||
|
if (this.ttc.isTrackedTypeCheckFile(absoluteFrom(info.fileName))) {
|
||||||
|
const templateDefinitionInfo =
|
||||||
|
convertToTemplateDocumentSpan(info, this.ttc, this.driver.getProgram());
|
||||||
|
if (templateDefinitionInfo === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.push(templateDefinitionInfo);
|
||||||
|
} else {
|
||||||
|
result.push(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
getTypeDefinitionsAtPosition(fileName: string, position: number):
|
getTypeDefinitionsAtPosition(fileName: string, position: number):
|
||||||
readonly ts.DefinitionInfo[]|undefined {
|
readonly ts.DefinitionInfo[]|undefined {
|
||||||
const templateInfo = getTemplateInfoAtPosition(fileName, position, this.compiler);
|
const templateInfo = getTemplateInfoAtPosition(fileName, position, this.compiler);
|
||||||
|
@ -118,7 +118,7 @@ export class LanguageService {
|
|||||||
if (!isInAngularContext(compiler.getCurrentProgram(), fileName, position)) {
|
if (!isInAngularContext(compiler.getCurrentProgram(), fileName, position)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return new DefinitionBuilder(this.tsLS, compiler)
|
return new DefinitionBuilder(this.tsLS, compiler, this.programDriver)
|
||||||
.getDefinitionAndBoundSpan(fileName, position);
|
.getDefinitionAndBoundSpan(fileName, position);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -129,7 +129,7 @@ export class LanguageService {
|
|||||||
if (!isTemplateContext(compiler.getCurrentProgram(), fileName, position)) {
|
if (!isTemplateContext(compiler.getCurrentProgram(), fileName, position)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return new DefinitionBuilder(this.tsLS, compiler)
|
return new DefinitionBuilder(this.tsLS, compiler, this.programDriver)
|
||||||
.getTypeDefinitionsAtPosition(fileName, position);
|
.getTypeDefinitionsAtPosition(fileName, position);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -176,6 +176,35 @@ describe('definitions', () => {
|
|||||||
assertFileNames(definitions, ['style.scss']);
|
assertFileNames(definitions, ['style.scss']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('gets definition for property of variable declared in template', () => {
|
||||||
|
initMockFileSystem('Native');
|
||||||
|
const files = {
|
||||||
|
'app.html': `
|
||||||
|
<ng-container *ngIf="{prop: myVal} as myVar">
|
||||||
|
{{myVar.prop.name}}
|
||||||
|
</ng-container>
|
||||||
|
`,
|
||||||
|
'app.ts': `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({templateUrl: '/app.html'})
|
||||||
|
export class AppCmp {
|
||||||
|
myVal = {name: 'Andrew'};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
const env = LanguageServiceTestEnv.setup();
|
||||||
|
|
||||||
|
const project = createModuleAndProjectWithDeclarations(env, 'test', files);
|
||||||
|
const template = project.openFile('app.html');
|
||||||
|
project.expectNoSourceDiagnostics();
|
||||||
|
|
||||||
|
template.moveCursorToText('{{myVar.pro¦p.name}}');
|
||||||
|
const {definitions} = getDefinitionsAndAssertBoundSpan(env, template);
|
||||||
|
expect(definitions![0].name).toEqual('"prop"');
|
||||||
|
assertFileNames(Array.from(definitions!), ['app.html']);
|
||||||
|
});
|
||||||
|
|
||||||
function getDefinitionsAndAssertBoundSpan(env: LanguageServiceTestEnv, file: OpenBuffer) {
|
function getDefinitionsAndAssertBoundSpan(env: LanguageServiceTestEnv, file: OpenBuffer) {
|
||||||
env.expectNoSourceDiagnostics();
|
env.expectNoSourceDiagnostics();
|
||||||
const definitionAndBoundSpan = file.getDefinitionAndBoundSpan();
|
const definitionAndBoundSpan = file.getDefinitionAndBoundSpan();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user