import { isIdentifier } from "../utils.js"; const message = "Do not add redundant helpers that only construct a tagged error. Construct the tagged error directly. Skill: wrdn-effect-typed-errors."; const isErrorFactoryName = (name) => /^make[A-Z].*Error$/.test(name); const isErrorHelperName = (name) => isErrorFactoryName(name) || String(name ?? "").endsWith("Error"); const parameterName = (param) => { if (isIdentifier(param)) return param.name; if (param?.type === "AssignmentPattern" && isIdentifier(param.left)) return param.left.name; if (param?.type === "RestElement" && isIdentifier(param.argument)) return param.argument.name; return undefined;}; const isNewErrorExpression = (node) => node?.type === "NewExpression" && isIdentifier(node.callee) && node.callee.name.endsWith("Error"); const isForwardedValue = (node, parameterNames) => { if (node?.type === "Literal" || node?.type === "StringLiteral") return true; if (node?.type === "Identifier") return parameterNames.has(node.name); return ( node?.type === "MemberExpression" && isIdentifier(node.object) && parameterNames.has(node.object.name) );}; const isObjectWithOnlyForwardedFields = (node, parameterNames) => { if (node?.type !== "ObjectExpression") return true; return (node.properties ?? []).every((property) => { if (property.type === "SpreadElement") return false; return isForwardedValue(property.value, parameterNames); });}; const isRedundantNewErrorExpression = (node, parameterNames) => { if (!isNewErrorExpression(node)) return false; if ((node.arguments ?? []).length === 0) return true; if (node.arguments.length > 1) return false; const argument = node.arguments[0]; if (argument?.type === "Identifier") return parameterNames.has(argument.name); return isObjectWithOnlyForwardedFields(argument, parameterNames);}; const returnsOnlyNewError = (node) => { const parameterNames = new Set((node?.params ?? []).map(parameterName).filter(Boolean)); if (isRedundantNewErrorExpression(node?.body ?? node, parameterNames)) return true; if (node?.type !== "BlockStatement") return false; const statements = node.body ?? []; return ( statements.length === 1 && statements[0]?.type === "ReturnStatement" && isRedundantNewErrorExpression(statements[0].argument, parameterNames) );}; const reportIfRedundantFactory = (context, name, fnNode, reportNode) => { if (isErrorHelperName(name) && returnsOnlyNewError(fnNode)) { context.report({ node: reportNode, message }); }}; export default { meta: { type: "problem", docs: { description: message, }, }, create(context) { return { FunctionDeclaration(node) { reportIfRedundantFactory(context, node.id?.name, node, node); }, VariableDeclarator(node) { if (!isIdentifier(node.id)) return; if ( node.init?.type !== "ArrowFunctionExpression" && node.init?.type !== "FunctionExpression" ) { return; } reportIfRedundantFactory(context, node.id.name, node.init, node); }, }; },};
import { isIdentifier } from "../utils.js";
const message =
"Do not add redundant helpers that only construct a tagged error. Construct the tagged error directly. Skill: wrdn-effect-typed-errors.";
const isErrorFactoryName = (name) => /^make[A-Z].*Error$/.test(name);
const isErrorHelperName = (name) =>
isErrorFactoryName(name) || String(name ?? "").endsWith("Error");
const parameterName = (param) => {
if (isIdentifier(param)) return param.name;
if (param?.type === "AssignmentPattern" && isIdentifier(param.left)) return param.left.name;
if (param?.type === "RestElement" && isIdentifier(param.argument)) return param.argument.name;
return undefined;
};
const isNewErrorExpression = (node) =>
node?.type === "NewExpression" && isIdentifier(node.callee) && node.callee.name.endsWith("Error");
const isForwardedValue = (node, parameterNames) => {
if (node?.type === "Literal" || node?.type === "StringLiteral") return true;
if (node?.type === "Identifier") return parameterNames.has(node.name);
return (
node?.type === "MemberExpression" &&
isIdentifier(node.object) &&
parameterNames.has(node.object.name)
);
};
const isObjectWithOnlyForwardedFields = (node, parameterNames) => {
if (node?.type !== "ObjectExpression") return true;
return (node.properties ?? []).every((property) => {
if (property.type === "SpreadElement") return false;
return isForwardedValue(property.value, parameterNames);
});
};
const isRedundantNewErrorExpression = (node, parameterNames) => {
if (!isNewErrorExpression(node)) return false;
if ((node.arguments ?? []).length === 0) return true;
if (node.arguments.length > 1) return false;
const argument = node.arguments[0];
if (argument?.type === "Identifier") return parameterNames.has(argument.name);
return isObjectWithOnlyForwardedFields(argument, parameterNames);
};
const returnsOnlyNewError = (node) => {
const parameterNames = new Set((node?.params ?? []).map(parameterName).filter(Boolean));
if (isRedundantNewErrorExpression(node?.body ?? node, parameterNames)) return true;
if (node?.type !== "BlockStatement") return false;
const statements = node.body ?? [];
return (
statements.length === 1 &&
statements[0]?.type === "ReturnStatement" &&
isRedundantNewErrorExpression(statements[0].argument, parameterNames)
);
};
const reportIfRedundantFactory = (context, name, fnNode, reportNode) => {
if (isErrorHelperName(name) && returnsOnlyNewError(fnNode)) {
context.report({ node: reportNode, message });
}
};
export default {
meta: {
type: "problem",
docs: {
description: message,
},
},
create(context) {
return {
FunctionDeclaration(node) {
reportIfRedundantFactory(context, node.id?.name, node, node);
},
VariableDeclarator(node) {
if (!isIdentifier(node.id)) return;
if (
node.init?.type !== "ArrowFunctionExpression" &&
node.init?.type !== "FunctionExpression"
) {
return;
}
reportIfRedundantFactory(context, node.id.name, node.init, node);
},
};
},
};