From 5260021447d395d75e9b0dbb7e364d2185e300b6 Mon Sep 17 00:00:00 2001 From: ivanwonder Date: Thu, 26 Dec 2019 15:19:38 +0800 Subject: [PATCH] feat(language-service): support hover/definitions for structural directive (#34564) PR Close #34564 --- .../language-service/src/locate_symbol.ts | 18 +++++------- .../language-service/test/definitions_spec.ts | 29 +++++++++++++++++++ packages/language-service/test/hover_spec.ts | 14 +++++++++ 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/packages/language-service/src/locate_symbol.ts b/packages/language-service/src/locate_symbol.ts index dba92ab926..ca2d4a7b0b 100644 --- a/packages/language-service/src/locate_symbol.ts +++ b/packages/language-service/src/locate_symbol.ts @@ -150,16 +150,14 @@ function findAttribute(info: AstResult, position: number): Attribute|undefined { function findInputBinding( info: AstResult, path: TemplateAstPath, binding: BoundDirectivePropertyAst): Symbol|undefined { - const element = path.first(ElementAst); - if (element) { - for (const directive of element.directives) { - const invertedInput = invertMap(directive.directive.inputs); - const fieldName = invertedInput[binding.templateName]; - if (fieldName) { - const classSymbol = info.template.query.getTypeSymbol(directive.directive.type.reference); - if (classSymbol) { - return classSymbol.members().get(fieldName); - } + const directive = path.parentOf(path.tail); + if (directive instanceof DirectiveAst) { + const invertedInput = invertMap(directive.directive.inputs); + const fieldName = invertedInput[binding.templateName]; + if (fieldName) { + const classSymbol = info.template.query.getTypeSymbol(directive.directive.type.reference); + if (classSymbol) { + return classSymbol.members().get(fieldName); } } } diff --git a/packages/language-service/test/definitions_spec.ts b/packages/language-service/test/definitions_spec.ts index 175a8ecfd3..41d5ff7ea6 100644 --- a/packages/language-service/test/definitions_spec.ts +++ b/packages/language-service/test/definitions_spec.ts @@ -262,6 +262,35 @@ describe('definitions', () => { } }); + it('should be able to find a structural directive', () => { + const fileName = mockHost.addCode(` + @Component({ + template: '
' + }) + export class MyComponent { }`); + + // Get the marker for ngIf in the code added above. + const marker = mockHost.getReferenceMarkerFor(fileName, 'ngIf'); + + const result = ngService.getDefinitionAt(fileName, marker.start); + expect(result).toBeDefined(); + const {textSpan, definitions} = result !; + + // Get the marker for bounded text in the code added above + const boundedText = mockHost.getLocationMarkerFor(fileName, 'my'); + expect(textSpan).toEqual(boundedText); + + expect(definitions).toBeDefined(); + expect(definitions !.length).toBe(1); + + const refFileName = '/node_modules/@angular/common/common.d.ts'; + const def = definitions ![0]; + expect(def.fileName).toBe(refFileName); + expect(def.name).toBe('ngIf'); + expect(def.kind).toBe('property'); + // Not asserting the textSpan of definition because it's external file + }); + it('should be able to find a template from a url', () => { const fileName = mockHost.addCode(` @Component({ diff --git a/packages/language-service/test/hover_spec.ts b/packages/language-service/test/hover_spec.ts index d94a94ff63..c08bb865cc 100644 --- a/packages/language-service/test/hover_spec.ts +++ b/packages/language-service/test/hover_spec.ts @@ -148,6 +148,20 @@ describe('hover', () => { expect(toText(displayParts)).toBe('(property) TestComponent.name: string'); }); + it('should be able to find a structural directive', () => { + const fileName = mockHost.addCode(` + @Component({ + template: '
' + }) + export class MyComponent { }`); + const marker = mockHost.getDefinitionMarkerFor(fileName, 'ngIf'); + const quickInfo = ngLS.getHoverAt(fileName, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) NgIf.ngIf: T'); + }); + it('should be able to ignore a reference declaration', () => { const fileName = mockHost.addCode(` @Component({