UNPKG

@addon24/eslint-config

Version:

ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types

161 lines (143 loc) 4.94 kB
/** * 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; }