Files
2026-06-24 09:48:54 +02:00

600 lines
21 KiB
JavaScript

import { JSXUtils } from "../../jsx-utils.js";
import { ALLOWED_CUSTOM_COMPONENTS, EXCLUDED_FIELDS } from "../../consts.js";
export class JSXAttributeUtils {
types;
constructor(types) {
this.types = types;
}
hasAttribute(path, attributeName) {
return path.node.attributes.some((attr) => this.types.isJSXAttribute(attr) &&
JSXUtils.getAttributeName(attr) === attributeName);
}
getAttributeValue(path, attributeName) {
for (const attr of path.node.attributes) {
if (this.types.isJSXAttribute(attr) &&
JSXUtils.getAttributeName(attr) === attributeName) {
return attr.value;
}
}
return null;
}
getAttributeStringValue(path, attributeName) {
const value = this.getAttributeValue(path, attributeName);
if (value && this.types.isStringLiteral(value)) {
return value.value;
}
return null;
}
addStringAttribute(path, attributeName, value) {
if (!this.hasAttribute(path, attributeName)) {
path.node.attributes.push(this.types.jsxAttribute(this.types.jsxIdentifier(attributeName), this.types.stringLiteral(value)));
}
}
addExpressionAttribute(path, attributeName, expression) {
if (!this.hasAttribute(path, attributeName)) {
path.node.attributes.push(this.types.jsxAttribute(this.types.jsxIdentifier(attributeName), this.types.jsxExpressionContainer(expression)));
}
}
findAncestorWithAttribute(path, attributeName) {
let current = path.parentPath;
while (current) {
if (current.isJSXElement()) {
const opening = current.get("openingElement");
if (this.hasAttribute(opening, attributeName)) {
return opening;
}
}
current = current.parentPath;
}
return null;
}
}
export class PathNavigationUtils {
types;
constructor(types) {
this.types = types;
}
findParentJSXElement(path) {
let current = path.parentPath;
while (current) {
if (current.isJSXElement())
return current;
current = current.parentPath;
}
return null;
}
findEnclosingFunction(path) {
let current = path.parentPath;
while (current) {
if (current.isFunction())
return current;
current = current.parentPath;
}
return null;
}
findReturnStatement(path) {
let current = path.parentPath;
while (current) {
if (current.isReturnStatement())
return current;
current = current.parentPath;
}
return null;
}
isRootReturnElement(path) {
const jsxElement = path.parentPath;
if (!jsxElement?.isJSXElement())
return false;
const parent = jsxElement.parentPath;
if (!parent)
return false;
if (parent.isReturnStatement())
return true;
if (parent.isArrowFunctionExpression())
return true;
if (parent.isParenthesizedExpression()) {
const grandparent = parent.parentPath;
if (grandparent?.isReturnStatement())
return true;
if (grandparent?.isArrowFunctionExpression())
return true;
}
return false;
}
findDOMElementTarget(path) {
if (this.isDOMOrAllowedElement(path))
return path;
let current = path.parentPath;
while (current) {
if (current.isJSXElement()) {
const opening = current.get("openingElement");
if (this.isDOMOrAllowedElement(opening))
return opening;
}
current = current.parentPath;
}
return null;
}
isDOMOrAllowedElement(path) {
const name = JSXUtils.getElementName(path.node);
if (!name)
return false;
if (name.charAt(0) === name.charAt(0).toLowerCase())
return true;
return ALLOWED_CUSTOM_COMPONENTS.includes(name);
}
}
export class ExpressionAnalysisUtils {
types;
constructor(types) {
this.types = types;
}
isIdAccess(node) {
if (!this.types.isMemberExpression(node))
return false;
if (this.types.isIdentifier(node.property)) {
return node.property.name === "_id" || node.property.name === "id";
}
if (this.types.isStringLiteral(node.property)) {
return node.property.value === "_id" || node.property.value === "id";
}
return false;
}
isItemsAccess(node) {
return (this.types.isMemberExpression(node) &&
this.types.isIdentifier(node.property) &&
node.property.name === "items");
}
isLengthAccess(node) {
return (this.types.isMemberExpression(node) &&
this.types.isIdentifier(node.property) &&
node.property.name === "length");
}
extractRootIdentifier(node) {
if (this.types.isIdentifier(node))
return node;
if (this.types.isMemberExpression(node)) {
return this.extractRootIdentifier(node.object);
}
if (this.types.isOptionalMemberExpression(node)) {
return this.extractRootIdentifier(node.object);
}
if (this.types.isCallExpression(node) && this.types.isMemberExpression(node.callee)) {
return this.extractRootIdentifier(node.callee.object);
}
return null;
}
unwrapLogicalExpression(node) {
if (this.types.isLogicalExpression(node)) {
if (node.operator === "&&")
return node.right;
if (node.operator === "||" || node.operator === "??") {
return node.left;
}
}
return node;
}
collectMemberExpressionPath(node) {
const parts = [];
let current = node;
while (this.types.isMemberExpression(current) ||
this.types.isOptionalMemberExpression(current)) {
const prop = current.property;
if (this.types.isIdentifier(prop)) {
parts.unshift(prop.name);
}
else if (this.types.isStringLiteral(prop)) {
parts.unshift(prop.value);
}
current = current.object;
}
if (this.types.isIdentifier(current)) {
parts.unshift(current.name);
}
return parts;
}
createOptionalChainExpression(node) {
const object = this.types.isOptionalMemberExpression(node.object)
? node.object
: this.types.isMemberExpression(node.object)
? this.createOptionalChainExpression(node.object)
: node.object;
const property = node.property;
return this.types.optionalMemberExpression(object, property, node.computed, true);
}
getFieldPathFromExpression(node, rootName) {
const parts = this.collectMemberExpressionPath(node);
if (parts.length < 2)
return null;
if (parts[0] !== rootName)
return null;
const fieldParts = parts.slice(1);
const fieldPath = fieldParts.join(".");
if (EXCLUDED_FIELDS.includes(fieldPath))
return null;
return fieldPath;
}
}
export class BindingUtils {
types;
constructor(types) {
this.types = types;
}
isFunctionParameter(identifierName, path) {
const fn = path.getFunctionParent();
if (!fn)
return false;
const params = fn.get("params");
for (const param of (Array.isArray(params) ? params : [params])) {
if (param.isIdentifier() && param.node.name === identifierName) {
return true;
}
if (param.isObjectPattern()) {
for (const prop of param.get("properties")) {
if (prop.isObjectProperty() &&
this.types.isIdentifier(prop.node.value) &&
prop.node.value.name === identifierName) {
return true;
}
}
}
if (param.isArrayPattern()) {
for (const el of param.get("elements")) {
if (el.isIdentifier() && el.node.name === identifierName) {
return true;
}
}
}
}
return false;
}
isUseStateCall(init) {
if (!init.isCallExpression())
return null;
const callee = init.get("callee");
if (!callee.isIdentifier() || callee.node.name !== "useState")
return null;
const declarator = init.parentPath;
if (!declarator?.isVariableDeclarator())
return null;
const id = declarator.get("id");
if (!id.isArrayPattern())
return null;
const elements = id.get("elements");
const setterEl = elements[1];
const setterName = setterEl && setterEl.isIdentifier() ? setterEl.node.name : null;
return { stateIndex: 0, setterName };
}
isPromiseAllCall(init) {
if (!init.isAwaitExpression()) {
if (init.isCallExpression()) {
const callee = init.get("callee");
return this.isPromiseAllCallee(callee);
}
return false;
}
const argument = init.get("argument");
if (!argument.isCallExpression())
return false;
const callee = argument.get("callee");
return this.isPromiseAllCallee(callee);
}
isPromiseAllCallee(callee) {
if (!callee.isMemberExpression())
return false;
const obj = callee.get("object");
const prop = callee.get("property");
return (obj.isIdentifier() &&
obj.node.name === "Promise" &&
prop.isIdentifier() &&
prop.node.name === "all");
}
extractDestructuredProperties(pattern) {
const properties = [];
for (const prop of pattern.properties) {
if (this.types.isObjectProperty(prop)) {
if (this.types.isIdentifier(prop.key)) {
properties.push(prop.key.name);
}
else if (this.types.isStringLiteral(prop.key)) {
properties.push(prop.key.value);
}
}
}
return properties;
}
findSetterCallInScope(setterName, scope) {
let result = null;
scope.traverse({
CallExpression(callPath) {
if (result)
return;
const callee = callPath.get("callee");
if (callee.isIdentifier() && callee.node.name === setterName) {
result = callPath;
}
},
});
return result;
}
}
export class CallExpressionUtils {
types;
constructor(types) {
this.types = types;
}
isGetAllCall(node) {
const callee = node.callee;
if (!this.types.isMemberExpression(callee))
return null;
const obj = callee.object;
const prop = callee.property;
if (!this.types.isIdentifier(obj) ||
!this.types.isIdentifier(prop) ||
prop.name !== "getAll") {
return null;
}
const args = node.arguments;
if (args.length < 1)
return null;
const firstArg = args[0];
if (!this.types.isStringLiteral(firstArg))
return null;
const collectionName = firstArg.value;
const references = this.extractReferencesFromArg(args[1]);
return { collectionName, references };
}
isGetByIdCall(node) {
const callee = node.callee;
if (!this.types.isMemberExpression(callee))
return null;
const obj = callee.object;
const prop = callee.property;
if (!this.types.isIdentifier(obj) ||
!this.types.isIdentifier(prop) ||
prop.name !== "getById") {
return null;
}
const args = node.arguments;
if (args.length < 1)
return null;
const firstArg = args[0];
if (!this.types.isStringLiteral(firstArg))
return null;
const collectionName = firstArg.value;
const multiRefFields = this.extractMultiRefFromOptions(args[2]);
return { collectionName, multiRefFields };
}
extractReferencesFromArg(arg) {
if (!arg || !this.types.isArrayExpression(arg))
return [];
return arg.elements
.filter((el) => this.types.isStringLiteral(el))
.map((el) => el.value);
}
extractMultiRefFromOptions(arg) {
if (!arg || !this.types.isObjectExpression(arg))
return [];
for (const prop of arg.properties) {
if (this.types.isObjectProperty(prop) &&
this.types.isIdentifier(prop.key) &&
prop.key.name === "multiRef" &&
this.types.isArrayExpression(prop.value)) {
return prop.value.elements
.filter((el) => this.types.isStringLiteral(el))
.map((el) => el.value);
}
}
return [];
}
isArrayMethod(node, methodName) {
const callee = node.callee;
return (this.types.isMemberExpression(callee) &&
this.types.isIdentifier(callee.property) &&
callee.property.name === methodName);
}
isChainedArrayMethod(node) {
const chainMethods = ["filter", "sort", "slice", "concat", "reverse", "flat"];
return chainMethods.some((m) => this.isArrayMethod(node, m));
}
/**
* Detect base44.entities.EntityName.list() or .getAll() patterns.
* Returns the entity name as the collection name.
*/
isBase44EntityListCall(node) {
const callee = node.callee;
if (!this.types.isMemberExpression(callee))
return null;
const method = callee.property;
if (!this.types.isIdentifier(method) ||
(method.name !== "list" && method.name !== "getAll" && method.name !== "filter")) {
return null;
}
const entityAccess = callee.object;
if (!this.types.isMemberExpression(entityAccess))
return null;
const entityName = entityAccess.property;
if (!this.types.isIdentifier(entityName))
return null;
const entitiesAccess = entityAccess.object;
if (!this.types.isMemberExpression(entitiesAccess))
return null;
const entitiesProp = entitiesAccess.property;
if (!this.types.isIdentifier(entitiesProp) ||
entitiesProp.name !== "entities") {
return null;
}
return { collectionName: entityName.name };
}
/**
* Detect base44.entities.EntityName.getById() or .get() patterns.
*/
isBase44EntityGetCall(node) {
const callee = node.callee;
if (!this.types.isMemberExpression(callee))
return null;
const method = callee.property;
if (!this.types.isIdentifier(method) ||
(method.name !== "get" && method.name !== "getById")) {
return null;
}
const entityAccess = callee.object;
if (!this.types.isMemberExpression(entityAccess))
return null;
const entityName = entityAccess.property;
if (!this.types.isIdentifier(entityName))
return null;
const entitiesAccess = entityAccess.object;
if (!this.types.isMemberExpression(entitiesAccess))
return null;
const entitiesProp = entitiesAccess.property;
if (!this.types.isIdentifier(entitiesProp) ||
entitiesProp.name !== "entities") {
return null;
}
return { collectionName: entityName.name };
}
getCallbackArgument(callExpr) {
const firstArg = callExpr.arguments[0];
if (this.types.isArrowFunctionExpression(firstArg) ||
this.types.isFunctionExpression(firstArg)) {
return firstArg;
}
return null;
}
}
export class StaticValueUtils {
types;
constructor(types) {
this.types = types;
}
isPrimitiveLiteral(path) {
return (path.isStringLiteral() ||
path.isNumericLiteral() ||
path.isBooleanLiteral() ||
path.isNullLiteral());
}
isStaticValue(path, visited = new Set()) {
if (this.isPrimitiveLiteral(path))
return true;
if (path.isIdentifier())
return this.isStaticIdentifier(path, visited);
if (path.isObjectExpression())
return this.isStaticObject(path, visited);
if (path.isArrayExpression())
return this.isStaticArrayExpression(path, visited);
return false;
}
isStaticIdentifier(path, visited = new Set()) {
const binding = path.scope.getBinding(path.node.name);
if (!binding)
return false;
if (binding.kind === "module")
return true;
if (binding.kind === "const" && binding.path.isVariableDeclarator()) {
const name = path.node.name;
if (visited.has(name))
return false;
visited.add(name);
const init = binding.path.get("init");
if (init.hasNode()) {
return this.isStaticValue(init, visited);
}
}
return false;
}
isStaticObject(path, visited = new Set()) {
return path.get("properties").every((prop) => {
if (!prop.isObjectProperty())
return false;
return this.isStaticValue(prop.get("value"), visited);
});
}
isStaticArrayExpression(arrayExpression, visited = new Set()) {
return arrayExpression.get("elements").every((element) => {
if (!element.node || element.isSpreadElement())
return true;
return this.isStaticValue(element, visited);
});
}
isDerivedFromStaticData(identifierName, path) {
const binding = path.scope.getBinding(identifierName);
if (!binding)
return false;
if (binding.path.isVariableDeclarator()) {
const init = binding.path.get("init");
if (init.isArrayExpression() || init.isObjectExpression()) {
return this.isStaticValue(init);
}
}
const fnParent = path.getFunctionParent();
if (!fnParent)
return false;
const params = fnParent.get("params");
for (const param of (Array.isArray(params) ? params : [params])) {
if (param.isIdentifier() && param.node.name === identifierName) {
const mapCall = fnParent.parentPath;
if (mapCall?.isCallExpression()) {
const callee = mapCall.get("callee");
if (callee.isMemberExpression() &&
callee.get("property").isIdentifier()) {
const propName = callee.get("property").node.name;
if (propName === "map" || propName === "flatMap") {
const arrayObj = callee.get("object");
if (arrayObj.isIdentifier()) {
return this.isDerivedFromStaticData(arrayObj.node.name, arrayObj);
}
if (arrayObj.isArrayExpression()) {
return this.isStaticArrayExpression(arrayObj);
}
}
}
}
}
}
return false;
}
}
export class TypeCheckUtils {
types;
constructor(types) {
this.types = types;
}
isArrayIsArrayCheck(node) {
if (!this.types.isCallExpression(node))
return false;
const callee = node.callee;
return (this.types.isMemberExpression(callee) &&
this.types.isIdentifier(callee.object) &&
callee.object.name === "Array" &&
this.types.isIdentifier(callee.property) &&
callee.property.name === "isArray");
}
isTypeofObjectCheck(node) {
if (!this.types.isBinaryExpression(node))
return false;
if (node.operator !== "===" && node.operator !== "==")
return false;
const isTypeof = (side) => this.types.isUnaryExpression(side) && side.operator === "typeof";
const isObjectString = (side) => this.types.isStringLiteral(side) && side.value === "object";
return ((isTypeof(node.left) &&
isObjectString(node.right)) ||
(isObjectString(node.left) &&
isTypeof(node.right)));
}
isReferenceTypeCheck(node) {
return this.isArrayIsArrayCheck(node) || this.isTypeofObjectCheck(node);
}
isLengthCheck(node) {
if (this.types.isMemberExpression(node)) {
return (this.types.isIdentifier(node.property) &&
node.property.name === "length");
}
if (this.types.isOptionalMemberExpression(node)) {
return (this.types.isIdentifier(node.property) &&
node.property.name === "length");
}
return false;
}
}
//# sourceMappingURL=shared-utils.js.map