@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,240 @@
1
+ const enforceViteHealthMetricsRule = {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Enforce Vite configuration to include required health and metrics endpoints",
6
+ category: "Best Practices",
7
+ recommended: true,
8
+ },
9
+ fixable: null,
10
+ schema: [{
11
+ type: "object",
12
+ properties: {
13
+ requiredEndpoints: {
14
+ type: "array",
15
+ items: { type: "string" },
16
+ default: ["/health", "/health/live", "/health/ready", "/metrics"],
17
+ },
18
+ requiredPackages: {
19
+ type: "array",
20
+ items: { type: "string" },
21
+ default: ["@godaddy/terminus", "prom-client"],
22
+ },
23
+ requiredServices: {
24
+ type: "array",
25
+ items: { type: "string" },
26
+ default: ["HealthMetricsService", "MetricsService"],
27
+ },
28
+ },
29
+ additionalProperties: false,
30
+ }],
31
+ messages: {
32
+ missingHealthEndpoint: "Missing required health endpoint: {{endpoint}}. Add middleware for {{endpoint}} in Vite config.",
33
+ missingMetricsEndpoint: "Missing required metrics endpoint: {{endpoint}}. Add middleware for {{endpoint}} in Vite config.",
34
+ missingPackage: "Missing required package: {{package}}. Install {{package}} for health/metrics functionality.",
35
+ missingService: "Missing required service: {{service}}. Import and use {{service}} in Vite config.",
36
+ incorrectContentType: "Health endpoint {{endpoint}} should return 'text/plain' content type for Prometheus metrics.",
37
+ missingConfigureServer: "Vite config must include configureServer function for health/metrics endpoints.",
38
+ missingMiddlewares: "Vite config must include server.middlewares.use for health/metrics endpoints.",
39
+ },
40
+ },
41
+
42
+ create(context) {
43
+ const options = context.options[0] || {};
44
+ const requiredEndpoints = options.requiredEndpoints || ["/health", "/health/live", "/health/ready", "/metrics"];
45
+ const requiredPackages = options.requiredPackages || ["@godaddy/terminus", "prom-client"];
46
+ const requiredServices = options.requiredServices || ["HealthMetricsService", "MetricsService"];
47
+
48
+ let hasViteConfig = false;
49
+ let hasConfigureServer = false;
50
+ let hasMiddlewares = false;
51
+ let foundEndpoints = new Set();
52
+ let foundServices = new Set();
53
+ let foundPackages = new Set();
54
+
55
+ return {
56
+ // Check for Vite config file
57
+ Program() {
58
+ const filename = context.getFilename();
59
+ if (filename.includes("vite.config") || filename.includes("vite.config.ts") || filename.includes("vite.config.js")) {
60
+ hasViteConfig = true;
61
+ }
62
+ },
63
+
64
+ // Check for import statements
65
+ ImportDeclaration(node) {
66
+ const source = node.source.value;
67
+ if (requiredPackages.includes(source)) {
68
+ foundPackages.add(source);
69
+ }
70
+ },
71
+
72
+ // Check for configureServer function
73
+ FunctionDeclaration(node) {
74
+ if (node.id && node.id.name === "configureServer") {
75
+ hasConfigureServer = true;
76
+ }
77
+ },
78
+
79
+ // Check for ArrowFunctionExpression with configureServer
80
+ ArrowFunctionExpression(node) {
81
+ if (node.parent && node.parent.type === "Property" && node.parent.key && node.parent.key.name === "configureServer") {
82
+ hasConfigureServer = true;
83
+ }
84
+ },
85
+
86
+ // Check for server.middlewares.use calls and content type headers
87
+ CallExpression(node) {
88
+ // Check for server.middlewares.use calls
89
+ if (node.callee.type === "MemberExpression" &&
90
+ node.callee.object.type === "MemberExpression" &&
91
+ node.callee.object.object.name === "server" &&
92
+ node.callee.object.property.name === "middlewares" &&
93
+ node.callee.property.name === "use") {
94
+
95
+ hasMiddlewares = true;
96
+
97
+ // Check for endpoint path
98
+ const endpointArg = node.arguments[0];
99
+ if (endpointArg && endpointArg.type === "Literal" && typeof endpointArg.value === "string") {
100
+ const endpoint = endpointArg.value;
101
+ if (requiredEndpoints.includes(endpoint)) {
102
+ foundEndpoints.add(endpoint);
103
+ }
104
+ }
105
+ }
106
+
107
+ // Check for content type headers
108
+ if (node.callee.type === "MemberExpression" &&
109
+ node.callee.property.name === "setHeader") {
110
+
111
+ const headerName = node.arguments[0];
112
+ const headerValue = node.arguments[1];
113
+
114
+ if (headerName && headerName.type === "Literal" && headerName.value === "Content-Type") {
115
+ if (headerValue && headerValue.type === "Literal" && headerValue.value === "application/json") {
116
+ // Find the endpoint this belongs to by traversing up
117
+ let current = node.parent;
118
+ while (current && current.type !== "CallExpression") {
119
+ current = current.parent;
120
+ }
121
+
122
+ if (current && current.callee.type === "MemberExpression" &&
123
+ current.callee.property.name === "use") {
124
+ const endpointArg = current.arguments[0];
125
+ if (endpointArg && endpointArg.type === "Literal" &&
126
+ (endpointArg.value === "/health" || endpointArg.value === "/metrics")) {
127
+ context.report({
128
+ node: headerValue,
129
+ messageId: "incorrectContentType",
130
+ data: { endpoint: endpointArg.value }
131
+ });
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ },
138
+
139
+ // Check for service imports and usage
140
+ VariableDeclarator(node) {
141
+ if (node.init && node.init.type === "CallExpression" &&
142
+ node.init.callee.type === "Identifier" &&
143
+ node.init.callee.name === "import") {
144
+
145
+ const importArg = node.init.arguments[0];
146
+ if (importArg && importArg.type === "Literal" && typeof importArg.value === "string") {
147
+ const importPath = importArg.value;
148
+ if (importPath.includes("HealthMetricsService") || importPath.includes("MetricsService")) {
149
+ requiredServices.forEach(service => {
150
+ if (importPath.includes(service)) {
151
+ foundServices.add(service);
152
+ }
153
+ });
154
+ }
155
+ }
156
+ }
157
+ },
158
+
159
+ // Check for service instantiation
160
+ NewExpression(node) {
161
+ if (node.callee.type === "Identifier") {
162
+ const serviceName = node.callee.name;
163
+ if (requiredServices.includes(serviceName)) {
164
+ foundServices.add(serviceName);
165
+ }
166
+ }
167
+ },
168
+
169
+ // Final validation on program end
170
+ "Program:exit"() {
171
+ if (!hasViteConfig) {
172
+ return; // Not a Vite config file
173
+ }
174
+
175
+ // Check for missing packages
176
+ requiredPackages.forEach(packageName => {
177
+ if (!foundPackages.has(packageName)) {
178
+ context.report({
179
+ node: context.getSourceCode().ast,
180
+ messageId: "missingPackage",
181
+ data: { package: packageName }
182
+ });
183
+ }
184
+ });
185
+
186
+ // Check for missing services
187
+ requiredServices.forEach(serviceName => {
188
+ if (!foundServices.has(serviceName)) {
189
+ context.report({
190
+ node: context.getSourceCode().ast,
191
+ messageId: "missingService",
192
+ data: { service: serviceName }
193
+ });
194
+ }
195
+ });
196
+
197
+ // Check for missing endpoints
198
+ requiredEndpoints.forEach(endpoint => {
199
+ if (!foundEndpoints.has(endpoint)) {
200
+ if (endpoint === "/health" || endpoint === "/metrics") {
201
+ context.report({
202
+ node: context.getSourceCode().ast,
203
+ messageId: "missingMetricsEndpoint",
204
+ data: { endpoint }
205
+ });
206
+ } else {
207
+ context.report({
208
+ node: context.getSourceCode().ast,
209
+ messageId: "missingHealthEndpoint",
210
+ data: { endpoint }
211
+ });
212
+ }
213
+ }
214
+ });
215
+
216
+ // Check for configureServer function
217
+ if (!hasConfigureServer) {
218
+ context.report({
219
+ node: context.getSourceCode().ast,
220
+ messageId: "missingConfigureServer"
221
+ });
222
+ }
223
+
224
+ // Check for middlewares
225
+ if (!hasMiddlewares) {
226
+ context.report({
227
+ node: context.getSourceCode().ast,
228
+ messageId: "missingMiddlewares"
229
+ });
230
+ }
231
+ }
232
+ };
233
+ }
234
+ };
235
+
236
+ export default {
237
+ rules: {
238
+ "enforce-vite-health-metrics": enforceViteHealthMetricsRule,
239
+ },
240
+ };
@@ -0,0 +1,321 @@
1
+ /**
2
+ * @fileoverview Stellt sicher, dass alle required Properties einer Entity beim Erstellen einer Instanz gesetzt werden
3
+ * Diese Regel analysiert Entity-Klassen und prüft, ob bei `new EntityName()` alle nicht-optionalen Properties zugewiesen werden
4
+ */
5
+
6
+ import fs from "fs";
7
+ import path from "path";
8
+
9
+ /** @type {import('eslint').Rule.RuleModule} */
10
+ const entityRequiredPropertiesRule = {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description: "Stellt sicher, dass alle required Properties einer Entity beim Erstellen einer Instanz gesetzt werden",
15
+ category: "TypeORM",
16
+ recommended: true,
17
+ },
18
+ hasSuggestions: true,
19
+ schema: [],
20
+ messages: {
21
+ missingRequiredProperties: "Entity '{{entityName}}' Instanz fehlen required Properties: {{missingProps}}. Diese müssen nach 'new {{entityName}}()' gesetzt werden.",
22
+ optionalPropertyInfo: "Optional properties für '{{entityName}}': {{optionalProps}}",
23
+ },
24
+ },
25
+
26
+ create(context) {
27
+ const sourceCode = context.getSourceCode();
28
+ const filename = context.getFilename();
29
+
30
+ // Nur Service-Dateien und Controller prüfen (dort werden meist Entities erstellt)
31
+ if (!filename.includes("/service/") && !filename.includes("/controller/")) {
32
+ return {};
33
+ }
34
+
35
+ /**
36
+ * Analysiert Entity-Klasse und extrahiert required/optional Properties (String-basiert)
37
+ */
38
+ function analyzeEntityClass(entityFilePath) {
39
+ const required = [];
40
+ const optional = [];
41
+
42
+ try {
43
+ const content = fs.readFileSync(entityFilePath, "utf8");
44
+
45
+ // Finde Klassen-Definition
46
+ const classMatch = content.match(/export\s+class\s+(\w+Entity)/);
47
+ if (!classMatch) {
48
+ return { required, optional };
49
+ }
50
+
51
+ const className = classMatch[1];
52
+
53
+ // Teile Content in Zeilen und analysiere Properties
54
+ const lines = content.split('\n');
55
+ let insideClass = false;
56
+ let currentDecorators = [];
57
+
58
+ for (let i = 0; i < lines.length; i++) {
59
+ const line = lines[i].trim();
60
+
61
+ // Starte bei Klassendefinition
62
+ if (line.includes(`class ${className}`)) {
63
+ insideClass = true;
64
+ continue;
65
+ }
66
+
67
+ if (!insideClass) continue;
68
+
69
+ // Ende der Klasse
70
+ if (line === '}') {
71
+ break;
72
+ }
73
+
74
+ // Decorator erkennen (kann mehrzeilig sein)
75
+ if (line.startsWith('@')) {
76
+ const decoratorMatch = line.match(/@(\w+)/);
77
+ if (decoratorMatch) {
78
+ let decoratorContent = line;
79
+
80
+ // Wenn Decorator mehrzeilig ist, sammle alle Zeilen bis zur schließenden Klammer
81
+ let j = i + 1;
82
+ let openBraces = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
83
+ let openParens = (line.match(/\(/g) || []).length - (line.match(/\)/g) || []).length;
84
+
85
+ while (j < lines.length && (openBraces > 0 || openParens > 0)) {
86
+ const nextLine = lines[j].trim();
87
+ decoratorContent += " " + nextLine;
88
+
89
+ openBraces += (nextLine.match(/\{/g) || []).length - (nextLine.match(/\}/g) || []).length;
90
+ openParens += (nextLine.match(/\(/g) || []).length - (nextLine.match(/\)/g) || []).length;
91
+
92
+ j++;
93
+ }
94
+
95
+ currentDecorators.push({
96
+ name: decoratorMatch[1],
97
+ full: decoratorContent
98
+ });
99
+
100
+ // Springe über verarbeitete Zeilen
101
+ i = j - 1;
102
+ }
103
+ continue;
104
+ }
105
+
106
+ // Property erkennen - nur echte Klassenmember
107
+ const propertyMatch = line.match(/^(\w+)(\?)?\s*:\s*([^;{]+);?\s*$/);
108
+ if (propertyMatch && !line.includes('type:') && !line.includes('length:')) {
109
+ const propertyName = propertyMatch[1];
110
+ const isOptional = !!propertyMatch[2]; // hat ?
111
+ const propertyType = propertyMatch[3];
112
+
113
+ const isRequired = analyzePropertyRequirement(propertyName, isOptional, propertyType, currentDecorators);
114
+
115
+ if (isRequired) {
116
+ required.push(propertyName);
117
+ } else {
118
+ optional.push(propertyName);
119
+ }
120
+
121
+ // Reset für nächstes Property
122
+ currentDecorators = [];
123
+ }
124
+ }
125
+
126
+ } catch (error) {
127
+ // Bei Fehlern keine Warnungen
128
+ }
129
+
130
+ return { required, optional };
131
+ }
132
+
133
+ /**
134
+ * Analysiert ob ein Property required ist (String-basiert)
135
+ */
136
+ function analyzePropertyRequirement(propertyName, isOptional, propertyType, decorators) {
137
+ // TypeScript optional marker
138
+ if (isOptional) {
139
+ return false;
140
+ }
141
+
142
+ // Nur Properties mit Decorators überwachen
143
+ if (decorators.length === 0) {
144
+ return false;
145
+ }
146
+
147
+ // Automatische Properties
148
+ const autoGeneratedDecorators = ["PrimaryGeneratedColumn", "CreateDateColumn", "UpdateDateColumn"];
149
+ if (decorators.some(d => autoGeneratedDecorators.includes(d.name))) {
150
+ return false;
151
+ }
152
+
153
+ // @Column mit nullable oder default
154
+ const columnDecorator = decorators.find(d => d.name === "Column");
155
+ if (columnDecorator) {
156
+ const hasNullable = columnDecorator.full.includes("nullable: true");
157
+ const hasDefault = columnDecorator.full.includes("default:") || columnDecorator.full.includes("default {");
158
+
159
+ if (hasNullable || hasDefault) {
160
+ return false;
161
+ }
162
+ return true; // Column ohne nullable/default ist required
163
+ }
164
+
165
+ // Relationen
166
+ const relationDecorators = ["ManyToOne", "OneToOne", "OneToMany", "ManyToMany"];
167
+ const relationDecorator = decorators.find(d => relationDecorators.includes(d.name));
168
+ if (relationDecorator) {
169
+ const hasNullable = relationDecorator.full.includes("nullable: true");
170
+ const hasNullType = propertyType.includes("| null");
171
+
172
+ if (hasNullable || hasNullType) {
173
+ return false;
174
+ }
175
+ return true; // Relation ohne nullable ist required
176
+ }
177
+
178
+ // Andere Decorators (JoinColumn, etc.) sind optional
179
+ return false;
180
+ }
181
+
182
+ /**
183
+ * Findet Entity-Datei basierend auf Import
184
+ */
185
+ function findEntityFile(entityName) {
186
+ const currentDir = path.dirname(filename);
187
+ const projectRoot = path.resolve(currentDir, "../../../..");
188
+
189
+ // Rekursive Dateisuche ohne externe Dependencies
190
+ function findFileRecursively(dir, fileName) {
191
+ try {
192
+ const files = fs.readdirSync(dir);
193
+
194
+ for (const file of files) {
195
+ const fullPath = path.join(dir, file);
196
+ const stat = fs.statSync(fullPath);
197
+
198
+ if (stat.isFile() && file === fileName) {
199
+ return fullPath;
200
+ } else if (stat.isDirectory()) {
201
+ const found = findFileRecursively(fullPath, fileName);
202
+ if (found) return found;
203
+ }
204
+ }
205
+ } catch {
206
+ // Verzeichnis nicht zugänglich, weiter
207
+ }
208
+ return null;
209
+ }
210
+
211
+ // Suche in entity-Verzeichnissen
212
+ const entitySearchDirs = [
213
+ path.resolve(projectRoot, "src", "entity"),
214
+ path.resolve(currentDir, "../../../entity"),
215
+ path.resolve(currentDir, "../../../../entity"),
216
+ ];
217
+
218
+ for (const searchDir of entitySearchDirs) {
219
+ const found = findFileRecursively(searchDir, `${entityName}.ts`);
220
+ if (found) {
221
+ return found;
222
+ }
223
+ }
224
+
225
+ return null;
226
+ }
227
+
228
+ /**
229
+ * Sammelt alle Property-Zuweisungen nach einer Entity-Instanziierung
230
+ */
231
+ function collectPropertyAssignments(entityVariableName, startNode) {
232
+ const assignments = new Set();
233
+ const scope = sourceCode.getScope(startNode);
234
+
235
+ // Durchsuche nachfolgende Statements für Property-Zuweisungen
236
+ let parent = startNode.parent;
237
+ while (parent && parent.type !== "BlockStatement") {
238
+ parent = parent.parent;
239
+ }
240
+
241
+ if (parent && parent.body) {
242
+ const startIndex = parent.body.indexOf(startNode.parent);
243
+ const followingStatements = parent.body.slice(startIndex + 1);
244
+
245
+ followingStatements.forEach(statement => {
246
+ if (statement.type === "ExpressionStatement" &&
247
+ statement.expression.type === "AssignmentExpression") {
248
+ const left = statement.expression.left;
249
+ if (left.type === "MemberExpression" &&
250
+ left.object?.name === entityVariableName) {
251
+ assignments.add(left.property?.name);
252
+ }
253
+ }
254
+ });
255
+ }
256
+
257
+ return assignments;
258
+ }
259
+
260
+ return {
261
+ VariableDeclarator(node) {
262
+ // Prüfe auf `new EntityName()` Pattern
263
+ if (node.init?.type === "NewExpression") {
264
+ const entityName = node.init.callee?.name;
265
+
266
+ // Prüfe ob es eine Entity ist (Name endet mit "Entity")
267
+ if (entityName && entityName.endsWith("Entity")) {
268
+ const variableName = node.id?.name;
269
+
270
+ if (variableName) {
271
+ // Finde Entity-Datei und analysiere required Properties
272
+ const entityFile = findEntityFile(entityName);
273
+
274
+ if (entityFile) {
275
+ const { required, optional } = analyzeEntityClass(entityFile);
276
+
277
+ if (required.length > 0) {
278
+ // Sammle Property-Zuweisungen nach der Instanziierung
279
+ const assignments = collectPropertyAssignments(variableName, node);
280
+
281
+ // Finde fehlende required Properties
282
+ const missingProperties = required.filter(prop => !assignments.has(prop));
283
+
284
+ if (missingProperties.length > 0) {
285
+ context.report({
286
+ node: node.init,
287
+ messageId: "missingRequiredProperties",
288
+ data: {
289
+ entityName,
290
+ missingProps: missingProperties.join(", ")
291
+ },
292
+ suggest: [
293
+ {
294
+ desc: `Add missing required properties for ${entityName}`,
295
+ fix(fixer) {
296
+ // Erstelle Vorschlag für fehlende Properties
297
+ const suggestions = missingProperties.map(prop =>
298
+ `${variableName}.${prop} = /* TODO: set value */;`
299
+ ).join("\n");
300
+
301
+ return fixer.insertTextAfter(node.parent, `\n${suggestions}`);
302
+ }
303
+ }
304
+ ]
305
+ });
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
313
+ };
314
+ },
315
+ };
316
+
317
+ export default {
318
+ rules: {
319
+ "entity-required-properties": entityRequiredPropertiesRule,
320
+ },
321
+ };
@@ -0,0 +1,73 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Verhindert die direkte Verwendung von Entities in Controller-Responses und erzwingt DTOs",
6
+ category: "Security",
7
+ recommended: true,
8
+ },
9
+ fixable: null,
10
+ schema: [],
11
+ messages: {
12
+ 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.",
13
+ },
14
+ },
15
+
16
+ create(context) {
17
+ const filename = context.getFilename();
18
+
19
+ // Nur Controller-Dateien prüfen
20
+ if (!filename.includes("Controller.ts") || filename.includes("test")) {
21
+ return {};
22
+ }
23
+
24
+ // Entity-Pattern erkennen
25
+ function isEntityPattern(variableName) {
26
+ return (
27
+ variableName.endsWith("Definition") ||
28
+ variableName.endsWith("Entity") ||
29
+ variableName.endsWith("Model") ||
30
+ variableName.endsWith("Record") ||
31
+ variableName.endsWith("Instance") ||
32
+ variableName.startsWith("new") ||
33
+ variableName.includes("Entity") ||
34
+ variableName.includes("Definition")
35
+ );
36
+ }
37
+
38
+ return {
39
+ CallExpression(node) {
40
+ // Prüfe auf this.sendSuccess() Aufrufe
41
+ if (node.callee?.type === "MemberExpression" &&
42
+ node.callee.object?.type === "ThisExpression" &&
43
+ node.callee.property?.name === "sendSuccess") {
44
+
45
+ // Prüfe das zweite Argument (Response-Objekt)
46
+ if (node.arguments.length >= 2 &&
47
+ node.arguments[1]?.type === "ObjectExpression") {
48
+
49
+ const responseObject = node.arguments[1];
50
+
51
+ // Prüfe alle Properties im Response-Objekt
52
+ responseObject.properties.forEach(property => {
53
+ if (property.type === "Property" &&
54
+ property.value?.type === "Identifier") {
55
+
56
+ const variableName = property.value.name;
57
+
58
+ // Melde Entity-Patterns
59
+ if (isEntityPattern(variableName)) {
60
+ context.report({
61
+ node: property.value,
62
+ messageId: "entityInResponse",
63
+ data: { variableName }
64
+ });
65
+ }
66
+ }
67
+ });
68
+ }
69
+ }
70
+ }
71
+ };
72
+ },
73
+ };