fix(language-service): hybrid visitor returns parent node of BoundAttribute (#38995)
For the following example, the cursor is between `keySpan` and `valueSpan` of the `BoundAttribute`. ```html <test-cmp [foo]¦="bar"></test-cmp> ``` Our hybrid visitor will return `Element`in this case, which is the parent node of the `BoundAttribute`. This is because we only look at the `keySpan` and `valueSpan`, and not the source span. The last element in the AST path is `Element`, so it gets returned. In this PR, I propose fixing this by adding a sentinel value `undefined` to the AST path to signal that we've found a source span but the cursor is neither in the key span nor the value span. PR Close #38995
This commit is contained in:
parent
0dd859757a
commit
323be39297
|
@ -19,7 +19,19 @@ import * as t from '@angular/compiler/src/render3/r3_ast'; // t for temp
|
|||
export function findNodeAtPosition(ast: t.Node[], position: number): t.Node|e.AST|undefined {
|
||||
const visitor = new R3Visitor(position);
|
||||
visitor.visitAll(ast);
|
||||
return visitor.path[visitor.path.length - 1];
|
||||
const candidate = visitor.path[visitor.path.length - 1];
|
||||
if (!candidate) {
|
||||
return;
|
||||
}
|
||||
if (isTemplateNodeWithKeyAndValue(candidate)) {
|
||||
const {keySpan, valueSpan} = candidate;
|
||||
// If cursor is within source span but not within key span or value span,
|
||||
// do not return the node.
|
||||
if (!isWithin(position, keySpan) && (valueSpan && !isWithin(position, valueSpan))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
class R3Visitor implements t.Visitor {
|
||||
|
@ -31,11 +43,6 @@ class R3Visitor implements t.Visitor {
|
|||
constructor(private readonly position: number) {}
|
||||
|
||||
visit(node: t.Node) {
|
||||
if (node instanceof t.BoundAttribute) {
|
||||
node.visit(this);
|
||||
return;
|
||||
}
|
||||
|
||||
const {start, end} = getSpanIncludingEndTag(node);
|
||||
if (isWithin(this.position, {start, end})) {
|
||||
const length = end - start;
|
||||
|
@ -100,13 +107,8 @@ class R3Visitor implements t.Visitor {
|
|||
}
|
||||
|
||||
visitBoundAttribute(attribute: t.BoundAttribute) {
|
||||
if (isWithin(this.position, attribute.keySpan)) {
|
||||
this.path.push(attribute);
|
||||
} else if (attribute.valueSpan && isWithin(this.position, attribute.valueSpan)) {
|
||||
this.path.push(attribute);
|
||||
const visitor = new ExpressionVisitor(this.position);
|
||||
visitor.visit(attribute.value, this.path);
|
||||
}
|
||||
const visitor = new ExpressionVisitor(this.position);
|
||||
visitor.visit(attribute.value, this.path);
|
||||
}
|
||||
|
||||
visitBoundEvent(attribute: t.BoundEvent) {
|
||||
|
@ -166,6 +168,15 @@ export function isTemplateNode(node: t.Node|e.AST): node is t.Node {
|
|||
return node.sourceSpan instanceof ParseSourceSpan;
|
||||
}
|
||||
|
||||
interface NodeWithKeyAndValue extends t.Node {
|
||||
keySpan: ParseSourceSpan;
|
||||
valueSpan?: ParseSourceSpan;
|
||||
}
|
||||
|
||||
export function isTemplateNodeWithKeyAndValue(node: t.Node|e.AST): node is NodeWithKeyAndValue {
|
||||
return isTemplateNode(node) && node.hasOwnProperty('keySpan');
|
||||
}
|
||||
|
||||
export function isExpressionNode(node: t.Node|e.AST): node is e.AST {
|
||||
return node instanceof e.AST;
|
||||
}
|
||||
|
|
|
@ -97,6 +97,13 @@ describe('findNodeAtPosition for template AST', () => {
|
|||
expect(node).toBeInstanceOf(e.PropertyRead);
|
||||
});
|
||||
|
||||
it('should not locate bound attribute if cursor is between key and value', () => {
|
||||
const {errors, nodes, position} = parse(`<test-cmp [foo]¦="bar"></test-cmp>`);
|
||||
expect(errors).toBeNull();
|
||||
const node = findNodeAtPosition(nodes, position);
|
||||
expect(node).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should locate bound event key', () => {
|
||||
const {errors, nodes, position} = parse(`<test-cmp (fo¦o)="bar()"></test-cmp>`);
|
||||
expect(errors).toBe(null);
|
||||
|
|
Loading…
Reference in New Issue