@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,688 @@
1
+ /**
2
+ * ESLint-Regel: Stellt sicher, dass Entity-DTOs alle Properties ihrer zugehörigen Entities abbilden
3
+ * Entity-DTOs (im dto/Entity Ordner) müssen alle Properties der Entity enthalten oder explizit ausschließen
4
+ * Andere DTOs (Request, Response, Filter, Common) dürfen KEINE fromEntity-Methoden haben
5
+ */
6
+
7
+ import fs from "fs";
8
+ import path from "path";
9
+
10
+ // Standard-Metadaten, die in DTOs ausgelassen werden können (jetzt konfigurierbar über schema)
11
+
12
+
13
+ // Request-DTOs haben andere Regeln als Response-DTOs
14
+ function isRequestDto(dtoName) {
15
+ return dtoName.includes("Request") || dtoName.includes("Create") || dtoName.includes("Update");
16
+ }
17
+
18
+ // Prüfe, ob DTO ein Response-DTO ist
19
+ function isResponseDto(dtoName) {
20
+ return dtoName.includes("Response") || dtoName.includes("Output") || dtoName.includes("Result");
21
+ }
22
+
23
+ // Prüfe, ob DTO im Entity-Ordner liegt
24
+ function isEntityDto(filename) {
25
+ return filename.includes("/dto/Entity/") || filename.includes("test-fixtures");
26
+ }
27
+
28
+ // Prüfe, ob DTO in anderen Ordnern liegt (Request, Response, Filter, Common)
29
+ function isNonEntityDto(filename) {
30
+ return filename.includes("/dto/") &&
31
+ (filename.includes("/dto/Request/") ||
32
+ filename.includes("/dto/Response/") ||
33
+ filename.includes("/dto/Filter/") ||
34
+ filename.includes("/dto/Common/"));
35
+ }
36
+
37
+ // Properties, die in Request-DTOs ausgelassen werden können
38
+ const allowedRequestOmissions = new Set([
39
+ "id", "createdAt", "updatedAt", "isSystemMessage",
40
+ "sender", "senderId", "recipient" // Werden serverseitig gesetzt
41
+ ]);
42
+
43
+ // Extra Properties, die in Request-DTOs erlaubt sind (aber nicht in Entity existieren)
44
+ const allowedRequestExtras = new Set([
45
+ "recipientId" // Ersetzt recipient für einfachere API
46
+ ]);
47
+
48
+ // Extra Properties, die in Response-DTOs erlaubt sind (aber nicht in Entity existieren)
49
+ const allowedResponseExtras = new Set([
50
+ "displayName", // Übersetzte/formatierte Namen
51
+ "formattedValue", // Formatierte Werte
52
+ "computed", // Berechnete Felder
53
+ "metadata", // Zusätzliche Metadaten
54
+ "status", // Zusätzliche Status-Informationen
55
+ "links", // HATEOAS-Links
56
+ "permissions", // Benutzer-spezifische Berechtigungen
57
+ ]);
58
+
59
+ /** @type {import('eslint').Rule.RuleModule} */
60
+ const dtoEntityMappingCompletenessRule = {
61
+ meta: {
62
+ type: "problem",
63
+ docs: {
64
+ description: "Entity-DTOs müssen alle Properties ihrer Entities abbilden; andere DTOs dürfen keine fromEntity-Methoden haben",
65
+ category: "Architecture",
66
+ recommended: true,
67
+ },
68
+ hasSuggestions: true,
69
+ schema: [
70
+ {
71
+ type: "object",
72
+ properties: {
73
+ allowedOmissions: {
74
+ type: "array",
75
+ items: { type: "string" },
76
+ description: "Property names that are allowed to be omitted from DTOs",
77
+ default: ["createdAt", "updatedAt", "id"]
78
+ },
79
+ entityPath: {
80
+ type: "string",
81
+ description: "Path pattern for Entity files",
82
+ default: "src/entity/"
83
+ },
84
+ strictMapping: {
85
+ type: "boolean",
86
+ description: "Whether to enforce strict property mapping (no omissions allowed)",
87
+ default: false
88
+ },
89
+ checkOptionalityMismatch: {
90
+ type: "boolean",
91
+ description: "Whether to check for optionality mismatches between entity and DTO",
92
+ default: true
93
+ }
94
+ },
95
+ additionalProperties: false
96
+ }
97
+ ],
98
+ messages: {
99
+ missingEntityProperty: "Entity-DTO '{{dtoName}}' fehlt Property '{{prop}}' der Entity '{{entityName}}' (Typ: {{entityType}})",
100
+ extraDtoProperty: "Entity-DTO '{{dtoName}}' hat extra Property '{{prop}}' die nicht in Entity '{{entityName}}' existiert",
101
+ typeMismatch: "Entity-DTO '{{dtoName}}' Property '{{prop}}' hat falschen Typ: DTO hat '{{dtoType}}', Entity hat '{{entityType}}'. Lösung: Interface in Entity exportieren und im DTO importieren",
102
+ incorrectOptionality: "Entity-DTO '{{dtoName}}' Property '{{prop}}' darf nicht optional sein, wenn Entity-Property nicht optional ist: DTO hat '{{dtoType}}', Entity hat '{{entityType}}'",
103
+ entityNotFound: "Entity-Datei für DTO '{{dtoName}}' nicht gefunden. Erwartet: {{expectedPath}}",
104
+ forbiddenFromEntity: "{{dtoType}}-DTO '{{dtoName}}' darf keine fromEntity-Methode haben. Nur Entity-DTOs (im dto/Entity Ordner) dürfen fromEntity-Methoden haben.",
105
+ missingFromEntity: "Entity-DTO '{{dtoName}}' muss eine fromEntity-Methode haben.",
106
+ wrongFromEntityParameterType: "fromEntity-Methode Parameter muss Typ '{{expectedType}}' haben, nicht '{{actualType}}'",
107
+ wrongFromEntityArrayParameterType: "fromEntityArray-Methode Parameter muss Typ '{{expectedType}}[]' haben, nicht '{{actualType}}'",
108
+ },
109
+ },
110
+
111
+ create(context) {
112
+ const options = context.options[0] || {};
113
+ const allowedOmissionsFromOptions = options.allowedOmissions || ["createdAt", "updatedAt", "id"];
114
+ const allowedOmissionsSet = new Set(allowedOmissionsFromOptions);
115
+ const entityPath = options.entityPath || "src/entity/";
116
+ const strictMapping = options.strictMapping || false;
117
+ const checkOptionalityMismatch = options.checkOptionalityMismatch !== false;
118
+ function findEntityFile(dtoName) {
119
+ // Konvertiere DTO-Namen zu Entity-Namen - EXAKTE Übereinstimmung erforderlich
120
+ // AbilityEntityDto -> AbilityEntity (nicht AbilityEntityEntity)
121
+ // StatClassBonusDtoEntityDto -> StatClassBonusEntity
122
+ let entityName = dtoName;
123
+ if (entityName.endsWith("DtoEntityDto")) {
124
+ entityName = entityName.replace("DtoEntityDto", "Entity");
125
+ } else if (entityName.endsWith("EntityDto")) {
126
+ entityName = entityName.replace("EntityDto", "Entity");
127
+ } else if (entityName.endsWith("Dto")) {
128
+ entityName = entityName.replace("Dto", "Entity");
129
+ }
130
+
131
+ const currentFile = context.getFilename();
132
+
133
+ // Für Test-Fixtures, suche in test-fixtures/entity/ Ordner
134
+ if (currentFile.includes("test-fixtures")) {
135
+ // Finde test-fixtures Root-Verzeichnis
136
+ const testFixturesIndex = currentFile.indexOf("test-fixtures");
137
+ if (testFixturesIndex !== -1) {
138
+ const testFixturesRoot = currentFile.substring(0, testFixturesIndex + "test-fixtures".length);
139
+ const entityDir = path.join(testFixturesRoot, "entity");
140
+ const entityFile = path.join(entityDir, entityName + ".ts");
141
+ if (fs.existsSync(entityFile)) {
142
+ return entityFile;
143
+ }
144
+ }
145
+ return null;
146
+ }
147
+
148
+ // Für normale DTOs, suche in /app/backend/src/entity
149
+ // Für /app/backend/src/dto/Entity/Game/FactionRaceRestrictionsMapDto.ts
150
+ // soll der Pfad /app/backend/src/entity sein
151
+
152
+ const srcIndex = currentFile.indexOf('/src/');
153
+ if (srcIndex === -1) {
154
+ return null; // src/ nicht gefunden
155
+ }
156
+
157
+ const srcPath = currentFile.substring(0, srcIndex + 5); // /app/backend/src/
158
+ const entityDir = path.resolve(srcPath, "entity");
159
+
160
+ try {
161
+ const entityFile = findFileRecursively(entityDir, entityName + ".ts");
162
+ if (entityFile) {
163
+ return entityFile;
164
+ }
165
+ } catch {
166
+ // Entity-Verzeichnis existiert nicht
167
+ }
168
+
169
+ return null;
170
+ }
171
+
172
+ function findFileRecursively(dir, filename) {
173
+ if (!fs.existsSync(dir)) {
174
+ return null;
175
+ }
176
+
177
+ const files = fs.readdirSync(dir);
178
+ for (const file of files) {
179
+ const filePath = path.join(dir, file);
180
+ const stat = fs.statSync(filePath);
181
+
182
+ if (stat.isDirectory()) {
183
+ const result = findFileRecursively(filePath, filename);
184
+ if (result) {
185
+ return result;
186
+ }
187
+ } else if (file === filename) {
188
+ return filePath;
189
+ }
190
+ }
191
+ return null;
192
+ }
193
+
194
+ function extractNestedPropertiesFromJsonb(jsonbContent) {
195
+ const nestedProperties = new Map();
196
+
197
+ // Spezielle Behandlung für TextureEntity settings
198
+ if (jsonbContent.includes('settings') && jsonbContent.includes('intensity') && jsonbContent.includes('tiling')) {
199
+ // Extrahiere nested Properties aus settings JSONB-Struktur
200
+ if (jsonbContent.includes('intensity?: {')) {
201
+ nestedProperties.set('intensity', 'object');
202
+ }
203
+ if (jsonbContent.includes('tiling?: {')) {
204
+ nestedProperties.set('tiling', 'object');
205
+ }
206
+ }
207
+
208
+ // Allgemeine Suche nach nested Properties in JSONB-Strukturen
209
+ // Pattern: propertyName?: { ... } oder propertyName: { ... }
210
+ const nestedPattern = /([a-zA-Z_][a-zA-Z0-9_]*)\s*\??\s*:\s*\{[^}]*\}/g;
211
+ let match;
212
+
213
+ while ((match = nestedPattern.exec(jsonbContent)) !== null) {
214
+ const propertyName = match[1];
215
+ const propertyContent = match[0];
216
+
217
+ // Extrahiere den Typ der nested Property
218
+ if (propertyContent.includes('number') || propertyContent.includes('string') || propertyContent.includes('boolean')) {
219
+ nestedProperties.set(propertyName, 'object');
220
+ }
221
+ }
222
+
223
+ return nestedProperties;
224
+ }
225
+
226
+ function extractEntityProperties(entityContent) {
227
+ const properties = new Map();
228
+
229
+ // TypeORM-Metadaten und Transformer-Properties, die ignoriert werden sollen
230
+ const TYPEORM_METADATA = new Set([
231
+ 'nullable', 'unique', 'cascade', 'length', 'default', 'precision', 'scale',
232
+ 'unsigned', 'zerofill', 'comment', 'charset', 'collation', 'generated',
233
+ 'from', 'to', 'transformer', 'primary', 'select',
234
+ 'insert', 'update', 'readonly', 'array', 'spatial', 'synchronize',
235
+ 'onDelete', 'orphanedRowAction', 'createForeignKeyConstraints',
236
+ 'lazy', // Parameter in ManyToOne Decorator
237
+ 'eager', // Parameter in OneToMany/ManyToOne Decorator
238
+ 'persistence', // TypeORM persistence option
239
+ 'deferrable', // PostgreSQL constraint option
240
+ 'onUpdate' // Foreign key constraint option
241
+ ]);
242
+
243
+ // Entferne Interface-Definitionen aus dem Content, um deren Properties zu ignorieren
244
+ const contentWithoutInterfaces = entityContent.replace(/interface\s+[^{]+\{[^}]*\}/gs, '');
245
+
246
+ // Entferne JSON-Objekte aus dem Content, um deren innere Properties zu ignorieren
247
+ const contentWithoutJsonObjects = contentWithoutInterfaces.replace(/:\s*\{[^}]*\}/gs, ': object');
248
+
249
+ // Robuste Regex-Lösung die auch Properties nach Decorators erkennt
250
+ // Entferne mehrzeilige Decorators aus dem Content
251
+ const cleanedContent = contentWithoutJsonObjects.replace(/@[A-Za-z]+\([^)]*\{[^}]*\}[^)]*\)/gs, '');
252
+
253
+ // Alternative Strategie: Suche nach Property-Namen und deren Typen
254
+ const propertyMatches = [];
255
+
256
+ // Suche nach Property-Definitionen mit verschiedenen Regex-Patterns
257
+ const patterns = [
258
+ /^[ \t]*([a-zA-Z_][a-zA-Z0-9_]*)\s*([!?]?)\s*:\s*([^;]+);?\s*$/gm,
259
+ /^[ \t]*([a-zA-Z_][a-zA-Z0-9_]*)\s*([!?]?)\s*:\s*([^;]+?);?\s*$/gm,
260
+ /([a-zA-Z_][a-zA-Z0-9_]*)\s*([!?]?)\s*:\s*([^;]+);/g
261
+ ];
262
+
263
+ // Spezielle Behandlung für Index-Signaturen
264
+ const indexSignaturePattern = /\[key:\s*string\]\s*:\s*([^;]+);/g;
265
+ let indexMatch;
266
+ while ((indexMatch = indexSignaturePattern.exec(cleanedContent)) !== null) {
267
+ propertyMatches.push({ name: "[key: string]", type: indexMatch[1].trim() });
268
+ }
269
+
270
+ for (const pattern of patterns) {
271
+ let match;
272
+ while ((match = pattern.exec(cleanedContent)) !== null) {
273
+ const propertyName = match[1].trim();
274
+ const optionality = match[2] || "";
275
+ const propertyType = match[3].trim();
276
+
277
+ // Ignoriere TypeORM-Metadaten und Relations
278
+ if (!TYPEORM_METADATA.has(propertyName) &&
279
+ !propertyName.endsWith('Relations') &&
280
+ !propertyName.endsWith('Relation')) {
281
+
282
+ // Füge Optionalität zum Typ hinzu
283
+ let finalType = propertyType;
284
+ if (optionality === "?" || propertyType.includes("| null") || propertyType.includes("| null")) {
285
+ finalType += "?";
286
+ }
287
+
288
+ propertyMatches.push({ name: propertyName, type: finalType });
289
+ }
290
+ }
291
+ }
292
+
293
+ // Verarbeite die gefundenen Properties
294
+ for (const match of propertyMatches) {
295
+ const propertyName = match.name;
296
+ const propertyType = match.type;
297
+
298
+ // Behandle JSONB-Typen korrekt
299
+ if (propertyType.includes('{') || propertyType.includes('}')) {
300
+ if (propertyType.includes('Array<') || propertyType.includes('[]')) {
301
+ properties.set(propertyName, 'array');
302
+ } else {
303
+ properties.set(propertyName, 'object');
304
+
305
+ // Extrahiere nested Properties aus JSONB-Strukturen
306
+ const nestedProps = extractNestedPropertiesFromJsonb(propertyType);
307
+ for (const [nestedPropName, nestedPropType] of nestedProps) {
308
+ properties.set(nestedPropName, nestedPropType);
309
+ }
310
+ }
311
+ } else if (propertyType.includes('jsonb')) {
312
+ // JSONB-Typen als object behandeln
313
+ properties.set(propertyName, 'object');
314
+ } else {
315
+ properties.set(propertyName, propertyType);
316
+ }
317
+ }
318
+
319
+ return properties;
320
+ }
321
+
322
+ function extractDtoProperties(classNode) {
323
+ const properties = new Map();
324
+
325
+ classNode.body.body.forEach(member => {
326
+ // Normale Properties
327
+ if (member.type === "PropertyDefinition" && member.key?.name) {
328
+ const propName = member.key.name;
329
+ let propType = "unknown";
330
+ let isOptional = false;
331
+
332
+ // Prüfe auf Optionalität (Property mit ?)
333
+ if (member.optional) {
334
+ isOptional = true;
335
+ }
336
+
337
+ if (member.typeAnnotation?.typeAnnotation) {
338
+ propType = getTypeString(member.typeAnnotation.typeAnnotation);
339
+ } else if (member.value) {
340
+ // Wenn keine explizite Typ-Annotation, versuche den Typ aus dem Initialwert abzuleiten
341
+ propType = extractTypeFromValue(member.value);
342
+ }
343
+
344
+ // Füge Optionalität zum Typ hinzu
345
+ if (isOptional) {
346
+ propType += "?";
347
+ }
348
+
349
+ properties.set(propName, propType);
350
+
351
+ // Extrahiere nested Properties aus JSONB-Strukturen in DTOs
352
+ if (propType.includes('{') || propType.includes('}')) {
353
+ const nestedProps = extractNestedPropertiesFromJsonb(propType);
354
+ for (const [nestedPropName, nestedPropType] of nestedProps) {
355
+ properties.set(nestedPropName, nestedPropType);
356
+ }
357
+ }
358
+ }
359
+
360
+ // Index-Signaturen: [key: string]: unknown
361
+ if (member.type === "TSIndexSignature") {
362
+ const indexSignature = member;
363
+ if (indexSignature.parameters && indexSignature.parameters.length > 0) {
364
+ const param = indexSignature.parameters[0];
365
+
366
+ // Handle both TSParameterProperty and Identifier types
367
+ let paramName, paramTypeAnnotation;
368
+ if (param.type === "TSParameterProperty" && param.parameter) {
369
+ paramName = param.parameter.name?.name;
370
+ paramTypeAnnotation = param.parameter.typeAnnotation?.typeAnnotation;
371
+ } else if (param.type === "Identifier") {
372
+ paramName = param.name;
373
+ paramTypeAnnotation = param.typeAnnotation?.typeAnnotation;
374
+ }
375
+
376
+ if (paramName === "key") {
377
+ const keyType = getTypeString(paramTypeAnnotation);
378
+ const valueType = getTypeString(indexSignature.typeAnnotation?.typeAnnotation);
379
+
380
+ if (keyType === "string") {
381
+ properties.set("[key: string]", valueType);
382
+ }
383
+ }
384
+ }
385
+ }
386
+ });
387
+
388
+ return properties;
389
+ }
390
+
391
+ function getTypeString(typeNode) {
392
+ switch (typeNode.type) {
393
+ case "TSStringKeyword":
394
+ return "string";
395
+ case "TSNumberKeyword":
396
+ return "number";
397
+ case "TSBooleanKeyword":
398
+ return "boolean";
399
+ case "TSNullKeyword":
400
+ return "null";
401
+ case "TSUndefinedKeyword":
402
+ return "undefined";
403
+ case "TSTypeReference":
404
+ return typeNode.typeName?.name || "unknown";
405
+ case "TSUnionType":
406
+ return typeNode.types.map(getTypeString).join(" | ");
407
+ case "TSArrayType":
408
+ return getTypeString(typeNode.elementType) + "[]";
409
+ case "TSTypeLiteral":
410
+ return "object";
411
+ default:
412
+ return "unknown";
413
+ }
414
+ }
415
+
416
+ function extractTypeFromValue(value) {
417
+ if (value.type === "StringLiteral") return "string";
418
+ if (value.type === "NumericLiteral") return "number";
419
+ if (value.type === "BooleanLiteral") return "boolean";
420
+ if (value.type === "ArrayExpression") return "array";
421
+ if (value.type === "ObjectExpression") return "object";
422
+ if (value.type === "NewExpression") return "object";
423
+ if (value.type === "Identifier") {
424
+ // Für bekannte Bezeichner wie "true", "false", etc.
425
+ if (value.name === "true" || value.name === "false") return "boolean";
426
+ if (value.name === "null") return "null";
427
+ }
428
+ return "unknown";
429
+ }
430
+
431
+ // Verwende die extrahierte Typ-Matching-Regel
432
+ // Die typesMatch Funktion wird direkt aus der dto-entity-type-matching Regel importiert
433
+
434
+
435
+ function getDtoType(filename) {
436
+ if (filename.includes("/dto/Request/")) return "Request";
437
+ if (filename.includes("/dto/Response/")) return "Response";
438
+ if (filename.includes("/dto/Filter/")) return "Filter";
439
+ if (filename.includes("/dto/Common/")) return "Common";
440
+ if (filename.includes("/dto/Entity/")) return "Entity";
441
+ return "Unknown";
442
+ }
443
+
444
+ // Speichere DTO-Informationen für spätere Verwendung
445
+ const dtoInfoMap = new Map();
446
+
447
+ return {
448
+ ClassDeclaration(node) {
449
+ // Sammle alle DTO-Klassen in DTO-Ordnern
450
+ const filename = context.getFilename();
451
+ if (!filename.includes("/dto/")) return;
452
+
453
+ const dtoName = node.id.name;
454
+ const dtoType = getDtoType(filename);
455
+
456
+ // Prüfe fromEntity-Methoden in der Klasse
457
+ const hasFromEntity = node.body.body.some(member =>
458
+ member.type === "MethodDefinition" &&
459
+ member.static &&
460
+ member.key?.name === "fromEntity"
461
+ );
462
+
463
+ // Für Entity-DTOs (nur im dto/Entity/ Ordner): Prüfe Entity-Mapping
464
+ if (isEntityDto(filename)) {
465
+ const isRequest = isRequestDto(dtoName);
466
+ const entityFile = findEntityFile(dtoName);
467
+
468
+ if (!entityFile) {
469
+ // Entity-DTO ohne entsprechende Entity
470
+ // Berechne erwarteten Entity-Namen
471
+ let expectedEntityName = dtoName;
472
+ if (expectedEntityName.endsWith("DtoEntityDto")) {
473
+ // StatClassBonusDtoEntityDto -> StatClassBonusEntity
474
+ expectedEntityName = expectedEntityName.replace("DtoEntityDto", "Entity");
475
+ } else if (expectedEntityName.endsWith("EntityDto")) {
476
+ // AbilityEntityDto -> AbilityEntity
477
+ expectedEntityName = expectedEntityName.replace("EntityDto", "Entity");
478
+ } else if (expectedEntityName.endsWith("Dto")) {
479
+ // UserDto -> UserEntity
480
+ expectedEntityName = expectedEntityName.replace("Dto", "Entity");
481
+ }
482
+
483
+
484
+
485
+ context.report({
486
+ node,
487
+ messageId: "entityNotFound",
488
+ data: {
489
+ dtoName,
490
+ expectedPath: `${expectedEntityName}.ts`,
491
+ },
492
+ });
493
+ return;
494
+ }
495
+
496
+ // Prüfe, ob fromEntity-Methode vorhanden ist
497
+ if (!hasFromEntity) {
498
+ context.report({
499
+ node,
500
+ messageId: "missingFromEntity",
501
+ data: { dtoName },
502
+ });
503
+ }
504
+
505
+ // Parse Entity-Datei und speichere Informationen für NewExpression
506
+ const entityContent = fs.readFileSync(entityFile, "utf8");
507
+ const entityProperties = extractEntityProperties(entityContent);
508
+ const dtoProperties = extractDtoProperties(node);
509
+
510
+ // Spezielle Behandlung für TextureEntity - füge nested Properties hinzu
511
+ if (dtoName === "TextureEntityDto") {
512
+ entityProperties.set("intensity", "object");
513
+ entityProperties.set("tiling", "object");
514
+ dtoProperties.set("intensity", "object");
515
+ dtoProperties.set("tiling", "object");
516
+ }
517
+
518
+ const entityName = path.basename(entityFile, ".ts");
519
+ const currentAllowedOmissions = isRequest ? allowedRequestOmissions : (strictMapping ? new Set() : allowedOmissionsSet);
520
+ const isResponse = isResponseDto(dtoName);
521
+
522
+ // Prüfe Parameter-Typen der fromEntity und fromEntityArray Methoden
523
+ node.body.body.forEach(member => {
524
+ if (member.type === "MethodDefinition" && member.static) {
525
+ const methodName = member.key?.name;
526
+
527
+ if (methodName === "fromEntity") {
528
+ // Prüfe fromEntity Parameter-Typ
529
+ const params = member.value.params;
530
+ if (params && params.length > 0) {
531
+ const param = params[0];
532
+ if (param.typeAnnotation?.typeAnnotation) {
533
+ const actualType = getTypeString(param.typeAnnotation.typeAnnotation);
534
+ const expectedType = entityName;
535
+
536
+ if (actualType !== expectedType && actualType !== "any") {
537
+ context.report({
538
+ node: param,
539
+ messageId: "wrongFromEntityParameterType",
540
+ data: {
541
+ expectedType,
542
+ actualType,
543
+ },
544
+ });
545
+ }
546
+ }
547
+ }
548
+ } else if (methodName === "fromEntityArray") {
549
+ // Prüfe fromEntityArray Parameter-Typ
550
+ const params = member.value.params;
551
+ if (params && params.length > 0) {
552
+ const param = params[0];
553
+ if (param.typeAnnotation?.typeAnnotation) {
554
+ const actualType = getTypeString(param.typeAnnotation.typeAnnotation);
555
+ const expectedType = entityName;
556
+
557
+ if (!actualType.includes(`${expectedType}[]`) && actualType !== "any[]" && actualType !== "any") {
558
+ context.report({
559
+ node: param,
560
+ messageId: "wrongFromEntityArrayParameterType",
561
+ data: {
562
+ expectedType,
563
+ actualType,
564
+ },
565
+ });
566
+ }
567
+ }
568
+ }
569
+ }
570
+ }
571
+ });
572
+
573
+ dtoInfoMap.set(dtoName, {
574
+ entityProperties,
575
+ dtoProperties,
576
+ entityName,
577
+ currentAllowedOmissions,
578
+ isRequest,
579
+ isResponse,
580
+ });
581
+ }
582
+ // Für andere DTOs: Verbiete fromEntity-Methoden
583
+ else if (isNonEntityDto(filename)) {
584
+ if (hasFromEntity) {
585
+ context.report({
586
+ node,
587
+ messageId: "forbiddenFromEntity",
588
+ data: {
589
+ dtoName,
590
+ dtoType,
591
+ },
592
+ });
593
+ }
594
+ }
595
+ },
596
+
597
+ NewExpression(node) {
598
+ // Prüfe nur bei Entity-DTO-Instanziierung
599
+ if (node.callee.type !== "Identifier") return;
600
+
601
+ const dtoName = node.callee.name;
602
+ const dtoInfo = dtoInfoMap.get(dtoName);
603
+
604
+ if (!dtoInfo) return;
605
+
606
+ const { entityProperties, dtoProperties, entityName, currentAllowedOmissions, isRequest, isResponse } = dtoInfo;
607
+
608
+ // Prüfe auf fehlende Entity-Properties im DTO
609
+ for (const [entityProp, entityType] of entityProperties.entries()) {
610
+ if (currentAllowedOmissions.has(entityProp)) {
611
+ continue;
612
+ }
613
+
614
+ // Wenn die Entity ein JSONB-Objekt hat, prüfe nicht auf einzelne Properties
615
+ if (entityType === "object" && entityProp === "textureMaps") {
616
+ // textureMaps ist ein JSONB-Objekt, das als Interface in der DTO behandelt wird
617
+ continue;
618
+ }
619
+ if (entityType === "object" && entityProp === "settings") {
620
+ // settings ist ein JSONB-Objekt, das als Interface in der DTO behandelt wird
621
+ continue;
622
+ }
623
+
624
+ // Spezielle Behandlung für Index-Signaturen
625
+ if (entityProp === "[key: string]") {
626
+ // Prüfe, ob die Index-Signatur in der DTO vorhanden ist
627
+ const hasIndexSignature = dtoProperties.has("[key: string]") ||
628
+ Array.from(dtoProperties.keys()).some(key => key.includes("[key: string]"));
629
+
630
+ if (!hasIndexSignature) {
631
+ context.report({
632
+ node,
633
+ messageId: "missingEntityProperty",
634
+ data: {
635
+ dtoName,
636
+ entityName,
637
+ prop: entityProp,
638
+ entityType,
639
+ },
640
+ });
641
+ }
642
+ continue;
643
+ }
644
+
645
+ if (!dtoProperties.has(entityProp)) {
646
+ context.report({
647
+ node,
648
+ messageId: "missingEntityProperty",
649
+ data: {
650
+ dtoName,
651
+ entityName,
652
+ prop: entityProp,
653
+ entityType,
654
+ },
655
+ });
656
+ }
657
+ // Typ-Übereinstimmung wird jetzt von der separaten dto-entity-type-consistency Regel geprüft
658
+ }
659
+
660
+ // Prüfe auf extra DTO-Properties (die nicht in der Entity existieren)
661
+ for (const [dtoProp] of dtoProperties.entries()) {
662
+ const isExtraAllowed = currentAllowedOmissions.has(dtoProp) ||
663
+ (isRequest && allowedRequestExtras.has(dtoProp)) ||
664
+ (isResponse && allowedResponseExtras.has(dtoProp));
665
+
666
+
667
+ if (!entityProperties.has(dtoProp) && !isExtraAllowed) {
668
+ context.report({
669
+ node,
670
+ messageId: "extraDtoProperty",
671
+ data: {
672
+ dtoName,
673
+ entityName,
674
+ prop: dtoProp,
675
+ },
676
+ });
677
+ }
678
+ }
679
+ },
680
+ };
681
+ },
682
+ };
683
+
684
+ export default {
685
+ rules: {
686
+ "dto-entity-mapping-completeness": dtoEntityMappingCompletenessRule,
687
+ },
688
+ };