@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,265 @@
1
+ /**
2
+ * @fileoverview Enforce Swagger attributes in DTOs and prohibit them in Entities based on folder structure
3
+ */
4
+
5
+ "use strict";
6
+
7
+ export default {
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "enforce @ApiProperty in DTOs and prohibit Swagger attributes in Entities",
12
+ category: "Architecture",
13
+ recommended: true,
14
+ },
15
+ fixable: "code",
16
+ schema: [],
17
+ messages: {
18
+ missingApiPropertyInDto: "DTO property '{{propertyName}}' is missing @ApiProperty decorator. All DTO properties must have Swagger documentation.",
19
+ forbiddenSwaggerInEntity: "Entity property '{{propertyName}}' has Swagger decorator '{{decoratorName}}'. Entities should not contain API documentation - use DTOs instead.",
20
+ missingApiPropertyImportInDto: "DTO file is missing 'ApiProperty' import from '@nestjs/swagger'. All DTO properties must be documented.",
21
+ unnecessarySwaggerImportInEntity: "Entity file imports Swagger decorators ('{{importName}}'). Entities should not contain API documentation - remove Swagger imports.",
22
+ germanDescriptionInApiProperty: "DTO property '{{propertyName}}' has German description in @ApiProperty. All API descriptions must be in English for international compatibility.",
23
+ },
24
+ },
25
+
26
+ create(context) {
27
+ const filename = context.getFilename();
28
+ const isDtoFile = filename.includes("/dto/") || filename.includes("\\dto\\");
29
+ const isEntityFile = filename.includes("/entity/") || filename.includes("\\entity\\");
30
+
31
+ if (!isDtoFile && !isEntityFile) {
32
+ return {};
33
+ }
34
+
35
+ let hasApiPropertyImport = false;
36
+ let hasSwaggerImports = [];
37
+
38
+ function isSwaggerDecorator(decoratorName) {
39
+ const swaggerDecorators = [
40
+ "ApiProperty",
41
+ "ApiHideProperty",
42
+ "ApiPropertyOptional",
43
+ "ApiResponse",
44
+ "ApiOperation",
45
+ "ApiTags",
46
+ "ApiBearerAuth",
47
+ "ApiBody",
48
+ "ApiParam",
49
+ "ApiQuery"
50
+ ];
51
+ return swaggerDecorators.includes(decoratorName);
52
+ }
53
+
54
+ function hasApiPropertyDecorator(decorators) {
55
+ if (!decorators) return false;
56
+
57
+ return decorators.some(decorator => {
58
+ const decoratorName = decorator.expression?.name || decorator.expression?.callee?.name;
59
+ return decoratorName === "ApiProperty" || decoratorName === "ApiPropertyOptional";
60
+ });
61
+ }
62
+
63
+ function getSwaggerDecorators(decorators) {
64
+ if (!decorators) return [];
65
+
66
+ return decorators.filter(decorator => {
67
+ const decoratorName = decorator.expression?.name || decorator.expression?.callee?.name;
68
+ return isSwaggerDecorator(decoratorName);
69
+ }).map(decorator => decorator.expression?.name || decorator.expression?.callee?.name);
70
+ }
71
+
72
+ function isPublicProperty(member) {
73
+ // TypeScript class properties that are public (default) or explicitly public
74
+ return member.type === "PropertyDefinition" ||
75
+ (member.type === "TSParameterProperty" &&
76
+ (member.accessibility === "public" || !member.accessibility));
77
+ }
78
+
79
+ function shouldCheckProperty(member) {
80
+ // Skip private/protected properties and constructor parameters
81
+ if (member.accessibility === "private" || member.accessibility === "protected") {
82
+ return false;
83
+ }
84
+
85
+ // Skip static properties
86
+ if (member.static) {
87
+ return false;
88
+ }
89
+
90
+ // Skip methods
91
+ if (member.type === "MethodDefinition") {
92
+ return false;
93
+ }
94
+
95
+ return isPublicProperty(member);
96
+ }
97
+
98
+ function containsGermanWords(text) {
99
+ if (!text || typeof text !== "string") return false;
100
+
101
+ // Verwende franc für automatische Spracherkennung
102
+ // franc erkennt über 400 Sprachen und ist sehr präzise
103
+ try {
104
+ // Importiere franc dynamisch für ESLint-Regel
105
+ const franc = require("franc");
106
+
107
+ // Mindestlänge für zuverlässige Erkennung
108
+ if (text.length < 10) {
109
+ // Für sehr kurze Texte: einfache Heuristik mit häufigen deutschen Wörtern
110
+ const shortGermanWords = ["der", "die", "das", "und", "für", "von", "mit", "ist", "sind", "hat", "haben", "wird", "werden"];
111
+ const lowercaseText = text.toLowerCase();
112
+ return shortGermanWords.some(word => new RegExp(`\\b${word}\\b`).test(lowercaseText));
113
+ }
114
+
115
+ // franc gibt ISO 639-3 Codes zurück: 'deu' für Deutsch, 'eng' für Englisch
116
+ const detectedLanguage = franc(text);
117
+
118
+ // Prüfe auf deutsche Sprache (alle deutschen Varianten)
119
+ const germanLanguageCodes = ["deu", "gsw", "bar", "ksh", "pfl", "sli", "vmf", "wae"];
120
+ return germanLanguageCodes.includes(detectedLanguage);
121
+
122
+ } catch (error) {
123
+ // Fallback: Einfache Heuristik wenn franc nicht verfügbar ist
124
+ const commonGermanWords = ["der", "die", "das", "und", "oder", "für", "von", "zu", "mit", "bei", "ist", "sind", "wird", "werden", "benutzer", "anwender", "einstellung", "konfiguration", "beschreibung"];
125
+ const lowercaseText = text.toLowerCase();
126
+ return commonGermanWords.some(word => new RegExp(`\\b${word}\\b`).test(lowercaseText));
127
+ }
128
+ }
129
+
130
+ function checkApiPropertyDescription(decorator, propertyName) {
131
+ if (!decorator.expression?.arguments?.[0]) return;
132
+
133
+ const optionsNode = decorator.expression.arguments[0];
134
+ if (optionsNode.type !== "ObjectExpression") return;
135
+
136
+ const descriptionProperty = optionsNode.properties.find(prop =>
137
+ prop.key?.name === "description" ||
138
+ (prop.key?.type === "Literal" && prop.key?.value === "description")
139
+ );
140
+
141
+ if (descriptionProperty && descriptionProperty.value?.type === "Literal") {
142
+ const description = descriptionProperty.value.value;
143
+ if (containsGermanWords(description)) {
144
+ context.report({
145
+ node: descriptionProperty,
146
+ messageId: "germanDescriptionInApiProperty",
147
+ data: {
148
+ propertyName: propertyName
149
+ }
150
+ });
151
+ }
152
+ }
153
+ }
154
+
155
+ return {
156
+ ImportDeclaration(node) {
157
+ if (node.source.value === "@nestjs/swagger") {
158
+ node.specifiers.forEach(spec => {
159
+ const importName = spec.imported?.name || spec.local?.name;
160
+
161
+ if (importName === "ApiProperty" || importName === "ApiPropertyOptional") {
162
+ hasApiPropertyImport = true;
163
+ }
164
+
165
+ if (isSwaggerDecorator(importName)) {
166
+ hasSwaggerImports.push(importName);
167
+ }
168
+ });
169
+
170
+ // Check for Entity files with Swagger imports
171
+ if (isEntityFile && hasSwaggerImports.length > 0) {
172
+ context.report({
173
+ node,
174
+ messageId: "unnecessarySwaggerImportInEntity",
175
+ data: {
176
+ importName: hasSwaggerImports.join(", ")
177
+ }
178
+ });
179
+ }
180
+ }
181
+ },
182
+
183
+ ClassDeclaration(node) {
184
+ if (!node.body || !node.body.body) return;
185
+
186
+ const classProperties = node.body.body.filter(shouldCheckProperty);
187
+
188
+ // For DTO files
189
+ if (isDtoFile) {
190
+ // Check if DTO has properties but no ApiProperty import
191
+ if (classProperties.length > 0 && !hasApiPropertyImport) {
192
+ context.report({
193
+ node,
194
+ messageId: "missingApiPropertyImportInDto",
195
+ fix (fixer) {
196
+ const sourceCode = context.getSourceCode();
197
+ const program = sourceCode.ast;
198
+
199
+ // Find the last import statement
200
+ const imports = program.body.filter(stmt => stmt.type === "ImportDeclaration");
201
+
202
+ if (imports.length > 0) {
203
+ const lastImport = imports[imports.length - 1];
204
+ return fixer.insertTextAfter(
205
+ lastImport,
206
+ '\nimport { ApiProperty } from "@nestjs/swagger";'
207
+ );
208
+ } else {
209
+ // Insert at the beginning of the file
210
+ return fixer.insertTextBefore(
211
+ program.body[0],
212
+ 'import { ApiProperty } from "@nestjs/swagger";\n\n'
213
+ );
214
+ }
215
+ },
216
+ });
217
+ }
218
+
219
+ // Check each property for @ApiProperty
220
+ classProperties.forEach(member => {
221
+ if (!hasApiPropertyDecorator(member.decorators)) {
222
+ const propertyName = member.key?.name || "unknown";
223
+
224
+ context.report({
225
+ node: member,
226
+ messageId: "missingApiPropertyInDto",
227
+ data: {
228
+ propertyName: propertyName
229
+ }
230
+ });
231
+ } else {
232
+ // Check existing ApiProperty decorators for German descriptions
233
+ member.decorators?.forEach(decorator => {
234
+ const decoratorName = decorator.expression?.name || decorator.expression?.callee?.name;
235
+ if (decoratorName === "ApiProperty" || decoratorName === "ApiPropertyOptional") {
236
+ const propertyName = member.key?.name || "unknown";
237
+ checkApiPropertyDescription(decorator, propertyName);
238
+ }
239
+ });
240
+ }
241
+ });
242
+ }
243
+
244
+ // For Entity files
245
+ if (isEntityFile) {
246
+ classProperties.forEach(member => {
247
+ const swaggerDecorators = getSwaggerDecorators(member.decorators);
248
+ if (swaggerDecorators.length > 0) {
249
+ const propertyName = member.key?.name || "unknown";
250
+
251
+ context.report({
252
+ node: member,
253
+ messageId: "forbiddenSwaggerInEntity",
254
+ data: {
255
+ propertyName: propertyName,
256
+ decoratorName: swaggerDecorators.join(", ")
257
+ }
258
+ });
259
+ }
260
+ });
261
+ }
262
+ }
263
+ };
264
+ }
265
+ };
@@ -0,0 +1,352 @@
1
+ /**
2
+ * ESLint-Regel: Prüft korrekte Typ-Übereinstimmung zwischen Entity und DTO Properties
3
+ * Spezifisch für Entity-DTO-Mappings:
4
+ * - ClassTranslationEntity[] -> ClassTranslationEntityDto[]
5
+ * - MonsterEntity[] -> MonsterEntityDto[]
6
+ * - etc.
7
+ */
8
+
9
+ import fs from "fs";
10
+ import path from "path";
11
+
12
+ /** @type {import('eslint').Rule.RuleModule} */
13
+ const dtoEntityTypeConsistencyRule = {
14
+ meta: {
15
+ type: "problem",
16
+ docs: {
17
+ description: "Entity-DTO Properties müssen korrekte DTO-Typen für Relations haben",
18
+ category: "Architecture",
19
+ recommended: true,
20
+ },
21
+ hasSuggestions: true,
22
+ schema: [],
23
+ messages: {
24
+ incorrectRelationType: "Entity-DTO '{{dtoName}}' Property '{{prop}}' hat falschen Relation-Typ: DTO hat '{{dtoType}}', Entity hat '{{entityType}}'. Erwartet: {{expectedDtoType}}",
25
+ missingRelationDto: "Entity-DTO '{{dtoName}}' Property '{{prop}}' verwendet Entity-Typ '{{entityType}}' statt DTO-Typ '{{expectedDtoType}}'",
26
+ },
27
+ },
28
+
29
+ create(context) {
30
+ return {
31
+ ClassDeclaration(node) {
32
+ const filename = context.getFilename();
33
+
34
+ // Nur für DTO-Dateien anwenden
35
+ if (!filename.includes('/dto/') && !filename.endsWith('Dto.ts')) {
36
+ return;
37
+ }
38
+
39
+ const dtoName = node.id.name;
40
+ if (!dtoName) return;
41
+
42
+ // Ausnahme: CreationDataDto-Klassen verwenden Entity-Typen für Entity-Erstellung
43
+ if (dtoName.includes('CreationData') || dtoName.includes('CreateData')) {
44
+ return;
45
+ }
46
+
47
+ // Prüfe DTO-Properties direkt
48
+ node.body.body.forEach(member => {
49
+ if (member.type === 'PropertyDefinition' && member.key.type === 'Identifier') {
50
+ const propName = member.key.name;
51
+ const dtoType = getTypeString(member.typeAnnotation?.typeAnnotation);
52
+
53
+ // Prüfe, ob es sich um eine Entity-Relation handelt (ohne EntityDto)
54
+ // Enums sind erlaubt (z.B. EntityTargetType, MovementPatternType)
55
+ if (isEntityRelationType(dtoType)) {
56
+ const expectedDtoType = getExpectedDtoType(dtoType);
57
+
58
+ context.report({
59
+ node: member,
60
+ messageId: 'missingRelationDto',
61
+ data: {
62
+ dtoName,
63
+ prop: propName,
64
+ dtoType,
65
+ entityType: dtoType,
66
+ expectedDtoType
67
+ }
68
+ });
69
+ }
70
+ }
71
+ });
72
+ }
73
+ };
74
+
75
+ function findEntityFile(dtoName) {
76
+ let entityName = dtoName;
77
+ if (entityName.endsWith("DtoEntityDto")) {
78
+ entityName = entityName.replace("DtoEntityDto", "Entity");
79
+ } else if (entityName.endsWith("EntityDto")) {
80
+ entityName = entityName.replace("EntityDto", "Entity");
81
+ } else if (entityName.endsWith("Dto")) {
82
+ entityName = entityName.replace("Dto", "Entity");
83
+ }
84
+
85
+ const currentFile = context.getFilename();
86
+ const srcIndex = currentFile.indexOf('/src/');
87
+ const testIndex = currentFile.indexOf('/__tests__/');
88
+
89
+ if (srcIndex === -1 && testIndex === -1) {
90
+ return null;
91
+ }
92
+
93
+ let entityDir;
94
+ if (srcIndex !== -1) {
95
+ const srcPath = currentFile.substring(0, srcIndex + 5);
96
+ entityDir = path.resolve(srcPath, "entity");
97
+ } else {
98
+ // Für Test-Fixtures: Suche nach Entity-Dateien in der Nähe
99
+ const testPath = currentFile.substring(0, testIndex);
100
+ entityDir = path.resolve(testPath, "src", "entity");
101
+
102
+ // Fallback: Suche in den Fixtures selbst
103
+ if (!fs.existsSync(entityDir)) {
104
+ entityDir = path.resolve(testPath, "__tests__", "fixtures");
105
+ }
106
+ }
107
+
108
+ try {
109
+ const entityFile = findFileRecursively(entityDir, entityName + ".ts");
110
+ if (entityFile) {
111
+ return entityFile;
112
+ }
113
+ } catch {
114
+ // Entity-Verzeichnis existiert nicht
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ function findFileRecursively(dir, filename) {
121
+ if (!fs.existsSync(dir)) {
122
+ return null;
123
+ }
124
+
125
+ const files = fs.readdirSync(dir);
126
+ for (const file of files) {
127
+ const filePath = path.join(dir, file);
128
+ const stat = fs.statSync(filePath);
129
+
130
+ if (stat.isDirectory()) {
131
+ const result = findFileRecursively(filePath, filename);
132
+ if (result) {
133
+ return result;
134
+ }
135
+ } else if (file === filename) {
136
+ return filePath;
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+
142
+ function extractEntityRelations(entityContent) {
143
+ const relations = new Map();
144
+
145
+ // Suche nach @OneToMany, @ManyToOne, @OneToOne, @ManyToMany Dekoratoren
146
+ const relationPatterns = [
147
+ /@OneToMany\s*\(\s*\(\)\s*=>\s*(\w+)/g,
148
+ /@ManyToOne\s*\(\s*\(\)\s*=>\s*(\w+)/g,
149
+ /@OneToOne\s*\(\s*\(\)\s*=>\s*(\w+)/g,
150
+ /@ManyToMany\s*\(\s*\(\)\s*=>\s*(\w+)/g,
151
+ ];
152
+
153
+ const lines = entityContent.split('\n');
154
+
155
+ for (let i = 0; i < lines.length; i++) {
156
+ const line = lines[i].trim();
157
+
158
+ // Suche nach Relation-Dekoratoren
159
+ for (const pattern of relationPatterns) {
160
+ const match = pattern.exec(line);
161
+ if (match) {
162
+ const relationEntityName = match[1];
163
+
164
+ // Suche nach der Property-Definition nach dem Dekorator
165
+ let j = i + 1;
166
+ while (j < lines.length && !lines[j].trim().match(/^\w+:\s*[^;]+;$/)) {
167
+ j++;
168
+ }
169
+
170
+ if (j < lines.length) {
171
+ const propLine = lines[j].trim();
172
+ const propMatch = propLine.match(/^(\w+):\s*([^;]+);$/);
173
+
174
+ if (propMatch) {
175
+ const propName = propMatch[1];
176
+ const propType = propMatch[2].trim();
177
+
178
+ relations.set(propName, {
179
+ entityType: relationEntityName,
180
+ fullType: propType
181
+ });
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ return relations;
189
+ }
190
+
191
+ function extractDtoProperties(classNode) {
192
+ const properties = new Map();
193
+
194
+ classNode.body.body.forEach(member => {
195
+ if (member.type === "PropertyDefinition" && member.key?.name) {
196
+ const propName = member.key.name;
197
+ let propType = "unknown";
198
+
199
+ if (member.typeAnnotation?.typeAnnotation) {
200
+ propType = getTypeString(member.typeAnnotation.typeAnnotation);
201
+ }
202
+
203
+ properties.set(propName, propType);
204
+ }
205
+ });
206
+
207
+ return properties;
208
+ }
209
+
210
+ function getTypeString(typeNode) {
211
+ if (!typeNode) {
212
+ return "unknown";
213
+ }
214
+ switch (typeNode.type) {
215
+ case "TSStringKeyword":
216
+ return "string";
217
+ case "TSNumberKeyword":
218
+ return "number";
219
+ case "TSBooleanKeyword":
220
+ return "boolean";
221
+ case "TSNullKeyword":
222
+ return "null";
223
+ case "TSUndefinedKeyword":
224
+ return "undefined";
225
+ case "TSTypeReference":
226
+ return typeNode.typeName?.name || "unknown";
227
+ case "TSUnionType":
228
+ return typeNode.types.map(getTypeString).join(" | ");
229
+ case "TSArrayType":
230
+ return getTypeString(typeNode.elementType) + "[]";
231
+ case "TSTypeLiteral":
232
+ return "object";
233
+ default:
234
+ return "unknown";
235
+ }
236
+ }
237
+
238
+ function isEntityRelationType(type) {
239
+ // Prüfe, ob der Typ eine Entity-Relation ist
240
+ // Enums sind erlaubt (z.B. EntityTargetType, MovementPatternType)
241
+ if (type.includes("Type") || type.includes("Enum")) {
242
+ return false;
243
+ }
244
+ return type.includes("Entity") && !type.includes("EntityDto");
245
+ }
246
+
247
+ function getExpectedDtoType(entityType) {
248
+ // Transformiere Entity-Typ zu DTO-Typ
249
+ if (entityType.endsWith("Entity")) {
250
+ return entityType.replace("Entity", "EntityDto");
251
+ }
252
+ return entityType + "Dto";
253
+ }
254
+
255
+ function isCorrectDtoType(dtoType, expectedEntityType) {
256
+ const expectedDtoType = getExpectedDtoType(expectedEntityType);
257
+
258
+ // Prüfe verschiedene Varianten
259
+ const variants = [
260
+ expectedDtoType,
261
+ expectedDtoType + "[]",
262
+ expectedDtoType + " | null",
263
+ expectedDtoType + "[] | null"
264
+ ];
265
+
266
+ return variants.some(variant => dtoType === variant);
267
+ }
268
+
269
+ // Speichere DTO-Informationen für spätere Verwendung
270
+ const dtoInfoMap = new Map();
271
+
272
+ return {
273
+ ClassDeclaration(node) {
274
+ const filename = context.getFilename();
275
+ if (!filename.includes("/dto/Entity/")) return;
276
+
277
+ const dtoName = node.id.name;
278
+ const entityFile = findEntityFile(dtoName);
279
+
280
+ if (!entityFile) return;
281
+
282
+ // Parse Entity-Datei und extrahiere Relations
283
+ const entityContent = fs.readFileSync(entityFile, "utf8");
284
+ const entityRelations = extractEntityRelations(entityContent);
285
+ const dtoProperties = extractDtoProperties(node);
286
+
287
+ dtoInfoMap.set(dtoName, {
288
+ entityRelations,
289
+ dtoProperties,
290
+ entityName: path.basename(entityFile, ".ts")
291
+ });
292
+ },
293
+
294
+ NewExpression(node) {
295
+ if (node.callee.type !== "Identifier") return;
296
+
297
+ const dtoName = node.callee.name;
298
+ const dtoInfo = dtoInfoMap.get(dtoName);
299
+
300
+ if (!dtoInfo) return;
301
+
302
+ const { entityRelations, dtoProperties } = dtoInfo;
303
+
304
+ // Prüfe jede Entity-Relation
305
+ for (const [propName, relationInfo] of entityRelations) {
306
+ const dtoType = dtoProperties.get(propName);
307
+
308
+ if (!dtoType) continue;
309
+
310
+ const { entityType } = relationInfo;
311
+ const expectedDtoType = getExpectedDtoType(entityType);
312
+
313
+ // Prüfe, ob DTO-Typ korrekt ist
314
+ if (!isCorrectDtoType(dtoType, entityType)) {
315
+ // Spezielle Behandlung: Wenn DTO-Typ Entity-Typ verwendet statt DTO-Typ
316
+ if (isEntityRelationType(dtoType)) {
317
+ context.report({
318
+ node,
319
+ messageId: "missingRelationDto",
320
+ data: {
321
+ dtoName,
322
+ prop: propName,
323
+ dtoType,
324
+ entityType,
325
+ expectedDtoType
326
+ }
327
+ });
328
+ } else {
329
+ context.report({
330
+ node,
331
+ messageId: "incorrectRelationType",
332
+ data: {
333
+ dtoName,
334
+ prop: propName,
335
+ dtoType,
336
+ entityType,
337
+ expectedDtoType
338
+ }
339
+ });
340
+ }
341
+ }
342
+ }
343
+ }
344
+ };
345
+ },
346
+ };
347
+
348
+ export default {
349
+ rules: {
350
+ "dto-entity-type-consistency": dtoEntityTypeConsistencyRule,
351
+ },
352
+ };