feat(language-service): support multiple symbol definitions (#34782)
In Angular, symbol can have multiple definitions (e.g. a two-way binding). This commit adds support for for multiple definitions for a queried location in a template. PR Close #34782
This commit is contained in:

committed by
Andrew Kushnir

parent
48f8ca5483
commit
1ea04ffc05
@ -14,6 +14,7 @@ import {TypeScriptServiceHost} from '../src/typescript_host';
|
||||
import {MockTypescriptHost} from './test_utils';
|
||||
|
||||
const TEST_TEMPLATE = '/app/test.ng';
|
||||
const PARSING_CASES = '/app/parsing-cases.ts';
|
||||
|
||||
describe('definitions', () => {
|
||||
const mockHost = new MockTypescriptHost(['/app/main.ts']);
|
||||
@ -49,28 +50,29 @@ describe('definitions', () => {
|
||||
});
|
||||
|
||||
it('should be able to find a field in a attribute reference', () => {
|
||||
const fileName = mockHost.addCode(`
|
||||
@Component({
|
||||
template: '<input [(ngModel)]="«name»">'
|
||||
})
|
||||
export class MyComponent {
|
||||
«ᐱnameᐱ: string;»
|
||||
}`);
|
||||
mockHost.override(TEST_TEMPLATE, `<input [(ngModel)]="«title»">`);
|
||||
|
||||
const marker = mockHost.getReferenceMarkerFor(fileName, 'name');
|
||||
const result = ngService.getDefinitionAndBoundSpan(fileName, marker.start);
|
||||
const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title');
|
||||
const result = ngService.getDefinitionAndBoundSpan(TEST_TEMPLATE, marker.start);
|
||||
expect(result).toBeDefined();
|
||||
const {textSpan, definitions} = result !;
|
||||
|
||||
expect(textSpan).toEqual(marker);
|
||||
expect(definitions).toBeDefined();
|
||||
expect(definitions !.length).toBe(1);
|
||||
const def = definitions ![0];
|
||||
|
||||
expect(def.fileName).toBe(fileName);
|
||||
expect(def.name).toBe('name');
|
||||
expect(def.kind).toBe('property');
|
||||
expect(def.textSpan).toEqual(mockHost.getDefinitionMarkerFor(fileName, 'name'));
|
||||
// There are exactly two, indentical definitions here, corresponding to the "name" on the
|
||||
// property and event bindings of the two-way binding. The two-way binding is effectively
|
||||
// syntactic sugar for `[ngModel]="name" (ngModel)="name=$event"`.
|
||||
expect(definitions !.length).toBe(2);
|
||||
for (const def of definitions !) {
|
||||
expect(def.fileName).toBe(PARSING_CASES);
|
||||
expect(def.name).toBe('title');
|
||||
expect(def.kind).toBe('property');
|
||||
|
||||
const fileContent = mockHost.readFile(def.fileName);
|
||||
expect(fileContent !.substring(def.textSpan.start, def.textSpan.start + def.textSpan.length))
|
||||
.toEqual(`title = 'Some title';`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should be able to find a method from a call', () => {
|
||||
@ -304,18 +306,24 @@ describe('definitions', () => {
|
||||
const boundedText = mockHost.getLocationMarkerFor(TEST_TEMPLATE, 'my');
|
||||
expect(textSpan).toEqual(boundedText);
|
||||
|
||||
// There should be exactly 1 definition
|
||||
expect(definitions).toBeDefined();
|
||||
expect(definitions !.length).toBe(1);
|
||||
const def = definitions ![0];
|
||||
expect(definitions !.length).toBe(2);
|
||||
const [def1, def2] = definitions !;
|
||||
|
||||
const refFileName = '/app/parsing-cases.ts';
|
||||
expect(def.fileName).toBe(refFileName);
|
||||
expect(def.name).toBe('model');
|
||||
expect(def.kind).toBe('property');
|
||||
const content = mockHost.readFile(refFileName) !;
|
||||
expect(content.substring(def.textSpan.start, def.textSpan.start + def.textSpan.length))
|
||||
expect(def1.fileName).toBe(refFileName);
|
||||
expect(def1.name).toBe('model');
|
||||
expect(def1.kind).toBe('property');
|
||||
let content = mockHost.readFile(refFileName) !;
|
||||
expect(content.substring(def1.textSpan.start, def1.textSpan.start + def1.textSpan.length))
|
||||
.toEqual(`@Input() model: string = 'model';`);
|
||||
|
||||
expect(def2.fileName).toBe(refFileName);
|
||||
expect(def2.name).toBe('modelChange');
|
||||
expect(def2.kind).toBe('event');
|
||||
content = mockHost.readFile(refFileName) !;
|
||||
expect(content.substring(def2.textSpan.start, def2.textSpan.start + def2.textSpan.length))
|
||||
.toEqual(`@Output() modelChange: EventEmitter<string> = new EventEmitter();`);
|
||||
});
|
||||
|
||||
it('should be able to find a template from a url', () => {
|
||||
|
Reference in New Issue
Block a user