185 lines
5.8 KiB
TypeScript
185 lines
5.8 KiB
TypeScript
|
|
import type { Node } from "acorn";
|
||
|
|
import type {
|
||
|
|
BinaryExpressionNode,
|
||
|
|
CallExpressionNode,
|
||
|
|
ConditionalExpressionNode,
|
||
|
|
ExpressionStatement,
|
||
|
|
IdentifierNode,
|
||
|
|
MemberExpressionNode,
|
||
|
|
} from "../index";
|
||
|
|
import { isAwaitExpressionNode } from "../index";
|
||
|
|
import { isBinaryExpressionNode } from "../index";
|
||
|
|
import { isConditionalExpressionNode } from "../index";
|
||
|
|
import {
|
||
|
|
isCallExpressionNode,
|
||
|
|
isExpressionStatementNode,
|
||
|
|
isIdentifierNode,
|
||
|
|
isMemberExpressionNode,
|
||
|
|
isThisExpressionNode,
|
||
|
|
} from "../index";
|
||
|
|
import * as escodegen from "escodegen";
|
||
|
|
import { NodeTypes } from "../constants/ast";
|
||
|
|
import type { PeekOverlayExpressionIdentifierOptions } from "./index";
|
||
|
|
|
||
|
|
export const isPositionWithinNode = (node: Node, pos: number) =>
|
||
|
|
pos >= node.start && pos <= node.end;
|
||
|
|
|
||
|
|
export const getExpressionStringAtPos = (
|
||
|
|
node: Node,
|
||
|
|
pos: number,
|
||
|
|
options?: PeekOverlayExpressionIdentifierOptions,
|
||
|
|
replaceThisExpression = true,
|
||
|
|
): string | undefined => {
|
||
|
|
if (!isPositionWithinNode(node, pos)) return;
|
||
|
|
if (isMemberExpressionNode(node)) {
|
||
|
|
return getExpressionAtPosFromMemberExpression(
|
||
|
|
node,
|
||
|
|
pos,
|
||
|
|
options,
|
||
|
|
replaceThisExpression,
|
||
|
|
);
|
||
|
|
} else if (isExpressionStatementNode(node)) {
|
||
|
|
return getExpressionAtPosFromExpressionStatement(node, pos, options);
|
||
|
|
} else if (isCallExpressionNode(node)) {
|
||
|
|
return getExpressionAtPosFromCallExpression(node, pos, options);
|
||
|
|
} else if (isBinaryExpressionNode(node)) {
|
||
|
|
return getExpressionAtPosFromBinaryExpression(node, pos, options);
|
||
|
|
} else if (isAwaitExpressionNode(node)) {
|
||
|
|
return getExpressionStringAtPos(node.argument, pos, options);
|
||
|
|
} else if (isConditionalExpressionNode(node)) {
|
||
|
|
return getExpressionAtPosFromConditionalExpression(node, pos, options);
|
||
|
|
} else if (isIdentifierNode(node)) {
|
||
|
|
return removeSemiColon(escodegen.generate(node));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getExpressionAtPosFromMemberExpression = (
|
||
|
|
node: MemberExpressionNode,
|
||
|
|
pos: number,
|
||
|
|
options?: PeekOverlayExpressionIdentifierOptions,
|
||
|
|
replaceThisExpression = true,
|
||
|
|
): string | undefined => {
|
||
|
|
const objectNode = node.object;
|
||
|
|
if (isLocalVariableNode(node) || isLocalVariableNode(objectNode)) return;
|
||
|
|
if (replaceThisExpression && options?.thisExpressionReplacement) {
|
||
|
|
node = replaceThisinMemberExpression(node, options);
|
||
|
|
}
|
||
|
|
// stop if objectNode is a function call -> needs evaluation
|
||
|
|
if (isCallExpressionNode(objectNode)) return;
|
||
|
|
// position is within the object node
|
||
|
|
if (pos <= objectNode.end) {
|
||
|
|
return getExpressionStringAtPos(objectNode, pos, options, false);
|
||
|
|
}
|
||
|
|
// position is within the property node
|
||
|
|
else {
|
||
|
|
const propertyNode = node.property;
|
||
|
|
if (isMemberExpressionNode(propertyNode)) {
|
||
|
|
return getExpressionAtPosFromMemberExpression(
|
||
|
|
propertyNode,
|
||
|
|
pos,
|
||
|
|
options,
|
||
|
|
false,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
// generate string for the whole path
|
||
|
|
return escodegen.generate(node);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const getExpressionAtPosFromExpressionStatement = (
|
||
|
|
node: ExpressionStatement,
|
||
|
|
pos: number,
|
||
|
|
options?: PeekOverlayExpressionIdentifierOptions,
|
||
|
|
): string | undefined => {
|
||
|
|
if (
|
||
|
|
isThisExpressionNode(node.expression) &&
|
||
|
|
options?.thisExpressionReplacement
|
||
|
|
) {
|
||
|
|
node.expression = thisReplacementNode(node.expression, options);
|
||
|
|
}
|
||
|
|
return getExpressionStringAtPos(node.expression, pos, options);
|
||
|
|
};
|
||
|
|
|
||
|
|
const getExpressionAtPosFromCallExpression = (
|
||
|
|
node: CallExpressionNode,
|
||
|
|
pos: number,
|
||
|
|
options?: PeekOverlayExpressionIdentifierOptions,
|
||
|
|
): string | undefined => {
|
||
|
|
let selectedNode: Node | undefined;
|
||
|
|
// function call -> needs evaluation
|
||
|
|
// if (isPositionWithinNode(node.callee, pos)) {
|
||
|
|
// selectedNode = node.callee;
|
||
|
|
// }
|
||
|
|
if (node.arguments.length > 0) {
|
||
|
|
const argumentNode = node.arguments.find((node) =>
|
||
|
|
isPositionWithinNode(node, pos),
|
||
|
|
);
|
||
|
|
if (argumentNode) {
|
||
|
|
selectedNode = argumentNode;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return selectedNode && getExpressionStringAtPos(selectedNode, pos, options);
|
||
|
|
};
|
||
|
|
|
||
|
|
const getExpressionAtPosFromConditionalExpression = (
|
||
|
|
node: ConditionalExpressionNode,
|
||
|
|
pos: number,
|
||
|
|
options?: PeekOverlayExpressionIdentifierOptions,
|
||
|
|
): string | undefined => {
|
||
|
|
let selectedNode: Node | undefined;
|
||
|
|
if (isPositionWithinNode(node.test, pos)) {
|
||
|
|
selectedNode = node.test;
|
||
|
|
} else if (isPositionWithinNode(node.consequent, pos)) {
|
||
|
|
selectedNode = node.consequent;
|
||
|
|
} else if (isPositionWithinNode(node.alternate, pos)) {
|
||
|
|
selectedNode = node.alternate;
|
||
|
|
}
|
||
|
|
return selectedNode && getExpressionStringAtPos(selectedNode, pos, options);
|
||
|
|
};
|
||
|
|
|
||
|
|
const getExpressionAtPosFromBinaryExpression = (
|
||
|
|
node: BinaryExpressionNode,
|
||
|
|
pos: number,
|
||
|
|
options?: PeekOverlayExpressionIdentifierOptions,
|
||
|
|
): string | undefined => {
|
||
|
|
let selectedNode: Node | undefined;
|
||
|
|
if (isPositionWithinNode(node.left, pos)) {
|
||
|
|
selectedNode = node.left;
|
||
|
|
} else if (isPositionWithinNode(node.right, pos)) {
|
||
|
|
selectedNode = node.right;
|
||
|
|
}
|
||
|
|
return selectedNode && getExpressionStringAtPos(selectedNode, pos, options);
|
||
|
|
};
|
||
|
|
|
||
|
|
export const replaceThisinMemberExpression = (
|
||
|
|
node: MemberExpressionNode,
|
||
|
|
options: PeekOverlayExpressionIdentifierOptions,
|
||
|
|
): MemberExpressionNode => {
|
||
|
|
if (isMemberExpressionNode(node.object)) {
|
||
|
|
node.object = replaceThisinMemberExpression(node.object, options);
|
||
|
|
} else if (isThisExpressionNode(node.object)) {
|
||
|
|
node.object = thisReplacementNode(node.object, options);
|
||
|
|
}
|
||
|
|
return node;
|
||
|
|
};
|
||
|
|
|
||
|
|
// replace "this" node with the provided replacement
|
||
|
|
const thisReplacementNode = (
|
||
|
|
node: Node,
|
||
|
|
options: PeekOverlayExpressionIdentifierOptions,
|
||
|
|
) => {
|
||
|
|
return {
|
||
|
|
...node,
|
||
|
|
type: NodeTypes.Identifier,
|
||
|
|
name: options.thisExpressionReplacement,
|
||
|
|
} as IdentifierNode;
|
||
|
|
};
|
||
|
|
|
||
|
|
const removeSemiColon = (value: string) =>
|
||
|
|
value.slice(-1) === ";" ? value.slice(0, value.length - 1) : value;
|
||
|
|
|
||
|
|
const isLocalVariableNode = (node: Node) =>
|
||
|
|
isMemberExpressionNode(node) &&
|
||
|
|
node.computed &&
|
||
|
|
isIdentifierNode(node.property);
|