@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
180 lines (159 loc) • 5.4 kB
JavaScript
/**
* ESLint-Regel: enforce-app-module-registration
*
* Überprüft, ob alle Controller und Services korrekt im AppModule registriert sind.
* Verhindert Dependency Injection Fehler durch fehlende Registrierungen.
*/
export default {
meta: {
type: "problem",
docs: {
description: "Enforce proper registration of controllers and services in AppModule",
category: "Architecture",
recommended: true,
},
messages: {
missingController: "Controller '{{controllerName}}' is not registered in AppModule controllers array",
missingService: "Service '{{serviceName}}' is not registered in AppModule providers array",
invalidControllerImport: "Controller '{{controllerName}}' is imported but not used in controllers array",
invalidServiceImport: "Service '{{serviceName}}' is imported but not used in providers array",
},
fixable: "code",
},
create(context) {
const filename = context.getFilename();
// Nur für AppModule.ts ausführen
if (!filename.includes('AppModule.ts')) {
return {};
}
return {
Program(node) {
try {
// Analysiere AppModule AST
const appModuleAnalysis = analyzeAppModule(node);
// Prüfe fehlende Controller
appModuleAnalysis.missingControllers.forEach(controller => {
context.report({
node,
messageId: "missingController",
data: { controllerName: controller.name }
});
});
// Prüfe fehlende Services
appModuleAnalysis.missingServices.forEach(service => {
context.report({
node,
messageId: "missingService",
data: { serviceName: service.name }
});
});
// Prüfe ungenutzte Imports
appModuleAnalysis.unusedImports.forEach(importItem => {
context.report({
node: importItem.node,
messageId: importItem.type === 'controller' ? "invalidControllerImport" : "invalidServiceImport",
data: {
controllerName: importItem.name,
serviceName: importItem.name
}
});
});
} catch (error) {
// Fehler beim Analysieren ignorieren
console.warn('AppModule registration check failed:', error.message);
}
}
};
}
};
/**
* Analysiert das AppModule und findet fehlende Registrierungen
*/
function analyzeAppModule(node) {
const missingControllers = [];
const missingServices = [];
const unusedImports = [];
let controllersArray = [];
let providersArray = [];
let imports = [];
// Durchlaufe alle Import-Statements
node.body.forEach(statement => {
if (statement.type === 'ImportDeclaration') {
const importPath = statement.source.value;
statement.specifiers.forEach(spec => {
if (spec.type === 'ImportDefaultSpecifier') {
imports.push({
name: spec.local.name,
path: importPath,
node: statement
});
}
});
}
});
// Finde @Module Decorator - suche nach der korrekten NestJS-Struktur
const moduleDecorator = node.body.find(statement => {
if (statement.type === 'ExportDefaultDeclaration' &&
statement.declaration.type === 'ClassDeclaration') {
// Prüfe ob es Decorators gibt
if (statement.declaration.decorators && statement.declaration.decorators.length > 0) {
return statement.declaration.decorators.some(dec => {
if (dec.expression && dec.expression.type === 'CallExpression') {
return dec.expression.callee && dec.expression.callee.name === 'Module';
}
return false;
});
}
}
return false;
});
if (moduleDecorator) {
const classBody = moduleDecorator.declaration.body;
// Finde controllers und providers Arrays
classBody.body.forEach(member => {
if (member.type === 'ClassProperty' && member.key && member.key.name === 'controllers') {
controllersArray = extractArrayElements(member.value);
}
if (member.type === 'ClassProperty' && member.key && member.key.name === 'providers') {
providersArray = extractArrayElements(member.value);
}
});
}
// Prüfe Controller-Imports
imports.forEach(importItem => {
if (importItem.path.includes('/controller/') || importItem.path.includes('Controller')) {
const isRegistered = controllersArray.includes(importItem.name);
if (!isRegistered) {
missingControllers.push(importItem);
}
}
});
// Prüfe Service-Imports
imports.forEach(importItem => {
if (importItem.path.includes('/service/') || importItem.path.includes('Service')) {
const isRegistered = providersArray.includes(importItem.name);
if (!isRegistered) {
missingServices.push(importItem);
}
}
});
return {
missingControllers,
missingServices,
unusedImports
};
}
/**
* Extrahiert Elemente aus einem Array-Literal
*/
function extractArrayElements(arrayNode) {
if (!arrayNode || arrayNode.type !== 'ArrayExpression') {
return [];
}
return arrayNode.elements.map(element => {
if (element && element.type === 'Identifier') {
return element.name;
}
return null;
}).filter(Boolean);
}