refactor(language-service): specifically identify empty argument positions (#41581)
This commit changes `getTemplateAtTarget` to be able to identify when a cursor position is specifically within the argument span of a `MethodCall` or `SafeMethodCall` with no arguments. If the call had arguments, one of the argument expressions would be returned instead, but in a call with no arguments the tightest node _is_ the `MethodCall`. Adding the additional argument context will allow for functionality that relies on tracking argument positions, like `getSignatureHelpItems`. PR Close #41581
This commit is contained in:
parent
e1a2930893
commit
d85e74e05c
|
@ -49,8 +49,8 @@ export interface TemplateTarget {
|
||||||
export type TargetContext = SingleNodeTarget|MultiNodeTarget;
|
export type TargetContext = SingleNodeTarget|MultiNodeTarget;
|
||||||
|
|
||||||
/** Contexts which logically target only a single node in the template AST. */
|
/** Contexts which logically target only a single node in the template AST. */
|
||||||
export type SingleNodeTarget = RawExpression|RawTemplateNode|ElementInBodyContext|
|
export type SingleNodeTarget = RawExpression|MethodCallExpressionInArgContext|RawTemplateNode|
|
||||||
ElementInTagContext|AttributeInKeyContext|AttributeInValueContext;
|
ElementInBodyContext|ElementInTagContext|AttributeInKeyContext|AttributeInValueContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contexts which logically target multiple nodes in the template AST, which cannot be
|
* Contexts which logically target multiple nodes in the template AST, which cannot be
|
||||||
|
@ -65,6 +65,7 @@ export type MultiNodeTarget = TwoWayBindingContext;
|
||||||
*/
|
*/
|
||||||
export enum TargetNodeKind {
|
export enum TargetNodeKind {
|
||||||
RawExpression,
|
RawExpression,
|
||||||
|
MethodCallExpressionInArgContext,
|
||||||
RawTemplateNode,
|
RawTemplateNode,
|
||||||
ElementInTagContext,
|
ElementInTagContext,
|
||||||
ElementInBodyContext,
|
ElementInBodyContext,
|
||||||
|
@ -79,6 +80,21 @@ export enum TargetNodeKind {
|
||||||
export interface RawExpression {
|
export interface RawExpression {
|
||||||
kind: TargetNodeKind.RawExpression;
|
kind: TargetNodeKind.RawExpression;
|
||||||
node: e.AST;
|
node: e.AST;
|
||||||
|
parents: e.AST[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An `e.MethodCall` or `e.SafeMethodCall` expression with the cursor in a position where an
|
||||||
|
* argument could appear.
|
||||||
|
*
|
||||||
|
* This is returned when the only matching node is the method call expression, but the cursor is
|
||||||
|
* within the method call parentheses. For example, in the expression `foo(|)` there is no argument
|
||||||
|
* expression that the cursor could be targeting, but the cursor is in a position where one could
|
||||||
|
* appear.
|
||||||
|
*/
|
||||||
|
export interface MethodCallExpressionInArgContext {
|
||||||
|
kind: TargetNodeKind.MethodCallExpressionInArgContext;
|
||||||
|
node: e.MethodCall|e.SafeMethodCall;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -158,10 +174,21 @@ export function getTargetAtPosition(template: t.Node[], position: number): Templ
|
||||||
|
|
||||||
// Given the candidate node, determine the full targeted context.
|
// Given the candidate node, determine the full targeted context.
|
||||||
let nodeInContext: TargetContext;
|
let nodeInContext: TargetContext;
|
||||||
if (candidate instanceof e.AST) {
|
if ((candidate instanceof e.MethodCall || candidate instanceof e.SafeMethodCall) &&
|
||||||
|
isWithin(position, candidate.argumentSpan)) {
|
||||||
|
nodeInContext = {
|
||||||
|
kind: TargetNodeKind.MethodCallExpressionInArgContext,
|
||||||
|
node: candidate,
|
||||||
|
};
|
||||||
|
} else if (candidate instanceof e.AST) {
|
||||||
|
const parents = path.filter((value: e.AST|t.Node): value is e.AST => value instanceof e.AST);
|
||||||
|
// Remove the current node from the parents list.
|
||||||
|
parents.pop();
|
||||||
|
|
||||||
nodeInContext = {
|
nodeInContext = {
|
||||||
kind: TargetNodeKind.RawExpression,
|
kind: TargetNodeKind.RawExpression,
|
||||||
node: candidate,
|
node: candidate,
|
||||||
|
parents,
|
||||||
};
|
};
|
||||||
} else if (candidate instanceof t.Element) {
|
} else if (candidate instanceof t.Element) {
|
||||||
// Elements have two contexts: the tag context (position is within the element tag) or the
|
// Elements have two contexts: the tag context (position is within the element tag) or the
|
||||||
|
|
|
@ -549,6 +549,15 @@ describe('getTargetAtPosition for expression AST', () => {
|
||||||
expect(node).toBeInstanceOf(e.SafeMethodCall);
|
expect(node).toBeInstanceOf(e.SafeMethodCall);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should identify when in the argument position in a no-arg method call', () => {
|
||||||
|
const {errors, nodes, position} = parse(`{{ title.toString(¦) }}`);
|
||||||
|
expect(errors).toBe(null);
|
||||||
|
const {context} = getTargetAtPosition(nodes, position)!;
|
||||||
|
expect(context.kind).toEqual(TargetNodeKind.MethodCallExpressionInArgContext);
|
||||||
|
const {node} = context as SingleNodeTarget;
|
||||||
|
expect(node).toBeInstanceOf(e.MethodCall);
|
||||||
|
});
|
||||||
|
|
||||||
it('should locate literal primitive in interpolation', () => {
|
it('should locate literal primitive in interpolation', () => {
|
||||||
const {errors, nodes, position} = parse(`{{ title.indexOf('t¦') }}`);
|
const {errors, nodes, position} = parse(`{{ title.indexOf('t¦') }}`);
|
||||||
expect(errors).toBe(null);
|
expect(errors).toBe(null);
|
||||||
|
|
Loading…
Reference in New Issue