@heycart-ag/admin-eslint-rules
Version:
HeyCart Administration ESLint rules
304 lines (286 loc) • 8.48 kB
JavaScript
export default {
meta: {
type: "suggestion",
docs: {
description:
"Replace HeyCart.State with HeyCart.Store (and destructured State accordingly).",
category: "Best Practices",
recommended: false,
},
fixable: "code",
schema: [],
minHeyCartVersion: "6.7.0.0",
},
create(context) {
const sourceCode = context.getSourceCode();
const stateVariableNames = new Set();
function getBaseIndent(node) {
const text = sourceCode.getText(node);
const match = text.match(/^\s*/);
return match ? match[0] : "";
}
function _isHeyCartState(node) {
return (
node &&
node.type === "MemberExpression" &&
!node.computed &&
node.object &&
node.object.type === "Identifier" &&
node.object.name === "HeyCart" &&
node.property &&
node.property.type === "Identifier" &&
node.property.name === "State"
);
}
return {
// Check for destructuring: const { State } = HeyCart;
VariableDeclarator(node) {
if (
node.init &&
node.init.type === "Identifier" &&
node.init.name === "HeyCart" &&
node.id.type === "ObjectPattern"
) {
node.id.properties.forEach((prop) => {
if (
prop.type === "Property" &&
prop.key &&
prop.key.type === "Identifier" &&
prop.key.name === "State"
) {
// Mark this local variable name (it could be renamed)
const localName = prop.value.name;
stateVariableNames.add(localName);
context.report({
node: prop,
message:
"Do not use destructured 'State', use destructured 'Store' instead.",
fix(fixer) {
// Fix the property key to change "State" to "Store"
// Preserve possible aliasing e.g., { State: MyState }
const fixedKey = fixer.replaceText(prop.key, "Store");
return fixedKey;
},
});
}
});
}
},
VariableDeclaration(node) {
if (
node.kind === "const" &&
node.declarations.length === 1 &&
node.declarations[0].id.type === "ObjectPattern" &&
node.declarations[0].init &&
node.declarations[0].init.type === "CallExpression" &&
node.declarations[0].init.callee.type === "MemberExpression"
) {
const callee = node.declarations[0].init.callee;
if (
callee.object.name === "Component" &&
callee.property.name === "getComponentHelper"
) {
context.report({
node,
message:
"Remove the unused Component.getComponentHelper() import.",
fix(fixer) {
// Remove the entire variable declaration.
return fixer.remove(node);
},
});
}
}
},
// 2. Transform spread elements of mapState.
SpreadElement(node) {
const arg = node.argument;
if (
arg &&
arg.type === "CallExpression" &&
arg.callee &&
(arg.callee.name === "mapState" ||
arg.callee.name === "mapGetters") &&
arg.arguments.length === 2 &&
arg.arguments[0].type === "Literal" &&
(arg.arguments[1].type === "ArrayExpression" ||
arg.arguments[1].type === "ObjectExpression")
) {
const storeName = arg.arguments[0].value;
const baseIndent = getBaseIndent(node);
let computedText = "";
// a. Handle array syntax:
if (arg.arguments[1].type === "ArrayExpression") {
const props = arg.arguments[1].elements
.filter(
(el) =>
el && el.type === "Literal" && typeof el.value === "string",
)
.map((el) => el.value);
computedText = props
.map((prop) => {
return (
"\n" +
baseIndent +
`${prop}() {\n` +
baseIndent +
" " +
`return HeyCart.Store.get('${storeName}').${prop};\n` +
baseIndent +
"}" +
(prop !== props[props.length - 1] ? "," : "")
);
})
.join("");
}
// b. Handle object syntax.
else if (arg.arguments[1].type === "ObjectExpression") {
computedText = arg.arguments[1].properties
.filter((prop) => prop.type === "Property")
.map((prop) => {
// Determine the computed property name.
let propName = "";
if (prop.key.type === "Identifier") {
propName = prop.key.name;
} else if (prop.key.type === "Literal") {
propName = prop.key.value;
} else {
return "";
}
// Case 1: property value is a literal string (mapping).
if (
prop.value.type === "Literal" &&
typeof prop.value.value === "string"
) {
const mappedProp = prop.value.value;
return (
"\n" +
baseIndent +
`${propName}() {\n` +
baseIndent +
" " +
`return HeyCart.Store.get('${storeName}').${mappedProp};\n` +
baseIndent +
"},"
);
}
// Case 2: property value is a function.
if (
prop.value.type === "ArrowFunctionExpression" ||
prop.value.type === "FunctionExpression"
) {
const fn = prop.value;
if (!fn.params || fn.params.length === 0) return "";
if (fn.params[0].type !== "Identifier") return "";
const paramName = fn.params[0].name;
let returnedExpr = null;
if (fn.body.type !== "BlockStatement") {
// Arrow function with implicit return.
returnedExpr = fn.body;
} else {
// Function with block body — find the return statement.
const retStmt = fn.body.body.find(
(n) => n.type === "ReturnStatement" && n.argument,
);
if (retStmt) {
returnedExpr = retStmt.argument;
}
}
if (!returnedExpr) return "";
const returnedText = sourceCode.getText(returnedExpr);
// Ensure returned text starts with the parameter.
const regex = new RegExp(`^${paramName}\\b`);
if (!regex.test(returnedText)) return "";
const newReturnedText = returnedText.replace(
regex,
`HeyCart.Store.get('${storeName}')`,
);
return (
"\n" +
baseIndent +
`${propName}() {\n` +
baseIndent +
" " +
`return ${newReturnedText};\n` +
baseIndent +
"},"
);
}
return "";
})
.join("");
if (computedText.endsWith(",")) {
computedText = computedText.slice(0, -1);
}
}
context.report({
node,
message:
"Replace spread mapState call with explicit computed property definitions.",
fix(fixer) {
return fixer.replaceText(node, computedText);
},
});
}
},
CallExpression(node) {
if (
node.callee &&
node.callee.type === "MemberExpression" &&
node.callee.property &&
node.callee.property.name === "commit"
) {
// Allow two cases:
// a) HeyCart.State.commit(...)
// b) State.commit(...)
const stateMember = node.callee.object;
let isValid = false;
let shortHand = false;
if (
stateMember.type === "MemberExpression" &&
stateMember.object &&
stateMember.object.type === "Identifier" &&
stateMember.object.name === "HeyCart" &&
stateMember.property &&
stateMember.property.name === "State"
) {
isValid = true;
} else if (
stateMember.type === "Identifier" &&
stateMember.name === "State"
) {
isValid = true;
shortHand = true;
}
if (!isValid) return;
if (node.arguments.length < 1) return;
const firstArg = node.arguments[0];
if (
firstArg.type === "Literal" &&
typeof firstArg.value === "string"
) {
const parts = firstArg.value.split("/");
if (parts.length === 2) {
const [storeName, methodName] = parts;
const args = node.arguments.slice(1);
const argsText = args
.map((arg) => sourceCode.getText(arg))
.join(", ");
const newCode = `${shortHand ? "" : "HeyCart."}Store.get('${storeName}').${methodName}(${
argsText ? argsText : ""
})`;
context.report({
node,
message:
"Replace State.commit/HeyCart.State.commit call with HeyCart.Store.get call.",
fix(fixer) {
return fixer.replaceText(node, newCode);
},
});
}
}
}
},
};
},
};