fix(language-service): provide hover for interpolation in attribute value (#35494)

I think the bug is introduced in my PR#34847. For example, 'model="{{title}}"', the attribute value cannot be parsed by 'parseTemplateBindings'.

PR Close #35494
This commit is contained in:
ivanwonder 2020-02-17 18:38:12 +08:00 committed by Miško Hevery
parent 54fd33fb80
commit 049f118c1b
2 changed files with 25 additions and 8 deletions

View File

@ -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;

View File

@ -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, `<div string-model model="{{«title»}}"></div>`);
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({