@echoes-of-order/eslint-config 1.121.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/CHANGELOG.md +1093 -0
  2. package/configs/.gitkeep +1 -0
  3. package/configs/admin.js +203 -0
  4. package/configs/api-client.js +46 -0
  5. package/configs/backend.js +895 -0
  6. package/configs/domains.js +123 -0
  7. package/configs/frontend.js +30 -0
  8. package/configs/image-server.js +26 -0
  9. package/configs/ionos-proxy.js +372 -0
  10. package/configs/nestjs.js +156 -0
  11. package/configs/node.js +92 -0
  12. package/configs/react.js +111 -0
  13. package/configs/wiki.js +42 -0
  14. package/index.js +39 -0
  15. package/package.json +85 -0
  16. package/rules/.gitkeep +1 -0
  17. package/rules/__tests__/analyze-relation-usage.test.js.disabled +300 -0
  18. package/rules/__tests__/complexity.test.js.disabled +300 -0
  19. package/rules/__tests__/enforce-dto-factory-in-services.integration.test.js +226 -0
  20. package/rules/__tests__/enforce-dto-factory-in-services.test.js +177 -0
  21. package/rules/__tests__/enforce-entity-dto-create-no-id.integration.test.js +18 -0
  22. package/rules/__tests__/enforce-function-argument-count.test.js.disabled +300 -0
  23. package/rules/__tests__/enforce-repository-token-handling.test.js +58 -0
  24. package/rules/__tests__/english-only-code-strings.test.js.disabled +300 -0
  25. package/rules/__tests__/eslint-rules.integration.test.ts +350 -0
  26. package/rules/__tests__/integration-test-controller-response-dto.js +261 -0
  27. package/rules/__tests__/integration-test-dto-factory-in-services.js +260 -0
  28. package/rules/__tests__/integration-test-no-entity-type-casting.js +161 -0
  29. package/rules/__tests__/integration-test-typeorm-naming-conventions.js +501 -0
  30. package/rules/__tests__/test-config.js +33 -0
  31. package/rules/admin-controller-security.js +180 -0
  32. package/rules/analyze-relation-usage.js +687 -0
  33. package/rules/api-response-dto.js +174 -0
  34. package/rules/auth-guard-required.js +142 -0
  35. package/rules/backend-specific.js +36 -0
  36. package/rules/best-practices.js +421 -0
  37. package/rules/complexity.js +20 -0
  38. package/rules/controller-architecture.js +340 -0
  39. package/rules/controller-naming-conventions.js +190 -0
  40. package/rules/controller-readonly-restriction.js +148 -0
  41. package/rules/controller-swagger-complete.js +312 -0
  42. package/rules/controller-swagger-docs.js +119 -0
  43. package/rules/controller-swagger-english.js +320 -0
  44. package/rules/coordinate-naming.js +132 -0
  45. package/rules/custom-mui-button.js +135 -0
  46. package/rules/dead-code-detection-backend.js +50 -0
  47. package/rules/dead-code-detection-frontend.js +48 -0
  48. package/rules/dead-code-detection.js +71 -0
  49. package/rules/debug-controller-response-dto.js +79 -0
  50. package/rules/deprecate.js +8 -0
  51. package/rules/dto-annotation-property-consistency.js +111 -0
  52. package/rules/dto-entity-mapping-completeness.js +688 -0
  53. package/rules/dto-entity-swagger-separation.js +265 -0
  54. package/rules/dto-entity-type-consistency.js +352 -0
  55. package/rules/dto-entity-type-matching.js +519 -0
  56. package/rules/dto-naming-convention.js +98 -0
  57. package/rules/dto-visibility-modifiers.js +159 -0
  58. package/rules/enforce-api-versioning.js +122 -0
  59. package/rules/enforce-app-module-registration.js +179 -0
  60. package/rules/enforce-basecontroller.js +152 -0
  61. package/rules/enforce-body-request-dto.js +141 -0
  62. package/rules/enforce-controller-response-dto.js +349 -0
  63. package/rules/enforce-custom-error-classes.js +242 -0
  64. package/rules/enforce-database-transaction-safety.js +179 -0
  65. package/rules/enforce-dto-constructor.js +95 -0
  66. package/rules/enforce-dto-create-parameter-types.js +170 -0
  67. package/rules/enforce-dto-create-pattern.js +274 -0
  68. package/rules/enforce-dto-entity-creation.js +164 -0
  69. package/rules/enforce-dto-factory-in-services.js +188 -0
  70. package/rules/enforce-dto-from-entity-method.js +47 -0
  71. package/rules/enforce-dto-from-entity.js +314 -0
  72. package/rules/enforce-dto-naming-conventions.js +212 -0
  73. package/rules/enforce-dto-naming.js +176 -0
  74. package/rules/enforce-dto-usage-simple.js +114 -0
  75. package/rules/enforce-dto-usage.js +407 -0
  76. package/rules/enforce-eager-translation-loading.js +178 -0
  77. package/rules/enforce-entity-creation-pattern.js +137 -0
  78. package/rules/enforce-entity-dto-convert-method.js +157 -0
  79. package/rules/enforce-entity-dto-create-no-id.js +117 -0
  80. package/rules/enforce-entity-dto-extends-base.js +141 -0
  81. package/rules/enforce-entity-dto-from-request-dto-structure.js +113 -0
  82. package/rules/enforce-entity-dto-fromentity-complex.js +69 -0
  83. package/rules/enforce-entity-dto-fromentity-simple.js +69 -0
  84. package/rules/enforce-entity-dto-fromrequestdto-structure.js +262 -0
  85. package/rules/enforce-entity-dto-methods-restriction.js +159 -0
  86. package/rules/enforce-entity-dto-no-request-dto.js +102 -0
  87. package/rules/enforce-entity-dto-optional-auto-fields.js +101 -0
  88. package/rules/enforce-entity-dto-required-methods.js +248 -0
  89. package/rules/enforce-entity-factory-pattern.js +180 -0
  90. package/rules/enforce-entity-instantiation-in-toentity.js +125 -0
  91. package/rules/enforce-enum-for-playable-entities.js +95 -0
  92. package/rules/enforce-error-handling.js +257 -0
  93. package/rules/enforce-explicit-dto-types.js +118 -0
  94. package/rules/enforce-from-request-dto-usage.js +62 -0
  95. package/rules/enforce-generic-entity-dto.js +71 -0
  96. package/rules/enforce-inject-decorator.js +133 -0
  97. package/rules/enforce-lazy-type-loading.js +170 -0
  98. package/rules/enforce-module-existence.js +157 -0
  99. package/rules/enforce-nonentity-dto-create.js +107 -0
  100. package/rules/enforce-playable-entity-naming.js +108 -0
  101. package/rules/enforce-repository-token-handling.js +92 -0
  102. package/rules/enforce-request-dto-no-entity-dto.js +201 -0
  103. package/rules/enforce-request-dto-required-fields.js +217 -0
  104. package/rules/enforce-result-pattern.js +45 -0
  105. package/rules/enforce-service-relation-loading.js +116 -0
  106. package/rules/enforce-test-coverage.js +96 -0
  107. package/rules/enforce-toentity-conditional-assignment.js +132 -0
  108. package/rules/enforce-translations-required.js +203 -0
  109. package/rules/enforce-typeorm-naming-conventions.js +366 -0
  110. package/rules/enforce-vite-health-metrics.js +240 -0
  111. package/rules/entity-required-properties.js +321 -0
  112. package/rules/entity-to-dto-test.js +73 -0
  113. package/rules/enum-database-validation.js +149 -0
  114. package/rules/errors.js +190 -0
  115. package/rules/es6.js +204 -0
  116. package/rules/eslint-plugin-no-comments.js +44 -0
  117. package/rules/filename-class-name-match.js +62 -0
  118. package/rules/forbid-fromentity-outside-entity-folder.js +237 -0
  119. package/rules/function-params-newline.js +111 -0
  120. package/rules/imports.js +264 -0
  121. package/rules/jest.js +13 -0
  122. package/rules/jsx.js +16 -0
  123. package/rules/max-classes-per-file.js +49 -0
  124. package/rules/multiline-formatting.js +146 -0
  125. package/rules/no-blank-lines-between-decorators-and-properties.js +95 -0
  126. package/rules/no-comments.js +62 -0
  127. package/rules/no-dto-constructors.js +126 -0
  128. package/rules/no-dto-default-values.js +220 -0
  129. package/rules/no-dto-duplicates.js +127 -0
  130. package/rules/no-dto-in-entity.js +99 -0
  131. package/rules/no-dynamic-import-in-types.js +71 -0
  132. package/rules/no-dynamic-imports-in-controllers.js +95 -0
  133. package/rules/no-entity-imports-in-controllers.js +101 -0
  134. package/rules/no-entity-in-swagger-docs.js +139 -0
  135. package/rules/no-entity-type-casting.js +104 -0
  136. package/rules/no-fetch.js +77 -0
  137. package/rules/no-import-meta-env.js +151 -0
  138. package/rules/no-inline-styles.js +5 -0
  139. package/rules/no-magic-values.js +85 -0
  140. package/rules/no-partial-type.js +168 -0
  141. package/rules/no-relative-imports.js +31 -0
  142. package/rules/no-tsyringe.js +181 -0
  143. package/rules/no-type-assertion.js +175 -0
  144. package/rules/no-undefined-entity-properties.js +121 -0
  145. package/rules/node.js +44 -0
  146. package/rules/perfectionist.js +50 -0
  147. package/rules/performance-minimal.js +155 -0
  148. package/rules/performance.js +44 -0
  149. package/rules/pino-logger-format.js +200 -0
  150. package/rules/prefer-dto-classes.js +112 -0
  151. package/rules/prefer-dto-create-method.js +225 -0
  152. package/rules/promises.js +17 -0
  153. package/rules/react-hooks.js +15 -0
  154. package/rules/react.js +28 -0
  155. package/rules/regexp.js +70 -0
  156. package/rules/require-dto-response.js +81 -0
  157. package/rules/require-valid-relations.js +388 -0
  158. package/rules/result-pattern.js +162 -0
  159. package/rules/security.js +37 -0
  160. package/rules/service-architecture.js +148 -0
  161. package/rules/sonarjs.js +26 -0
  162. package/rules/strict.js +7 -0
  163. package/rules/style.js +611 -0
  164. package/rules/stylistic.js +93 -0
  165. package/rules/typeorm-column-type-validation.js +224 -0
  166. package/rules/typescript-advanced.js +113 -0
  167. package/rules/typescript-core.js +111 -0
  168. package/rules/typescript.js +146 -0
  169. package/rules/unicorn.js +168 -0
  170. package/rules/variables.js +51 -0
  171. package/rules/websocket-architecture.js +115 -0
