@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
161 lines (143 loc) • 4.94 kB
JavaScript
/**
* ESLint-Regel: Erzwingt Custom Error-Klassen statt generischem Error
*
* Diese Regel verbietet `throw new Error()` und erzwingt die Verwendung
* von spezifischen Error-Klassen für bessere Fehlerbehandlung.
*/
export default {
meta: {
type: "problem",
docs: {
description: "Enforce custom error classes instead of generic Error",
category: "Best Practices",
recommended: true,
},
messages: {
useCustomError: "Use custom error classes instead of generic 'throw new Error()'. Create specific error classes like 'UserNotFoundError', 'ValidationError', etc.",
suggestCustomError: "Consider creating a custom error class like '{{suggestedName}}' for this specific error case.",
},
fixable: null,
schema: [
{
type: "object",
properties: {
allowedPatterns: {
type: "array",
items: { type: "string" },
description: "Patterns that are allowed to use generic Error",
},
exemptFiles: {
type: "array",
items: { type: "string" },
description: "File patterns that are exempt from this rule",
},
},
additionalProperties: false,
},
],
},
create(context) {
const options = context.options[0] || {};
const allowedPatterns = options.allowedPatterns || [];
const exemptFiles = options.exemptFiles || ["**/*.test.ts", "**/*.spec.ts", "**/__tests__/**"];
const filename = context.getFilename();
// Prüfe ob Datei von der Regel ausgenommen ist
const isExempt = exemptFiles.some(pattern => {
if (pattern.includes("**")) {
const regex = new RegExp(pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"));
return regex.test(filename);
}
return filename.includes(pattern);
});
if (isExempt) {
return {};
}
return {
ThrowStatement(node) {
// Prüfe ob es ein 'throw new Error()' ist
if (
node.argument &&
node.argument.type === "NewExpression" &&
node.argument.callee &&
node.argument.callee.name === "Error"
) {
// Prüfe ob es ein erlaubtes Pattern ist
const errorMessage = node.argument.arguments[0];
if (errorMessage && errorMessage.type === "Literal") {
const message = errorMessage.value;
const isAllowed = allowedPatterns.some(pattern =>
message && message.includes(pattern)
);
if (!isAllowed) {
// Generiere vorgeschlagenen Error-Klassennamen basierend auf Kontext
const suggestedName = generateErrorClassName(node, context);
context.report({
node,
messageId: "useCustomError",
data: {
suggestedName,
},
});
}
} else {
context.report({
node,
messageId: "useCustomError",
});
}
}
},
};
},
};
/**
* Generiert einen vorgeschlagenen Error-Klassennamen basierend auf dem Kontext
*/
function generateErrorClassName(node, context) {
const filename = context.getFilename();
const functionName = getFunctionName(node, context);
// Extrahiere Entity/Service-Namen aus Dateiname
const fileNameMatch = filename.match(/\/([^/]+)Service\.ts$/);
const entityName = fileNameMatch ? fileNameMatch[1] : "Unknown";
// Generiere basierend auf Error-Message
const errorMessage = node.argument.arguments[0];
if (errorMessage && errorMessage.type === "Literal") {
const message = errorMessage.value.toLowerCase();
if (message.includes("not found")) {
return `${entityName}NotFoundError`;
}
if (message.includes("invalid") || message.includes("validation")) {
return `Invalid${entityName}Error`;
}
if (message.includes("unauthorized") || message.includes("permission")) {
return `Unauthorized${entityName}Error`;
}
if (message.includes("already exists") || message.includes("duplicate")) {
return `${entityName}AlreadyExistsError`;
}
}
// Fallback basierend auf Funktionsname
if (functionName) {
return `${functionName}Error`;
}
return `${entityName}Error`;
}
/**
* Findet den Funktionsnamen, in dem der Error geworfen wird
*/
function getFunctionName(node, context) {
let current = node.parent;
while (current) {
if (current.type === "FunctionDeclaration" && current.id) {
return current.id.name;
}
if (current.type === "MethodDefinition" && current.key) {
return current.key.name;
}
if (current.type === "ArrowFunctionExpression" && current.parent && current.parent.type === "VariableDeclarator" && current.parent.id) {
return current.parent.id.name;
}
current = current.parent;
}
return null;
}