+12








fcd7c979df
Co-authored-by: Artur <145776561+arturict@users.noreply.github.com> Co-authored-by: arturict <arturict@users.noreply.github.com> Co-authored-by: Thomas <thomas@ajnart.fr> Co-authored-by: Afonso Jorge Ramos <afonsojorgeramos@gmail.com> Co-authored-by: Danura30082 <danura30082+github@gmail.com> Co-authored-by: Kushie <122986913+kushiemoon-dev@users.noreply.github.com> Co-authored-by: ajnart <thomas@ajnart.fr> Co-authored-by: ajnart <thomas@ajnart.dev> Co-authored-by: chenjun <chenjun@foxmail.com> Co-authored-by: Wikibear <wikibear@users.noreply.github.com> Co-authored-by: Bergasha <bergasha@gmail.com> Co-authored-by: leafyelin <43306196+leafyelin@users.noreply.github.com> Co-authored-by: Lucas Araujo <lucas.lra@gmail.com> Co-authored-by: GreenMonito <86085570+GreenMonito@users.noreply.github.com> Co-authored-by: Nerdy-Technician <roffo@nerdy-technician.social> Co-authored-by: Jun <34545896+jcstrong@users.noreply.github.com> Co-authored-by: Edmond Abraham <54119952+gitedmond@users.noreply.github.com> Co-authored-by: thrawnx <tight.bulb9384@fastmail.com> Co-authored-by: Chris <58918656+nordada@users.noreply.github.com> Co-authored-by: yi111 <153097222+Yi-111-a@users.noreply.github.com> Co-authored-by: Yanis <naamaneyanis@gmail.com> Co-authored-by: Wikibear <me@sej.de> Co-authored-by: alessiottavo <93441886+alessiottavo@users.noreply.github.com>
73 lines
2.9 KiB
TypeScript
73 lines
2.9 KiB
TypeScript
export interface AstNode {
|
|
type: string;
|
|
start?: number;
|
|
loc?: { start?: { line?: number; column?: number } };
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export const nodeOf = (value: unknown): AstNode | null =>
|
|
value !== null && typeof value === "object" && typeof (value as { type?: unknown }).type === "string"
|
|
? (value as AstNode)
|
|
: null;
|
|
|
|
export const nodesOf = (value: unknown): AstNode[] =>
|
|
Array.isArray(value) ? value.map(nodeOf).filter((node): node is AstNode => node !== null) : [];
|
|
|
|
export const staticPropertyName = (node: AstNode | null): string | undefined => {
|
|
if (!node) return undefined;
|
|
if (node.type === "Literal" && (typeof node.value === "string" || typeof node.value === "number")) {
|
|
return String(node.value);
|
|
}
|
|
if (node.type === "BinaryExpression" && node.operator === "+") {
|
|
const left = staticPropertyName(nodeOf(node.left));
|
|
const right = staticPropertyName(nodeOf(node.right));
|
|
return left === undefined || right === undefined ? undefined : left + right;
|
|
}
|
|
if (node.type === "TemplateLiteral" && Array.isArray(node.expressions) && node.expressions.length === 0) {
|
|
return (node.quasis as Array<{ value?: { cooked?: unknown } }> | undefined)
|
|
?.map((quasi) => String(quasi.value?.cooked ?? ""))
|
|
.join("");
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const callbackCollectionMethods = new Set(["every", "filter", "find", "findIndex", "map", "reduce", "some", "sort"]);
|
|
|
|
export function containsEscapingCallback(node: AstNode): boolean {
|
|
if (node.type === "ArrowFunctionExpression") return true;
|
|
if (node.type === "CallExpression") {
|
|
const callee = nodeOf(node.callee);
|
|
const arguments_ = nodesOf(node.arguments);
|
|
if (callee?.type === "ArrowFunctionExpression") {
|
|
if (arguments_.length > 0) return true;
|
|
const body = nodeOf(callee.body);
|
|
return body ? containsEscapingCallbackChildren(body) : false;
|
|
}
|
|
if (callee && containsEscapingCallback(callee)) return true;
|
|
const property = callee?.type === "MemberExpression" ? nodeOf(callee.property) : null;
|
|
const method = property?.type === "Identifier" ? String(property.name) : staticPropertyName(property);
|
|
return arguments_.some((argument) => {
|
|
if (argument.type !== "ArrowFunctionExpression" || !method || !callbackCollectionMethods.has(method)) {
|
|
return containsEscapingCallback(argument);
|
|
}
|
|
const body = nodeOf(argument.body);
|
|
return body ? containsEscapingCallback(body) : false;
|
|
});
|
|
}
|
|
return containsEscapingCallbackChildren(node);
|
|
}
|
|
|
|
function containsEscapingCallbackChildren(node: AstNode): boolean {
|
|
return Object.entries(node).some(([key, value]) => {
|
|
if (["loc", "start", "end", "type"].includes(key)) return false;
|
|
if (Array.isArray(value)) {
|
|
return value.some((entry) => {
|
|
const child = nodeOf(entry);
|
|
return child ? containsEscapingCallback(child) : false;
|
|
});
|
|
}
|
|
const child = nodeOf(value);
|
|
return child ? containsEscapingCallback(child) : false;
|
|
});
|
|
}
|