@@ -0,0 +1,159 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Enforce no explicit visibility modifiers or readonly in DTO classes",
6
+ category: "Best Practices",
7
+ recommended: true,
8
+ },
9
+ fixable: "code",
10
+ schema: [],
11
+ messages: {
12
+ removePublicModifier: "Remove unnecessary 'public' modifier from DTO member '{{memberName}}'",
13
+ removePrivateModifier: "Remove 'private' modifier from DTO member '{{memberName}}' - DTO members should be accessible",
14
+ removeProtectedModifier: "Remove 'protected' modifier from DTO member '{{memberName}}' - DTO members should be accessible",
15
+ removeReadonlyModifier: "Remove 'readonly' modifier from DTO member '{{memberName}}' - DTO members should be mutable",
16
+ },
17
+ },
18
+
19
+ create (context) {
20
+ return {
21
+ PropertyDefinition (node) {
22
+ // Check if this property is in a DTO class
23
+ const parent = node.parent;
24
+ if (parent && parent.type === "ClassBody") {
25
+ const classNode = parent.parent;
26
+ if (classNode && classNode.type === "ClassDeclaration") {
27
+ const className = classNode.id?.name;
28
+ if (className && className.endsWith("Dto")) {
29
+ // This is a DTO class property
30
+
31
+ // Check for visibility modifiers
32
+ if (node.accessibility) {
33
+ const memberName = node.key?.name || "unknown";
34
+ const modifier = node.accessibility;
35
+
36
+ context.report({
37
+ node,
38
+ messageId: `remove${modifier.charAt(0).toUpperCase() + modifier.slice(1)}Modifier`,
39
+ data: {
40
+ memberName,
41
+ },
42
+ fix (fixer) {
43
+ const sourceCode = context.getSourceCode();
44
+ const tokens = sourceCode.getTokens(node);
45
+
46
+ // Find the visibility modifier token
47
+ for (const token of tokens) {
48
+ if (token.type === "Keyword" &&
49
+ (token.value === "public" || token.value === "private" || token.value === "protected")) {
50
+
51
+ // Find the next token after the modifier
52
+ const nextToken = sourceCode.getTokenAfter(token);
53
+
54
+ if (nextToken) {
55
+ // Remove from start of modifier to start of next token (includes whitespace)
56
+ return fixer.removeRange([token.range[0], nextToken.range[0]]);
57
+ } else {
58
+ // Just remove the modifier token
59
+ return fixer.remove(token);
60
+ }
61
+ }
62
+ }
63
+
64
+ return null;
65
+ },
66
+ });
67
+ }
68
+
69
+ // Check for readonly modifier
70
+ if (node.readonly) {
71
+ const memberName = node.key?.name || "unknown";
72
+
73
+ context.report({
74
+ node,
75
+ messageId: "removeReadonlyModifier",
76
+ data: {
77
+ memberName,
78
+ },
79
+ fix (fixer) {
80
+ const sourceCode = context.getSourceCode();
81
+ const tokens = sourceCode.getTokens(node);
82
+
83
+ // Find the readonly modifier token
84
+ for (const token of tokens) {
85
+ if (token.type === "Keyword" && token.value === "readonly") {
86
+ // Find the next token after the modifier
87
+ const nextToken = sourceCode.getTokenAfter(token);
88
+
89
+ if (nextToken) {
90
+ // Remove from start of modifier to start of next token (includes whitespace)
91
+ return fixer.removeRange([token.range[0], nextToken.range[0]]);
92
+ } else {
93
+ // Just remove the modifier token
94
+ return fixer.remove(token);
95
+ }
96
+ }
97
+ }
98
+
99
+ return null;
100
+ },
101
+ });
102
+ }
103
+ }
104
+ }
105
+ }
106
+ },
107
+
108
+ MethodDefinition (node) {
109
+ // Check if this method is in a DTO class
110
+ const parent = node.parent;
111
+ if (parent && parent.type === "ClassBody") {
112
+ const classNode = parent.parent;
113
+ if (classNode && classNode.type === "ClassDeclaration") {
114
+ const className = classNode.id?.name;
115
+ if (className && className.endsWith("Dto")) {
116
+ // This is a DTO class method
117
+ if (node.accessibility) {
118
+ const memberName = node.key?.name || "unknown";
119
+ const modifier = node.accessibility;
120
+
121
+ context.report({
122
+ node,
123
+ messageId: `remove${modifier.charAt(0).toUpperCase() + modifier.slice(1)}Modifier`,
124
+ data: {
125
+ memberName,
126
+ },
127
+ fix (fixer) {
128
+ const sourceCode = context.getSourceCode();
129
+ const tokens = sourceCode.getTokens(node);
130
+
131
+ // Find the visibility modifier token
132
+ for (const token of tokens) {
133
+ if (token.type === "Keyword" &&
134
+ (token.value === "public" || token.value === "private" || token.value === "protected")) {
135
+
136
+ // Find the next token after the modifier
137
+ const nextToken = sourceCode.getTokenAfter(token);
138
+
139
+ if (nextToken) {
140
+ // Remove from start of modifier to start of next token (includes whitespace)
141
+ return fixer.removeRange([token.range[0], nextToken.range[0]]);
142
+ } else {
143
+ // Just remove the modifier token
144
+ return fixer.remove(token);
145
+ }
146
+ }
147
+ }
148
+
149
+ return null;
150
+ },
151
+ });
152
+ }
153
+ }
154
+ }
155
+ }
156
+ },
157
+ };
158
+ },
159
+ };
@@ -0,0 +1,122 @@
1
+ /**
2
+ * ESLint-Regel: enforce-api-versioning
3
+ * Erzwingt einheitliche API-Versionierung in Controller-Routen
4
+ */
5
+
6
+ /** @type {import('eslint').Rule.RuleModule} */
7
+ const enforceApiVersioningRule = {
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "Erzwingt einheitliche API-Versionierung (/api/v1/) in allen Controller-Routen",
12
+ category: "API Standards",
13
+ recommended: true,
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ missingApiVersion: "Controller-Route '{{route}}' muss API-Versionierung verwenden. Erwarte '/api/v1/' Prefix.",
18
+ inconsistentVersion: "Controller-Route '{{route}}' verwendet inkonsistente Versionierung. Verwende '/api/v1/' für alle Routen.",
19
+ invalidRouteFormat: "Controller-Route '{{route}}' hat ungültiges Format. Erwarte '/api/v1/resource-name'.",
20
+ },
21
+ },
22
+ create(context) {
23
+ const filename = context.getFilename();
24
+ const isControllerFile = filename.includes("/controller/") && filename.endsWith("Controller.ts");
25
+ const isAliasController = filename.includes("AliasController.ts");
26
+
27
+ if (!isControllerFile || isAliasController) return {};
28
+
29
+ const API_VERSION_PATTERN = /^\/api\/v1\/?.*/;
30
+ const VALID_ROUTE_PATTERN = /^\/api\/v1\/?[a-z-]*[a-z0-9-]*(?:\/[a-z-]+[a-z0-9-]*)*$/;
31
+
32
+ function validateControllerRoute(routePath) {
33
+ if (!routePath) return { valid: false, reason: "empty" };
34
+
35
+ // Entferne führende/trailing Whitespace
36
+ const cleanPath = routePath.trim();
37
+
38
+ // Prüfe auf API-Versionierung
39
+ if (!API_VERSION_PATTERN.test(cleanPath)) {
40
+ return { valid: false, reason: "missing-version" };
41
+ }
42
+
43
+ // Prüfe auf korrektes Format
44
+ if (!VALID_ROUTE_PATTERN.test(cleanPath)) {
45
+ return { valid: false, reason: "invalid-format" };
46
+ }
47
+
48
+ return { valid: true };
49
+ }
50
+
51
+ return {
52
+ Decorator(node) {
53
+ // Prüfe @Controller() Decorator
54
+ if (node.expression?.name === "Controller" ||
55
+ (node.expression?.type === "CallExpression" &&
56
+ node.expression.callee?.name === "Controller")) {
57
+
58
+ const args = node.expression?.arguments;
59
+ if (args && args.length > 0 && args[0].type === "Literal") {
60
+ const routePath = args[0].value;
61
+ const validation = validateControllerRoute(routePath);
62
+
63
+ if (!validation.valid) {
64
+ let messageId, data;
65
+
66
+ switch (validation.reason) {
67
+ case "missing-version":
68
+ messageId = "missingApiVersion";
69
+ data = { route: routePath };
70
+ break;
71
+ case "invalid-format":
72
+ messageId = "invalidRouteFormat";
73
+ data = { route: routePath };
74
+ break;
75
+ default:
76
+ messageId = "missingApiVersion";
77
+ data = { route: routePath || "undefined" };
78
+ }
79
+
80
+ context.report({
81
+ node: args[0],
82
+ messageId,
83
+ data,
84
+ });
85
+ }
86
+ }
87
+ }
88
+
89
+ // Prüfe HTTP-Method Decoratoren (@Get, @Post, etc.)
90
+ const httpMethods = ["Get", "Post", "Put", "Patch", "Delete"];
91
+ const decoratorName = node.expression?.name || node.expression?.callee?.name;
92
+
93
+ if (httpMethods.includes(decoratorName)) {
94
+ const args = node.expression?.arguments;
95
+ if (args && args.length > 0 && args[0].type === "Literal") {
96
+ const routePath = args[0].value;
97
+
98
+ // Relative Routen sind erlaubt (werden mit Controller-Route kombiniert)
99
+ if (!routePath.startsWith("/")) {
100
+ return;
101
+ }
102
+
103
+ const validation = validateControllerRoute(routePath);
104
+ if (!validation.valid && validation.reason === "missing-version") {
105
+ context.report({
106
+ node: args[0],
107
+ messageId: "missingApiVersion",
108
+ data: { route: routePath },
109
+ });
110
+ }
111
+ }
112
+ }
113
+ },
114
+ };
115
+ },
116
+ };
117
+
118
+ export default {
119
+ rules: {
120
+ "enforce-api-versioning": enforceApiVersioningRule,
121
+ },
122
+ };
@@ -0,0 +1,179 @@
1
+ /**
2
+ * ESLint-Regel: enforce-app-module-registration
3
+ *
4
+ * Überprüft, ob alle Controller und Services korrekt im AppModule registriert sind.
5
+ * Verhindert Dependency Injection Fehler durch fehlende Registrierungen.
6
+ */
7
+
8
+ export default {
9
+ meta: {
10
+ type: "problem",
11
+ docs: {
12
+ description: "Enforce proper registration of controllers and services in AppModule",
13
+ category: "Architecture",
14
+ recommended: true,
15
+ },
16
+ messages: {
17
+ missingController: "Controller '{{controllerName}}' is not registered in AppModule controllers array",
18
+ missingService: "Service '{{serviceName}}' is not registered in AppModule providers array",
19
+ invalidControllerImport: "Controller '{{controllerName}}' is imported but not used in controllers array",
20
+ invalidServiceImport: "Service '{{serviceName}}' is imported but not used in providers array",
21
+ },
22
+ fixable: "code",
23
+ },
24
+
25
+ create(context) {
26
+ const filename = context.getFilename();
27
+
28
+ // Nur für AppModule.ts ausführen
29
+ if (!filename.includes('AppModule.ts')) {
30
+ return {};
31
+ }
32
+
33
+ return {
34
+ Program(node) {
35
+ try {
36
+ // Analysiere AppModule AST
37
+ const appModuleAnalysis = analyzeAppModule(node);
38
+
39
+ // Prüfe fehlende Controller
40
+ appModuleAnalysis.missingControllers.forEach(controller => {
41
+ context.report({
42
+ node,
43
+ messageId: "missingController",
44
+ data: { controllerName: controller.name }
45
+ });
46
+ });
47
+
48
+ // Prüfe fehlende Services
49
+ appModuleAnalysis.missingServices.forEach(service => {
50
+ context.report({
51
+ node,
52
+ messageId: "missingService",
53
+ data: { serviceName: service.name }
54
+ });
55
+ });
56
+
57
+ // Prüfe ungenutzte Imports
58
+ appModuleAnalysis.unusedImports.forEach(importItem => {
59
+ context.report({
60
+ node: importItem.node,
61
+ messageId: importItem.type === 'controller' ? "invalidControllerImport" : "invalidServiceImport",
62
+ data: {
63
+ controllerName: importItem.name,
64
+ serviceName: importItem.name
65
+ }
66
+ });
67
+ });
68
+
69
+ } catch (error) {
70
+ // Fehler beim Analysieren ignorieren
71
+ console.warn('AppModule registration check failed:', error.message);
72
+ }
73
+ }
74
+ };
75
+ }
76
+ };
77
+
78
+ /**
79
+ * Analysiert das AppModule und findet fehlende Registrierungen
80
+ */
81
+ function analyzeAppModule(node) {
82
+ const missingControllers = [];
83
+ const missingServices = [];
84
+ const unusedImports = [];
85
+
86
+ let controllersArray = [];
87
+ let providersArray = [];
88
+ let imports = [];
89
+
90
+ // Durchlaufe alle Import-Statements
91
+ node.body.forEach(statement => {
92
+ if (statement.type === 'ImportDeclaration') {
93
+ const importPath = statement.source.value;
94
+ statement.specifiers.forEach(spec => {
95
+ if (spec.type === 'ImportDefaultSpecifier') {
96
+ imports.push({
97
+ name: spec.local.name,
98
+ path: importPath,
99
+ node: statement
100
+ });
101
+ }
102
+ });
103
+ }
104
+ });
105
+
106
+ // Finde @Module Decorator - suche nach der korrekten NestJS-Struktur
107
+ const moduleDecorator = node.body.find(statement => {
108
+ if (statement.type === 'ExportDefaultDeclaration' &&
109
+ statement.declaration.type === 'ClassDeclaration') {
110
+
111
+ // Prüfe ob es Decorators gibt
112
+ if (statement.declaration.decorators && statement.declaration.decorators.length > 0) {
113
+ return statement.declaration.decorators.some(dec => {
114
+ if (dec.expression && dec.expression.type === 'CallExpression') {
115
+ return dec.expression.callee && dec.expression.callee.name === 'Module';
116
+ }
117
+ return false;
118
+ });
119
+ }
120
+ }
121
+ return false;
122
+ });
123
+
124
+ if (moduleDecorator) {
125
+ const classBody = moduleDecorator.declaration.body;
126
+
127
+ // Finde controllers und providers Arrays
128
+ classBody.body.forEach(member => {
129
+ if (member.type === 'ClassProperty' && member.key && member.key.name === 'controllers') {
130
+ controllersArray = extractArrayElements(member.value);
131
+ }
132
+ if (member.type === 'ClassProperty' && member.key && member.key.name === 'providers') {
133
+ providersArray = extractArrayElements(member.value);
134
+ }
135
+ });
136
+ }
137
+
138
+ // Prüfe Controller-Imports
139
+ imports.forEach(importItem => {
140
+ if (importItem.path.includes('/controller/') || importItem.path.includes('Controller')) {
141
+ const isRegistered = controllersArray.includes(importItem.name);
142
+ if (!isRegistered) {
143
+ missingControllers.push(importItem);
144
+ }
145
+ }
146
+ });
147
+
148
+ // Prüfe Service-Imports
149
+ imports.forEach(importItem => {
150
+ if (importItem.path.includes('/service/') || importItem.path.includes('Service')) {
151
+ const isRegistered = providersArray.includes(importItem.name);
152
+ if (!isRegistered) {
153
+ missingServices.push(importItem);
154
+ }
155
+ }
156
+ });
157
+
158
+ return {
159
+ missingControllers,
160
+ missingServices,
161
+ unusedImports
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Extrahiert Elemente aus einem Array-Literal
167
+ */
168
+ function extractArrayElements(arrayNode) {
169
+ if (!arrayNode || arrayNode.type !== 'ArrayExpression') {
170
+ return [];
171
+ }
172
+
173
+ return arrayNode.elements.map(element => {
174
+ if (element && element.type === 'Identifier') {
175
+ return element.name;
176
+ }
177
+ return null;
178
+ }).filter(Boolean);
179
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * ESLint-Regel, die sicherstellt, dass Controller den BaseController erweitern
3
+ * und dessen Methoden für Response-Strukturen verwenden
4
+ */
5
+
6
+ /** @type {import('eslint').Rule.RuleModule} */
7
+ const enforceBaseControllerRule = {
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "Stellt sicher, dass Controller den BaseController erweitern und dessen Methoden verwenden",
12
+ category: "Best Practices",
13
+ recommended: true,
14
+ },
15
+ fixable: null,
16
+ schema: [
17
+ {
18
+ type: "object",
19
+ properties: {
20
+ controllerPattern: {
21
+ type: "string",
22
+ description: "Regex pattern for identifying controller files",
23
+ default: "Controller\\.ts$"
24
+ },
25
+ baseControllerName: {
26
+ type: "string",
27
+ description: "Name of the base controller class that all controllers should extend",
28
+ default: "BaseController"
29
+ },
30
+ allowDirectResponse: {
31
+ type: "boolean",
32
+ description: "Whether to allow direct response usage (res.json, res.status)",
33
+ default: false
34
+ },
35
+ excludePatterns: {
36
+ type: "array",
37
+ items: { type: "string" },
38
+ description: "File patterns to exclude from the rule",
39
+ default: ["BaseController.ts"]
40
+ }
41
+ },
42
+ additionalProperties: false
43
+ }
44
+ ],
45
+ messages: {
46
+ notExtendingBaseController: "Controller muss BaseController erweitern: '{{controllerName}}'",
47
+ directResponseUsage: "Verwende die BaseController-Methoden (sendSuccess, sendError, etc.) anstatt direkt res.json/status aufzurufen",
48
+ },
49
+ },
50
+ create(context) {
51
+ const options = context.options[0] || {};
52
+ const controllerPattern = new RegExp(options.controllerPattern || "Controller\\.ts$");
53
+ const baseControllerName = options.baseControllerName || "BaseController";
54
+ const allowDirectResponse = options.allowDirectResponse || false;
55
+ const excludePatterns = options.excludePatterns || ["BaseController.ts"];
56
+
57
+ const filename = context.getFilename();
58
+ const isExcluded = excludePatterns.some(pattern => filename.includes(pattern));
59
+ const isControllerFile = (controllerPattern.test(filename) || filename.includes('/fixtures/')) && !isExcluded;
60
+
61
+ // Speichert, ob die aktuelle Datei ein Controller ist, der BaseController erweitert
62
+ let extendsBaseController = false;
63
+ // Speichert den Namen des Controllers für Fehlermeldungen
64
+ let controllerName = "";
65
+
66
+ return {
67
+ // Prüfen, ob ein Controller den BaseController erweitert
68
+ ClassDeclaration(node) {
69
+ if (!isControllerFile) return;
70
+
71
+ // Extrahiere den Controller-Namen
72
+ controllerName = node.id.name;
73
+
74
+ // Ignoriere BaseController selbst
75
+ if (controllerName === baseControllerName) return;
76
+
77
+ // Prüfe, ob der Controller von BaseController erbt
78
+ if (node.superClass &&
79
+ node.superClass.type === "Identifier" &&
80
+ node.superClass.name === baseControllerName) {
81
+ extendsBaseController = true;
82
+ } else {
83
+ // Melde einen Fehler, wenn der Controller nicht von BaseController erbt
84
+ context.report({
85
+ node,
86
+ messageId: "notExtendingBaseController",
87
+ data: {
88
+ controllerName: controllerName,
89
+ },
90
+ });
91
+ }
92
+ },
93
+
94
+ // Prüfen, ob direkte Response-Methoden verwendet werden
95
+ CallExpression(node) {
96
+ if (!isControllerFile || !extendsBaseController) return;
97
+
98
+ // Prüfe auf direkte res.json() Aufrufe
99
+ if (
100
+ !allowDirectResponse &&
101
+ node.callee.type === "MemberExpression" &&
102
+ node.callee.object.type === "Identifier" &&
103
+ node.callee.object.name === "res" &&
104
+ node.callee.property.type === "Identifier" &&
105
+ node.callee.property.name === "json"
106
+ ) {
107
+ context.report({
108
+ node,
109
+ messageId: "directResponseUsage",
110
+ });
111
+ }
112
+
113
+ // Prüfe auf res.status().json() Aufrufe
114
+ if (
115
+ node.callee.type === "MemberExpression" &&
116
+ node.callee.property.type === "Identifier" &&
117
+ node.callee.property.name === "json" &&
118
+ node.callee.object.type === "CallExpression" &&
119
+ node.callee.object.callee.type === "MemberExpression" &&
120
+ node.callee.object.callee.object.type === "Identifier" &&
121
+ node.callee.object.callee.object.name === "res" &&
122
+ node.callee.object.callee.property.type === "Identifier" &&
123
+ node.callee.object.callee.property.name === "status"
124
+ ) {
125
+ context.report({
126
+ node,
127
+ messageId: "directResponseUsage",
128
+ });
129
+ }
130
+ },
131
+
132
+ // Prüfen auf Import des BaseController
133
+ ImportDeclaration(node) {
134
+ if (!isControllerFile) return;
135
+
136
+ // Prüfe, ob BaseController importiert wird
137
+ const specifiers = node.specifiers;
138
+ if (specifiers.some(specifier =>
139
+ specifier.type === "ImportDefaultSpecifier" &&
140
+ specifier.local.name === baseControllerName)) {
141
+ return;
142
+ }
143
+ },
144
+ };
145
+ },
146
+ };
147
+
148
+ export default {
149
+ rules: {
150
+ "enforce-basecontroller": enforceBaseControllerRule,
151
+ },
152
+ };