UNPKG

@addon24/eslint-config

Version:

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

225 lines (197 loc) 7.43 kB
/** * @fileoverview TypeORM Column Type Validation Rule * Stellt sicher, dass @Column Decorators explizite Type-Definitionen haben * Verhindert ColumnTypeUndefinedError zur Laufzeit */ /** @type {import('eslint').Rule.RuleModule} */ const typeormColumnTypeValidationRule = { meta: { type: "problem", docs: { description: "Ensure @Column decorators have explicit type definitions to prevent ColumnTypeUndefinedError", category: "TypeORM", recommended: true, }, hasSuggestions: true, schema: [], messages: { missingColumnType: "@Column decorator for '{{propertyName}}' must have explicit type definition. Add type parameter: @Column({ type: '{{suggestedType}}' })", missingColumnTypeGeneric: "@Column decorator for '{{propertyName}}' must have explicit type definition. Add type parameter: @Column({ type: '{{suggestedType}}' }{{additionalOptions}})", useCreateDateColumn: "Consider using @CreateDateColumn() instead of @Column() for '{{propertyName}}'", useUpdateDateColumn: "Consider using @UpdateDateColumn() instead of @Column() for '{{propertyName}}'", }, }, create(context) { const filename = context.getFilename(); const isEntityFile = filename.includes("/entity/") && filename.endsWith(".ts") && !filename.includes("test"); if (!isEntityFile) return {}; /** * Schlägt einen TypeORM-Typ basierend auf TypeScript-Typ vor */ function suggestTypeormType(tsType) { const typeMap = { "string": "varchar", "number": "int", "boolean": "boolean", "Date": "timestamp", "Date | null": "timestamp", "string | null": "varchar", "number | null": "int", "boolean | null": "boolean", }; return typeMap[tsType] || "varchar"; } /** * Prüft ob ein Property ein Datum ist */ function isDateProperty(propertyName, tsType) { const datePatterns = ["createdAt", "updatedAt", "deletedAt", "modifiedAt"]; return datePatterns.some(pattern => propertyName.toLowerCase().includes(pattern.toLowerCase()) ) || tsType === "Date" || tsType === "Date | null"; } /** * Schlägt den passenden Decorator für Datum-Properties vor */ function suggestDateDecorator(propertyName) { if (propertyName.toLowerCase().includes("created")) { return "CreateDateColumn"; } if (propertyName.toLowerCase().includes("updated") || propertyName.toLowerCase().includes("modified")) { return "UpdateDateColumn"; } return null; } /** * Extrahiert TypeScript-Typ aus Property-Definition */ function extractTypeScriptType(node) { if (!node.typeAnnotation?.typeAnnotation) return "unknown"; const sourceCode = context.getSourceCode(); const typeText = sourceCode.getText(node.typeAnnotation.typeAnnotation); // Entferne Whitespace und vereinfache return typeText.replace(/\s+/g, " ").trim(); } /** * Prüft ob @Column Decorator Argumente hat */ function hasColumnArguments(decorator) { return decorator.expression.arguments && decorator.expression.arguments.length > 0; } /** * Prüft ob @Column Decorator einen type-Parameter hat */ function hasTypeParameter(decorator) { if (!hasColumnArguments(decorator)) return false; const args = decorator.expression.arguments[0]; if (args.type !== "ObjectExpression") return false; return args.properties.some(prop => prop.type === "Property" && prop.key.name === "type" ); } return { PropertyDefinition(node) { if (!node.decorators || node.decorators.length === 0) return; const columnDecorator = node.decorators.find(decorator => decorator.expression.callee?.name === "Column" ); if (!columnDecorator) return; const propertyName = node.key.name; const tsType = extractTypeScriptType(node); // Prüfe ob @Column() ohne Argumente verwendet wird if (!hasColumnArguments(columnDecorator)) { const suggestedType = suggestTypeormType(tsType); const dateDecorator = suggestDateDecorator(propertyName); if (dateDecorator) { context.report({ node: columnDecorator, messageId: dateDecorator === "CreateDateColumn" ? "useCreateDateColumn" : "useUpdateDateColumn", data: { propertyName }, suggest: [ { desc: `Replace with @${dateDecorator}()`, fix(fixer) { return fixer.replaceText( columnDecorator.expression, `${dateDecorator}()` ); } } ] }); } else { context.report({ node: columnDecorator, messageId: "missingColumnType", data: { propertyName, suggestedType }, suggest: [ { desc: `Add type parameter to @Column`, fix(fixer) { return fixer.replaceText( columnDecorator.expression, `Column({ type: '${suggestedType}' })` ); } } ] }); } return; } // Prüfe ob @Column({ ... }) ohne type-Parameter verwendet wird if (!hasTypeParameter(columnDecorator)) { const suggestedType = suggestTypeormType(tsType); const args = columnDecorator.expression.arguments[0]; // Erstelle zusätzliche Optionen für den Fix let additionalOptions = ""; if (args.type === "ObjectExpression" && args.properties.length > 0) { const existingProps = args.properties .filter(prop => prop.key.name !== "type") .map(prop => { const sourceCode = context.getSourceCode(); return sourceCode.getText(prop); }) .join(", "); if (existingProps) { additionalOptions = `, ${existingProps}`; } } context.report({ node: columnDecorator, messageId: "missingColumnTypeGeneric", data: { propertyName, suggestedType, additionalOptions }, suggest: [ { desc: `Add type parameter to @Column`, fix(fixer) { const sourceCode = context.getSourceCode(); const decoratorText = sourceCode.getText(columnDecorator.expression); // Füge type-Parameter am Anfang der Objekt-Argumente hinzu const newDecoratorText = decoratorText.replace( /Column\(\s*\{/, `Column({ type: '${suggestedType}'` ); return fixer.replaceText(columnDecorator.expression, newDecoratorText); } } ] }); } } }; }, }; export default { rules: { "typeorm-column-type-validation": typeormColumnTypeValidationRule, }, };