fix(language-service): correctly parse expressions in an attribute (#34517)
Currently, the language service provides completions in a template node attribute by first checking if the attribute contains template bindings to provide completions for, and then providing completions for the expression in the attribute. In the latter case, the expression AST was being constructed "synthetically" inside the language service, in particular declaring the expression to be a `PropertyRead` with an implicit receiver. Unfortunately, this AST can be incorrect if the expression is actually a property read on a component property receiver (e.g. when reading `key` in the expression `obj.key`, `obj` is the receiver). The fix is pretty simple - rather than a synthetic construction of the AST, ask the expression parser to parse the expression in the attribute. Fixes https://github.com/angular/vscode-ng-language-service/issues/523 PR Close #34517
This commit is contained in:

committed by
Alex Rickabaugh

parent
c079f38cbb
commit
ee46b9b44f
@ -419,11 +419,9 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
||||
}
|
||||
|
||||
visitAttr(ast: AttrAst) {
|
||||
// The attribute value is a template expression but the expression AST
|
||||
// was not produced when the TemplateAst was produced so do that here.
|
||||
// First, verify the attribute consists of some binding we can give completions for.
|
||||
const {templateBindings} = this.info.expressionParser.parseTemplateBindings(
|
||||
ast.name, ast.value, ast.sourceSpan.toString(), ast.sourceSpan.start.offset);
|
||||
|
||||
// Find where the cursor is relative to the start of the attribute value.
|
||||
const valueRelativePosition = this.position - ast.sourceSpan.start.offset;
|
||||
// Find the template binding that contains the position
|
||||
@ -436,12 +434,11 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
||||
if (ast.name.startsWith('*')) {
|
||||
this.microSyntaxInAttributeValue(ast, binding);
|
||||
} else {
|
||||
// If the position is in the expression or after the key or there is no key,
|
||||
// return the expression completions
|
||||
const span = new ParseSpan(0, ast.value.length);
|
||||
const offset = ast.sourceSpan.start.offset;
|
||||
const receiver = new ImplicitReceiver(span, span.toAbsolute(offset));
|
||||
const expressionAst = new PropertyRead(span, span.toAbsolute(offset), receiver, '');
|
||||
// If the position is in the expression or after the key or there is no key, return the
|
||||
// expression completions.
|
||||
// The expression must be reparsed to get a valid AST rather than only template bindings.
|
||||
const expressionAst = this.info.expressionParser.parseBinding(
|
||||
ast.value, ast.sourceSpan.toString(), ast.sourceSpan.start.offset);
|
||||
this.addAttributeValuesToCompletions(expressionAst);
|
||||
}
|
||||
}
|
||||
@ -542,10 +539,8 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
||||
const KW_OF = ' of ';
|
||||
const ofLocation = attr.value.indexOf(KW_OF);
|
||||
if (ofLocation > 0 && valueRelativePosition >= ofLocation + KW_OF.length) {
|
||||
const span = new ParseSpan(0, attr.value.length);
|
||||
const offset = attr.sourceSpan.start.offset;
|
||||
const receiver = new ImplicitReceiver(span, span.toAbsolute(offset));
|
||||
const expressionAst = new PropertyRead(span, span.toAbsolute(offset), receiver, '');
|
||||
const expressionAst = this.info.expressionParser.parseBinding(
|
||||
attr.value, attr.sourceSpan.toString(), attr.sourceSpan.start.offset);
|
||||
this.addAttributeValuesToCompletions(expressionAst);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user