diff --git a/packages/language-service/src/locate_symbol.ts b/packages/language-service/src/locate_symbol.ts index 65591ea880..6780c68bc9 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 {Attribute, BoundDirectivePropertyAst, CssSelector, DirectiveAst, ElementAst, EmbeddedTemplateAst, RecursiveTemplateAstVisitor, SelectorMatcher, StaticSymbol, TemplateAst, TemplateAstPath, templateVisitAll, tokenReference} from '@angular/compiler'; +import {AST, Attribute, BoundDirectivePropertyAst, CssSelector, DirectiveAst, ElementAst, EmbeddedTemplateAst, RecursiveTemplateAstVisitor, SelectorMatcher, StaticSymbol, TemplateAst, TemplateAstPath, templateVisitAll, tokenReference} from '@angular/compiler'; import * as tss from 'typescript/lib/tsserverlibrary'; import {AstResult} from './common'; @@ -63,11 +63,18 @@ function locateSymbol(ast: TemplateAst, path: TemplateAstPath, info: AstResult): let symbol: Symbol|undefined; let span: Span|undefined; let staticSymbol: StaticSymbol|undefined; - const attributeValueSymbol = (): boolean => { + const attributeValueSymbol = (ast: AST): boolean => { const attribute = findAttribute(info, position); if (attribute) { if (inSpan(templatePosition, spanOf(attribute.valueSpan))) { - const result = getSymbolInAttributeValue(info, path, attribute); + let result: {symbol: Symbol, span: Span}|undefined; + if (attribute.name.startsWith('*')) { + result = getSymbolInMicrosyntax(info, path, attribute); + } else { + const dinfo = diagnosticInfoFromTemplateInfo(info); + const scope = getExpressionScope(dinfo, path); + result = getExpressionSymbol(scope, ast, templatePosition, info.template.query); + } if (result) { symbol = result.symbol; span = offsetSpan(result.span, attribute.valueSpan !.start.offset); @@ -108,13 +115,13 @@ function locateSymbol(ast: TemplateAst, path: TemplateAstPath, info: AstResult): }, visitVariable(ast) {}, visitEvent(ast) { - if (!attributeValueSymbol()) { + if (!attributeValueSymbol(ast.handler)) { symbol = findOutputBinding(ast, path, info.template.query); symbol = symbol && new OverrideKindSymbol(symbol, DirectiveKind.EVENT); span = spanOf(ast); } }, - visitElementProperty(ast) { attributeValueSymbol(); }, + visitElementProperty(ast) { attributeValueSymbol(ast.value); }, visitAttr(ast) { const element = path.head; if (!element || !(element instanceof ElementAst)) return; @@ -158,7 +165,7 @@ function locateSymbol(ast: TemplateAst, path: TemplateAstPath, info: AstResult): span = spanOf(ast); }, visitDirectiveProperty(ast) { - if (!attributeValueSymbol()) { + if (!attributeValueSymbol(ast.value)) { const directive = findParentOfBinding(info.templateAst, ast, templatePosition); const attribute = findAttribute(info, position); if (directive && attribute) { @@ -187,8 +194,8 @@ function locateSymbol(ast: TemplateAst, path: TemplateAstPath, info: AstResult): } } -// Get the symbol in attribute value at template position. -function getSymbolInAttributeValue(info: AstResult, path: TemplateAstPath, attribute: Attribute): +// Get the symbol in microsyntax at template position. +function getSymbolInMicrosyntax(info: AstResult, path: TemplateAstPath, attribute: Attribute): {symbol: Symbol, span: Span}|undefined { if (!attribute.valueSpan) { return; diff --git a/packages/language-service/test/hover_spec.ts b/packages/language-service/test/hover_spec.ts index 578f7fcc7e..77bf77abe0 100644 --- a/packages/language-service/test/hover_spec.ts +++ b/packages/language-service/test/hover_spec.ts @@ -39,6 +39,16 @@ describe('hover', () => { expect(toText(displayParts)).toBe('(property) MyComponent.name: string'); }); + it('should be able to find an interpolated value in an attribute', () => { + mockHost.override(TEST_TEMPLATE, `
`); + const marker = mockHost.getReferenceMarkerFor(TEST_TEMPLATE, 'title'); + const quickInfo = ngLS.getQuickInfoAtPosition(TEST_TEMPLATE, marker.start); + expect(quickInfo).toBeTruthy(); + const {textSpan, displayParts} = quickInfo !; + expect(textSpan).toEqual(marker); + expect(toText(displayParts)).toBe('(property) TemplateReference.title: string'); + }); + it('should be able to find a field in a attribute reference', () => { const fileName = mockHost.addCode(` @Component({