@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,340 @@
1
+ /**
2
+ * ESLint-Regeln für Controller-Architektur
3
+ * 1. Controller dürfen keine Repositories direkt nutzen, nur Services
4
+ * 2. Rückgabewerte müssen DTOs sein, nie Entities
5
+ * 3. Response-Handling muss über sendSuccess/sendError erfolgen
6
+ */
7
+
8
+ /** @type {import('eslint').Rule.RuleModule} */
9
+ const noDirectRepositoryUseRule = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description: "Controller dürfen keine Repositories direkt nutzen, nur Services",
14
+ category: "Architecture",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ noDirectRepository: "Controller dürfen keine Repositories direkt nutzen. Verwende stattdessen einen Service: '{{usage}}'",
20
+ noDirectDataSource: "Controller dürfen DataSource nicht direkt nutzen. Verwende stattdessen einen Service: '{{usage}}'",
21
+ },
22
+ },
23
+ create(context) {
24
+ const controllerFiles = /Controller\.ts$/;
25
+ const filename = context.getFilename();
26
+ const isControllerFile = controllerFiles.test(filename) &&
27
+ !/BaseController\.ts$/.test(filename);
28
+
29
+ if (!isControllerFile) return {};
30
+
31
+ return {
32
+ // Prüfe auf direkte Repository-Nutzung
33
+ CallExpression(node) {
34
+ // dataSource.getRepository() oder appDataSource.getRepository()
35
+ if (
36
+ node.callee.type === "MemberExpression" &&
37
+ node.callee.property.type === "Identifier" &&
38
+ node.callee.property.name === "getRepository" &&
39
+ node.callee.object.type === "Identifier" &&
40
+ (node.callee.object.name === "dataSource" ||
41
+ node.callee.object.name === "appDataSource" ||
42
+ node.callee.object.name.includes("DataSource"))
43
+ ) {
44
+ context.report({
45
+ node,
46
+ messageId: "noDirectRepository",
47
+ data: {
48
+ usage: `${node.callee.object.name}.getRepository()`,
49
+ },
50
+ });
51
+ }
52
+
53
+ // Repository-Methoden direkt aufrufen
54
+ if (
55
+ node.callee.type === "MemberExpression" &&
56
+ node.callee.object.type === "CallExpression" &&
57
+ node.callee.object.callee.type === "MemberExpression" &&
58
+ node.callee.object.callee.property.name === "getRepository"
59
+ ) {
60
+ context.report({
61
+ node,
62
+ messageId: "noDirectRepository",
63
+ data: {
64
+ usage: "repository method call",
65
+ },
66
+ });
67
+ }
68
+ },
69
+
70
+ // Prüfe auf direkte DataSource-Imports
71
+ ImportDeclaration(node) {
72
+ if (node.source.value === "typeorm" &&
73
+ node.specifiers.some(spec =>
74
+ spec.type === "ImportSpecifier" &&
75
+ spec.imported.name === "DataSource")) {
76
+ context.report({
77
+ node,
78
+ messageId: "noDirectDataSource",
79
+ data: {
80
+ usage: "DataSource import",
81
+ },
82
+ });
83
+ }
84
+
85
+ // Prüfe auf appDataSource Import
86
+ if (node.source.value &&
87
+ node.source.value.includes("ormconfig") ||
88
+ node.source.value.includes("data-source")) {
89
+ context.report({
90
+ node,
91
+ messageId: "noDirectDataSource",
92
+ data: {
93
+ usage: "appDataSource import",
94
+ },
95
+ });
96
+ }
97
+ },
98
+ };
99
+ },
100
+ };
101
+
102
+ /** @type {import('eslint').Rule.RuleModule} */
103
+ const requireDtoResponseRule = {
104
+ meta: {
105
+ type: "problem",
106
+ docs: {
107
+ description: "Controller müssen DTOs zurückgeben, nie Entities",
108
+ category: "Architecture",
109
+ recommended: true,
110
+ },
111
+ schema: [],
112
+ messages: {
113
+ returnEntity: "Controller dürfen keine Entities zurückgeben. Verwende DTOs: '{{entityName}}'",
114
+ serviceReturnsEntity: "Service-Methode '{{methodName}}' gibt wahrscheinlich Entity zurück. Controller müssen DTOs verwenden",
115
+ },
116
+ },
117
+ create(context) {
118
+ const controllerFiles = /Controller\.ts$/;
119
+ const filename = context.getFilename();
120
+ const isControllerFile = controllerFiles.test(filename) &&
121
+ !/BaseController\.ts$/.test(filename);
122
+
123
+ if (!isControllerFile) return {};
124
+
125
+ // Sammle Service-Imports und erkenne Entity-verdächtige Patterns
126
+ const serviceImports = new Set();
127
+ const entityImports = new Set();
128
+
129
+ return {
130
+ // Sammle Imports von Services und Entities
131
+ ImportDeclaration(node) {
132
+ if (node.source.value && typeof node.source.value === "string") {
133
+ const importPath = node.source.value;
134
+
135
+ // Erkenne Entity-Imports (endend mit "Entity" oder aus entity/ Ordnern)
136
+ if (importPath.includes("/entity/") ||
137
+ node.specifiers.some(spec =>
138
+ spec.type === "ImportDefaultSpecifier" &&
139
+ spec.local.name.endsWith("Entity"))) {
140
+ node.specifiers.forEach(spec => {
141
+ if (spec.type === "ImportDefaultSpecifier") {
142
+ entityImports.add(spec.local.name);
143
+ }
144
+ });
145
+ }
146
+
147
+ // Erkenne Service-Imports
148
+ if (importPath.includes("/service/") ||
149
+ node.specifiers.some(spec =>
150
+ spec.type === "ImportDefaultSpecifier" &&
151
+ spec.local.name.endsWith("Service"))) {
152
+ node.specifiers.forEach(spec => {
153
+ if (spec.type === "ImportDefaultSpecifier") {
154
+ serviceImports.add(spec.local.name);
155
+ }
156
+ });
157
+ }
158
+ }
159
+ },
160
+
161
+ // Prüfe Return-Statements auf Entity-Rückgaben
162
+ ReturnStatement(node) {
163
+ if (node.argument &&
164
+ node.argument.type === "CallExpression" &&
165
+ node.argument.callee.type === "MemberExpression" &&
166
+ node.argument.callee.object.type === "Identifier" &&
167
+ node.argument.callee.object.name === "repo") {
168
+ context.report({
169
+ node,
170
+ messageId: "returnEntity",
171
+ data: {
172
+ entityName: "repository result",
173
+ },
174
+ });
175
+ }
176
+ },
177
+
178
+ // Prüfe Service-Aufrufe in handleGetAll/handleGetById
179
+ CallExpression(node) {
180
+ // Prüfe auf this.handleGetAll(..., () => this.serviceMethod(), ...)
181
+ if (node.callee.type === "MemberExpression" &&
182
+ node.callee.object.type === "ThisExpression" &&
183
+ (node.callee.property.name === "handleGetAll" ||
184
+ node.callee.property.name === "handleGetById")) {
185
+
186
+ // Finde das Service-Callback (zweites Argument bei handleGetAll)
187
+ const callbackArg = node.arguments[1];
188
+ if (callbackArg && callbackArg.type === "ArrowFunctionExpression") {
189
+
190
+ let serviceCall = callbackArg.body;
191
+
192
+ // Handle async functions with BlockStatement body
193
+ if (serviceCall.type === "BlockStatement" && serviceCall.body.length > 0) {
194
+ const returnStmt = serviceCall.body.find(stmt => stmt.type === "ReturnStatement");
195
+ if (returnStmt && returnStmt.argument) {
196
+ serviceCall = returnStmt.argument;
197
+ }
198
+ }
199
+
200
+ // Check if it's a direct CallExpression
201
+ if (serviceCall && serviceCall.type === "CallExpression") {
202
+ if (serviceCall.callee.type === "MemberExpression") {
203
+ let methodName = "";
204
+
205
+ // Handle this.serviceProperty.methodName() pattern
206
+ if (serviceCall.callee.object.type === "MemberExpression" &&
207
+ serviceCall.callee.object.object.type === "ThisExpression" &&
208
+ serviceCall.callee.property.name) {
209
+ methodName = serviceCall.callee.property.name;
210
+ }
211
+ // Handle this.methodName() pattern
212
+ else if (serviceCall.callee.object.type === "ThisExpression" &&
213
+ serviceCall.callee.property.name) {
214
+ methodName = serviceCall.callee.property.name;
215
+ }
216
+
217
+ if (methodName) {
218
+ // Prüfe nur ob die Service-Klasse auf Entity endet
219
+ const isEntitySuspicious = serviceCall.callee.object.type === "MemberExpression" &&
220
+ serviceCall.callee.object.object.type === "ThisExpression" &&
221
+ serviceCall.callee.object.property.name.endsWith("Entity");
222
+
223
+ if (isEntitySuspicious) {
224
+ context.report({
225
+ node: serviceCall,
226
+ messageId: "serviceReturnsEntity",
227
+ data: {
228
+ methodName: methodName,
229
+ },
230
+ });
231
+ }
232
+ }
233
+ }
234
+ }
235
+ }
236
+ }
237
+ },
238
+ };
239
+ },
240
+ };
241
+
242
+ /** @type {import('eslint').Rule.RuleModule} */
243
+ const requireProperResponseHandlingRule = {
244
+ meta: {
245
+ type: "problem",
246
+ docs: {
247
+ description: "Controller müssen sendSuccess/sendError für Responses nutzen",
248
+ category: "Architecture",
249
+ recommended: true,
250
+ },
251
+ schema: [],
252
+ messages: {
253
+ useBaseControllerMethods: "Verwende this.sendSuccess() oder this.sendError() anstatt direkter Response-Methoden: '{{method}}'",
254
+ noDirectReturn: "Controller-Methoden dürfen nicht direkt Werte zurückgeben. Verwende this.sendSuccess(): '{{returnType}}'",
255
+ },
256
+ },
257
+ create(context) {
258
+ const controllerFiles = /Controller\.ts$/;
259
+ const filename = context.getFilename();
260
+ const isControllerFile = controllerFiles.test(filename) &&
261
+ !/BaseController\.ts$/.test(filename);
262
+
263
+ if (!isControllerFile) return {};
264
+
265
+ return {
266
+ // Prüfe auf direkte res.json/res.status Aufrufe
267
+ CallExpression(node) {
268
+ // res.json() direkt
269
+ if (
270
+ node.callee.type === "MemberExpression" &&
271
+ node.callee.object.type === "Identifier" &&
272
+ node.callee.object.name === "res" &&
273
+ node.callee.property.type === "Identifier" &&
274
+ (node.callee.property.name === "json" ||
275
+ node.callee.property.name === "status" ||
276
+ node.callee.property.name === "send")
277
+ ) {
278
+ context.report({
279
+ node,
280
+ messageId: "useBaseControllerMethods",
281
+ data: {
282
+ method: `res.${node.callee.property.name}()`,
283
+ },
284
+ });
285
+ }
286
+ },
287
+
288
+ // Prüfe Return-Statements in Controller-Methoden
289
+ MethodDefinition(node) {
290
+ // Nur öffentliche Methoden prüfen (nicht private oder protected)
291
+ if (node.accessibility === "private" || node.accessibility === "protected") {
292
+ return;
293
+ }
294
+
295
+ if (node.value.type === "FunctionExpression" ||
296
+ node.value.type === "ArrowFunctionExpression") {
297
+
298
+ // Durchsuche den Function Body nach Return-Statements
299
+ const checkReturnStatements = (blockStatement) => {
300
+ if (!blockStatement || !blockStatement.body) return;
301
+
302
+ for (const stmt of blockStatement.body) {
303
+ if (stmt.type === "ReturnStatement" &&
304
+ stmt.argument &&
305
+ stmt.argument.type !== "CallExpression") {
306
+
307
+ // Ignoriere "return;" ohne Wert
308
+ if (stmt.argument.type === "Identifier" &&
309
+ stmt.argument.name === "undefined") continue;
310
+
311
+ context.report({
312
+ node: stmt,
313
+ messageId: "noDirectReturn",
314
+ data: {
315
+ returnType: "value",
316
+ },
317
+ });
318
+ }
319
+ }
320
+ };
321
+
322
+ if (node.value.body.type === "BlockStatement") {
323
+ checkReturnStatements(node.value.body);
324
+ }
325
+ }
326
+ },
327
+ };
328
+ },
329
+ };
330
+
331
+ // Export-Objekt mit allen Regeln
332
+ const controllerArchitectureRules = {
333
+ rules: {
334
+ "controller-architecture": noDirectRepositoryUseRule,
335
+ "require-dto-response": requireDtoResponseRule,
336
+ "require-proper-response-handling": requireProperResponseHandlingRule,
337
+ },
338
+ };
339
+
340
+ export default controllerArchitectureRules;
@@ -0,0 +1,190 @@
1
+ /**
2
+ * @fileoverview Enforce consistent naming conventions for controller methods
3
+ */
4
+
5
+ "use strict";
6
+
7
+ export default {
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "enforce consistent naming conventions for controller HTTP methods",
12
+ category: "Best Practices",
13
+ recommended: true,
14
+ },
15
+ fixable: null,
16
+ schema: [],
17
+ messages: {
18
+ invalidMethodName: "Controller method '{{methodName}}' does not follow naming conventions. Use: getById (GET with params), getAll (GET without params), create (POST), update (PUT/PATCH), delete (DELETE)",
19
+ entityInResponse: "Entity-Variable '{{variableName}}' darf nicht direkt in sendSuccess() verwendet werden. Controller müssen Entities zu DTOs konvertieren bevor sie als API-Response zurückgegeben werden.",
20
+ },
21
+ },
22
+
23
+ create(context) {
24
+ const filename = context.getFilename();
25
+
26
+ if (!filename.includes("Controller.ts") || filename.includes("test") || filename.includes("spec")) {
27
+ return {};
28
+ }
29
+
30
+ function isControllerClass(node) {
31
+ if (node.type !== "ClassDeclaration") return false;
32
+
33
+ return node.decorators && node.decorators.some(decorator => {
34
+ return decorator.expression &&
35
+ ((decorator.expression.type === "Identifier" && decorator.expression.name === "Controller") ||
36
+ (decorator.expression.type === "CallExpression" &&
37
+ decorator.expression.callee.name === "Controller"));
38
+ });
39
+ }
40
+
41
+ function getHttpMethodType(decorators) {
42
+ if (!decorators) return null;
43
+
44
+ const httpMethodMap = {
45
+ "Get": "get",
46
+ "Post": "post",
47
+ "Put": "put",
48
+ "Delete": "delete",
49
+ "Patch": "patch"
50
+ };
51
+
52
+ for (const decorator of decorators) {
53
+ const decoratorName = decorator.expression?.name || decorator.expression?.callee?.name;
54
+ if (httpMethodMap[decoratorName]) {
55
+ let route = "";
56
+ let hasParameter = false;
57
+
58
+ if (decorator.expression?.type === "CallExpression" &&
59
+ decorator.expression.arguments.length > 0 &&
60
+ decorator.expression.arguments[0].type === "Literal") {
61
+ route = decorator.expression.arguments[0].value;
62
+ hasParameter = route.includes(":");
63
+ }
64
+
65
+ return {
66
+ type: httpMethodMap[decoratorName],
67
+ hasParameter,
68
+ route,
69
+ isSubResource: route.includes("/") && route.includes(":")
70
+ };
71
+ }
72
+ }
73
+
74
+ return null;
75
+ }
76
+
77
+ function validateMethodName(methodName, httpMethod) {
78
+ const conventions = {
79
+ get: {
80
+ withParam: "getById",
81
+ withoutParam: "getAll"
82
+ },
83
+ post: "create",
84
+ put: "update",
85
+ delete: "delete",
86
+ patch: "update"
87
+ };
88
+
89
+ // Allow flexible naming for sub-resource routes (e.g., ":id/effects", ":id/assignments")
90
+ if (httpMethod.isSubResource) {
91
+ return true;
92
+ }
93
+
94
+ if (httpMethod.type === "get") {
95
+ const expectedName = httpMethod.hasParameter ?
96
+ conventions.get.withParam :
97
+ conventions.get.withoutParam;
98
+ return methodName === expectedName;
99
+ }
100
+
101
+ return methodName === conventions[httpMethod.type];
102
+ }
103
+
104
+ function getExpectedName(httpMethod) {
105
+ if (httpMethod.type === "get") {
106
+ return httpMethod.hasParameter ? "getById" : "getAll";
107
+ }
108
+
109
+ const expectedNames = {
110
+ post: "create",
111
+ put: "update",
112
+ delete: "delete",
113
+ patch: "update"
114
+ };
115
+
116
+ return expectedNames[httpMethod.type];
117
+ }
118
+
119
+ // Entity-Pattern erkennen
120
+ function isEntityPattern(variableName) {
121
+ return (
122
+ variableName.endsWith("Definition") ||
123
+ variableName.endsWith("Entity") ||
124
+ variableName.endsWith("Model") ||
125
+ variableName.endsWith("Record") ||
126
+ variableName.endsWith("Instance") ||
127
+ variableName.startsWith("new") ||
128
+ variableName.includes("Entity") ||
129
+ variableName.includes("Definition")
130
+ );
131
+ }
132
+
133
+ return {
134
+ ClassDeclaration(node) {
135
+ if (!isControllerClass(node)) return;
136
+
137
+ node.body.body.forEach(member => {
138
+ if (member.type === "MethodDefinition" &&
139
+ member.kind === "method" &&
140
+ member.accessibility === "public") {
141
+
142
+ const httpMethod = getHttpMethodType(member.decorators);
143
+ if (!httpMethod) return;
144
+
145
+ const methodName = member.key.name;
146
+
147
+ if (!validateMethodName(methodName, httpMethod)) {
148
+ const expectedName = getExpectedName(httpMethod);
149
+
150
+ context.report({
151
+ node: member,
152
+ message: `Controller method '${methodName}' must be named '${expectedName}' for ${httpMethod.type.toUpperCase()} methods. Use consistent naming across all controllers.`,
153
+ });
154
+ }
155
+ }
156
+ });
157
+ },
158
+
159
+ // Prüfe Entity-Patterns in sendSuccess
160
+ CallExpression(node) {
161
+ if (node.callee?.type === "MemberExpression" &&
162
+ node.callee.object?.type === "ThisExpression" &&
163
+ node.callee.property?.name === "sendSuccess") {
164
+
165
+ if (node.arguments.length >= 2 &&
166
+ node.arguments[1]?.type === "ObjectExpression") {
167
+
168
+ const responseObject = node.arguments[1];
169
+
170
+ responseObject.properties.forEach(property => {
171
+ if (property.type === "Property" &&
172
+ property.value?.type === "Identifier") {
173
+
174
+ const variableName = property.value.name;
175
+
176
+ if (isEntityPattern(variableName)) {
177
+ context.report({
178
+ node: property.value,
179
+ messageId: "entityInResponse",
180
+ data: { variableName }
181
+ });
182
+ }
183
+ }
184
+ });
185
+ }
186
+ }
187
+ }
188
+ };
189
+ },
190
+ };
@@ -0,0 +1,148 @@
1
+ /**
2
+ * ESLint-Regel: Controller Readonly Restriction
3
+ * Normale Controller (nicht Admin) dürfen nur GET-Methoden haben.
4
+ * Schreibende Zugriffe (POST, PUT, DELETE) erfolgen über Socket.io.
5
+ */
6
+
7
+ /** @type {import('eslint').Rule.RuleModule} */
8
+ const noWriteMethodsInPublicControllersRule = {
9
+ meta: {
10
+ type: "problem",
11
+ docs: {
12
+ description: "Normale Controller dürfen nur GET-Methoden haben. Schreibende Zugriffe erfolgen über Socket.io.",
13
+ category: "Architecture",
14
+ recommended: true,
15
+ },
16
+ schema: [
17
+ {
18
+ type: "object",
19
+ properties: {
20
+ exceptions: {
21
+ type: "array",
22
+ items: {
23
+ type: "string"
24
+ },
25
+ description: "Array von Datei-Pfaden oder Mustern, die von dieser Regel ausgenommen werden sollen"
26
+ }
27
+ },
28
+ additionalProperties: false
29
+ }
30
+ ],
31
+ messages: {
32
+ noWriteMethods: "Normale Controller dürfen nur GET-Methoden haben. {{method}}-Methoden sind nicht erlaubt. Verwende Socket.io für schreibende Zugriffe.",
33
+ noWriteDecorators: "Normale Controller dürfen keine {{decorator}}-Decorators haben. Verwende Socket.io für schreibende Zugriffe.",
34
+ },
35
+ },
36
+ create(context) {
37
+ const controllerFiles = /Controller\.ts$/;
38
+ const filename = context.getFilename();
39
+ const isControllerFile = controllerFiles.test(filename) &&
40
+ !/BaseController\.ts$/.test(filename);
41
+
42
+ if (!isControllerFile) return {};
43
+
44
+ // Hole die Konfiguration für Ausnahmen
45
+ const options = context.options[0] || {};
46
+ const exceptions = options.exceptions || [];
47
+
48
+ // Prüfe ob die aktuelle Datei in den Ausnahmen ist
49
+ const isException = exceptions.some(exception => {
50
+ // Unterstütze sowohl exakte Pfade als auch Muster
51
+ if (exception.includes('*')) {
52
+ // Einfache Glob-Pattern-Unterstützung
53
+ const pattern = new RegExp(exception.replace(/\*/g, '.*'));
54
+ return pattern.test(filename);
55
+ } else {
56
+ // Exakter Pfad-Vergleich (auch relative Pfade)
57
+ return filename.includes(exception) || filename.endsWith(exception);
58
+ }
59
+ });
60
+
61
+ // Wenn die Datei eine Ausnahme ist, überspringe die Prüfung
62
+ if (isException) return {};
63
+
64
+ // Prüfe ob es ein Admin-Controller ist
65
+ const isAdminController = filename.includes("/Admin/") ||
66
+ filename.includes("AdminController") ||
67
+ context.getSourceCode().getText().includes("@UseGuards(AuthGuard, AdminGuard)");
68
+
69
+ // Normale Controller (nicht Admin) werden geprüft
70
+ if (isAdminController) return {};
71
+
72
+ const writeMethods = ["POST", "PUT", "PATCH", "DELETE"];
73
+ const writeDecorators = ["@Post", "@Put", "@Patch", "@Delete"];
74
+
75
+ return {
76
+ // Prüfe auf HTTP-Method-Decorators
77
+ Decorator(node) {
78
+ if (node.expression.type === "CallExpression" &&
79
+ node.expression.callee.type === "Identifier") {
80
+
81
+ const decoratorName = node.expression.callee.name;
82
+
83
+ if (writeDecorators.includes(`@${decoratorName}`)) {
84
+ context.report({
85
+ node,
86
+ messageId: "noWriteDecorators",
87
+ data: {
88
+ decorator: `@${decoratorName}`,
89
+ },
90
+ });
91
+ }
92
+ }
93
+ },
94
+
95
+ // Prüfe auf Methodennamen die schreibende Operationen implizieren
96
+ MethodDefinition(node) {
97
+ if (node.key.type === "Identifier") {
98
+ const methodName = node.key.name;
99
+
100
+ // Prüfe auf typische schreibende Methodennamen
101
+ const writeMethodNames = [
102
+ "create", "post", "add", "insert",
103
+ "update", "put", "patch", "edit", "modify",
104
+ "delete", "remove", "destroy", "drop"
105
+ ];
106
+
107
+ if (writeMethodNames.some(writeMethod =>
108
+ methodName.toLowerCase().includes(writeMethod))) {
109
+
110
+ context.report({
111
+ node,
112
+ messageId: "noWriteMethods",
113
+ data: {
114
+ method: methodName,
115
+ },
116
+ });
117
+ }
118
+ }
119
+ },
120
+
121
+ // Prüfe auf handleCreate, handleUpdate, handleDelete Aufrufe
122
+ CallExpression(node) {
123
+ if (node.callee.type === "MemberExpression" &&
124
+ node.callee.object.type === "ThisExpression" &&
125
+ node.callee.property.type === "Identifier") {
126
+
127
+ const methodName = node.callee.property.name;
128
+
129
+ if (["handleCreate", "handleUpdate", "handleDelete"].includes(methodName)) {
130
+ context.report({
131
+ node,
132
+ messageId: "noWriteMethods",
133
+ data: {
134
+ method: `this.${methodName}()`,
135
+ },
136
+ });
137
+ }
138
+ }
139
+ },
140
+ };
141
+ },
142
+ };
143
+
144
+ export default {
145
+ rules: {
146
+ "no-write-methods-in-public-controllers": noWriteMethodsInPublicControllersRule,
147
+ },
148
+ };