Files
homarr/packages/custom-widgets/src/jsx/analyzer-ast.ts
T

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;
});
}