diff --git a/packages/language-service/src/locate_symbol.ts b/packages/language-service/src/locate_symbol.ts index 8a2fa325cc..e31093de67 100644 --- a/packages/language-service/src/locate_symbol.ts +++ b/packages/language-service/src/locate_symbol.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AST, Attribute, BoundDirectivePropertyAst, BoundEventAst, CompileTypeSummary, ElementAst, TemplateAstPath, findNode, tokenReference} from '@angular/compiler'; +import {AST, Attribute, BoundDirectivePropertyAst, BoundEventAst, CompileTypeSummary, CssSelector, DirectiveAst, ElementAst, SelectorMatcher, TemplateAstPath, findNode, tokenReference} from '@angular/compiler'; import {getExpressionScope} from '@angular/compiler-cli/src/language_services'; import {AstResult} from './common'; @@ -88,7 +88,28 @@ export function locateSymbol(info: AstResult, position: number): SymbolInfo|unde } }, visitElementProperty(ast) { attributeValueSymbol(ast.value); }, - visitAttr(ast) {}, + visitAttr(ast) { + const element = path.head; + if (!element || !(element instanceof ElementAst)) return; + // Create a mapping of all directives applied to the element from their selectors. + const matcher = new SelectorMatcher(); + for (const dir of element.directives) { + if (!dir.directive.selector) continue; + matcher.addSelectables(CssSelector.parse(dir.directive.selector), dir); + } + + // See if this attribute matches the selector of any directive on the element. + // TODO(ayazhafiz): Consider caching selector matches (at the expense of potentially + // very high memory usage). + const attributeSelector = `[${ast.name}=${ast.value}]`; + const parsedAttribute = CssSelector.parse(attributeSelector); + if (!parsedAttribute.length) return; + matcher.match(parsedAttribute[0], (_, directive) => { + symbol = info.template.query.getTypeSymbol(directive.directive.type.reference); + symbol = symbol && new OverrideKindSymbol(symbol, DirectiveKind.DIRECTIVE); + span = spanOf(ast); + }); + }, visitBoundText(ast) { const expressionPosition = templatePosition - ast.sourceSpan.start.offset; if (inSpan(expressionPosition, ast.value.span)) { diff --git a/packages/language-service/test/hover_spec.ts b/packages/language-service/test/hover_spec.ts index 07ab3193e1..56bc477e23 100644 --- a/packages/language-service/test/hover_spec.ts +++ b/packages/language-service/test/hover_spec.ts @@ -106,6 +106,20 @@ describe('hover', () => { expect(toText(displayParts)).toBe('(component) AppModule.TestComponent: class'); }); + it('should be able to find a reference to a directive', () => { + const fileName = mockHost.addCode(` + @Component({ + template: '' + }) + export class MyComponent { }`); + const marker = mockHost.getReferenceMarkerFor(fileName, 'string-model'); + const quickInfo = ngLS.getHoverAt(fileName, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(directive) StringModel'); + }); + it('should be able to find an event provider', () => { const fileName = mockHost.addCode(` @Component({