refactor(compiler): add an `argumentSpan` to the method call AST (#41581)
This commit adds a separate span to `MethodCall` and `SafeMethodCall` which tracks the text span between the `(` and `)` tokens of the call. Tools like the Language Service can use this span to more accurately understand a cursor position within a method call expression. PR Close #41581
This commit is contained in:
parent
60d023449b
commit
34545ad2cc
|
@ -705,7 +705,8 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||||
leftMostSafe,
|
leftMostSafe,
|
||||||
new cdAst.MethodCall(
|
new cdAst.MethodCall(
|
||||||
leftMostSafe.span, leftMostSafe.sourceSpan, leftMostSafe.nameSpan,
|
leftMostSafe.span, leftMostSafe.sourceSpan, leftMostSafe.nameSpan,
|
||||||
leftMostSafe.receiver, leftMostSafe.name, leftMostSafe.args));
|
leftMostSafe.receiver, leftMostSafe.name, leftMostSafe.args,
|
||||||
|
leftMostSafe.argumentSpan));
|
||||||
} else {
|
} else {
|
||||||
this._nodeMap.set(
|
this._nodeMap.set(
|
||||||
leftMostSafe,
|
leftMostSafe,
|
||||||
|
|
|
@ -308,7 +308,8 @@ export class NonNullAssert extends AST {
|
||||||
export class MethodCall extends ASTWithName {
|
export class MethodCall extends ASTWithName {
|
||||||
constructor(
|
constructor(
|
||||||
span: ParseSpan, sourceSpan: AbsoluteSourceSpan, nameSpan: AbsoluteSourceSpan,
|
span: ParseSpan, sourceSpan: AbsoluteSourceSpan, nameSpan: AbsoluteSourceSpan,
|
||||||
public receiver: AST, public name: string, public args: any[]) {
|
public receiver: AST, public name: string, public args: any[],
|
||||||
|
public argumentSpan: AbsoluteSourceSpan) {
|
||||||
super(span, sourceSpan, nameSpan);
|
super(span, sourceSpan, nameSpan);
|
||||||
}
|
}
|
||||||
visit(visitor: AstVisitor, context: any = null): any {
|
visit(visitor: AstVisitor, context: any = null): any {
|
||||||
|
@ -319,7 +320,8 @@ export class MethodCall extends ASTWithName {
|
||||||
export class SafeMethodCall extends ASTWithName {
|
export class SafeMethodCall extends ASTWithName {
|
||||||
constructor(
|
constructor(
|
||||||
span: ParseSpan, sourceSpan: AbsoluteSourceSpan, nameSpan: AbsoluteSourceSpan,
|
span: ParseSpan, sourceSpan: AbsoluteSourceSpan, nameSpan: AbsoluteSourceSpan,
|
||||||
public receiver: AST, public name: string, public args: any[]) {
|
public receiver: AST, public name: string, public args: any[],
|
||||||
|
public argumentSpan: AbsoluteSourceSpan) {
|
||||||
super(span, sourceSpan, nameSpan);
|
super(span, sourceSpan, nameSpan);
|
||||||
}
|
}
|
||||||
visit(visitor: AstVisitor, context: any = null): any {
|
visit(visitor: AstVisitor, context: any = null): any {
|
||||||
|
@ -583,13 +585,13 @@ export class AstTransformer implements AstVisitor {
|
||||||
visitMethodCall(ast: MethodCall, context: any): AST {
|
visitMethodCall(ast: MethodCall, context: any): AST {
|
||||||
return new MethodCall(
|
return new MethodCall(
|
||||||
ast.span, ast.sourceSpan, ast.nameSpan, ast.receiver.visit(this), ast.name,
|
ast.span, ast.sourceSpan, ast.nameSpan, ast.receiver.visit(this), ast.name,
|
||||||
this.visitAll(ast.args));
|
this.visitAll(ast.args), ast.argumentSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitSafeMethodCall(ast: SafeMethodCall, context: any): AST {
|
visitSafeMethodCall(ast: SafeMethodCall, context: any): AST {
|
||||||
return new SafeMethodCall(
|
return new SafeMethodCall(
|
||||||
ast.span, ast.sourceSpan, ast.nameSpan, ast.receiver.visit(this), ast.name,
|
ast.span, ast.sourceSpan, ast.nameSpan, ast.receiver.visit(this), ast.name,
|
||||||
this.visitAll(ast.args));
|
this.visitAll(ast.args), ast.argumentSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitFunctionCall(ast: FunctionCall, context: any): AST {
|
visitFunctionCall(ast: FunctionCall, context: any): AST {
|
||||||
|
@ -719,7 +721,8 @@ export class AstMemoryEfficientTransformer implements AstVisitor {
|
||||||
const receiver = ast.receiver.visit(this);
|
const receiver = ast.receiver.visit(this);
|
||||||
const args = this.visitAll(ast.args);
|
const args = this.visitAll(ast.args);
|
||||||
if (receiver !== ast.receiver || args !== ast.args) {
|
if (receiver !== ast.receiver || args !== ast.args) {
|
||||||
return new MethodCall(ast.span, ast.sourceSpan, ast.nameSpan, receiver, ast.name, args);
|
return new MethodCall(
|
||||||
|
ast.span, ast.sourceSpan, ast.nameSpan, receiver, ast.name, args, ast.argumentSpan);
|
||||||
}
|
}
|
||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
@ -728,7 +731,8 @@ export class AstMemoryEfficientTransformer implements AstVisitor {
|
||||||
const receiver = ast.receiver.visit(this);
|
const receiver = ast.receiver.visit(this);
|
||||||
const args = this.visitAll(ast.args);
|
const args = this.visitAll(ast.args);
|
||||||
if (receiver !== ast.receiver || args !== ast.args) {
|
if (receiver !== ast.receiver || args !== ast.args) {
|
||||||
return new SafeMethodCall(ast.span, ast.sourceSpan, ast.nameSpan, receiver, ast.name, args);
|
return new SafeMethodCall(
|
||||||
|
ast.span, ast.sourceSpan, ast.nameSpan, receiver, ast.name, args, ast.argumentSpan);
|
||||||
}
|
}
|
||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
|
|
@ -940,14 +940,19 @@ export class _ParseAST {
|
||||||
const nameSpan = this.sourceSpan(nameStart);
|
const nameSpan = this.sourceSpan(nameStart);
|
||||||
|
|
||||||
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
|
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
|
||||||
|
const argumentStart = this.inputIndex;
|
||||||
this.rparensExpected++;
|
this.rparensExpected++;
|
||||||
const args = this.parseCallArguments();
|
const args = this.parseCallArguments();
|
||||||
|
const argumentSpan =
|
||||||
|
this.span(argumentStart, this.inputIndex).toAbsolute(this.absoluteOffset);
|
||||||
|
|
||||||
this.expectCharacter(chars.$RPAREN);
|
this.expectCharacter(chars.$RPAREN);
|
||||||
this.rparensExpected--;
|
this.rparensExpected--;
|
||||||
const span = this.span(start);
|
const span = this.span(start);
|
||||||
const sourceSpan = this.sourceSpan(start);
|
const sourceSpan = this.sourceSpan(start);
|
||||||
return isSafe ? new SafeMethodCall(span, sourceSpan, nameSpan, receiver, id, args) :
|
return isSafe ?
|
||||||
new MethodCall(span, sourceSpan, nameSpan, receiver, id, args);
|
new SafeMethodCall(span, sourceSpan, nameSpan, receiver, id, args, argumentSpan) :
|
||||||
|
new MethodCall(span, sourceSpan, nameSpan, receiver, id, args, argumentSpan);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (isSafe) {
|
if (isSafe) {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AbsoluteSourceSpan, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, ParserError, TemplateBinding, VariableBinding} from '@angular/compiler/src/expression_parser/ast';
|
import {AbsoluteSourceSpan, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, MethodCall, ParserError, TemplateBinding, VariableBinding} from '@angular/compiler/src/expression_parser/ast';
|
||||||
import {Lexer} from '@angular/compiler/src/expression_parser/lexer';
|
import {Lexer} from '@angular/compiler/src/expression_parser/lexer';
|
||||||
import {IvyParser, Parser, SplitInterpolation} from '@angular/compiler/src/expression_parser/parser';
|
import {IvyParser, Parser, SplitInterpolation} from '@angular/compiler/src/expression_parser/parser';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
|
@ -350,6 +350,11 @@ describe('parser', () => {
|
||||||
expect(unparseWithSpan(ast)).toContain(['foo()', '[nameSpan] foo']);
|
expect(unparseWithSpan(ast)).toContain(['foo()', '[nameSpan] foo']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should record method call argument span', () => {
|
||||||
|
const ast = parseAction('foo(1 + 2)');
|
||||||
|
expect(unparseWithSpan(ast)).toContain(['foo(1 + 2)', '[argumentSpan] 1 + 2']);
|
||||||
|
});
|
||||||
|
|
||||||
it('should record accessed method call span', () => {
|
it('should record accessed method call span', () => {
|
||||||
const ast = parseAction('foo.bar()');
|
const ast = parseAction('foo.bar()');
|
||||||
expect(unparseWithSpan(ast)).toContain(['foo.bar()', 'foo.bar()']);
|
expect(unparseWithSpan(ast)).toContain(['foo.bar()', 'foo.bar()']);
|
||||||
|
|
|
@ -229,6 +229,9 @@ export function unparseWithSpan(
|
||||||
if (ast.hasOwnProperty('nameSpan')) {
|
if (ast.hasOwnProperty('nameSpan')) {
|
||||||
this.recordUnparsed(ast, 'nameSpan', unparsedList);
|
this.recordUnparsed(ast, 'nameSpan', unparsedList);
|
||||||
}
|
}
|
||||||
|
if (ast.hasOwnProperty('argumentSpan')) {
|
||||||
|
this.recordUnparsed(ast, 'argumentSpan', unparsedList);
|
||||||
|
}
|
||||||
ast.visit(this, unparsedList);
|
ast.visit(this, unparsedList);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue