@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,217 @@
1
+ /**
2
+ * @fileoverview Enforce that Request DTOs have required fields for properties that are required in the corresponding Entity DTO
3
+ * @author Echoes of Order Team
4
+ */
5
+
6
+ import fs from "fs";
7
+ import path from "path";
8
+
9
+ /** @type {import('eslint').Rule.RuleModule} */
10
+ const enforceRequestDtoRequiredFieldsRule = {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description: "Request DTOs must not have optional properties if they are required in the corresponding Entity DTO",
15
+ category: "Architecture",
16
+ recommended: true,
17
+ },
18
+ fixable: null,
19
+ schema: [],
20
+ messages: {
21
+ optionalPropertyInRequestDto: "Property '{{propertyName}}' is optional in Request DTO but required in Entity DTO '{{entityDtoName}}'. Make it required: {{propertyName}}!: {{propertyType}}",
22
+ },
23
+ },
24
+
25
+ create(context) {
26
+ const filename = context.getFilename();
27
+
28
+ // Nur auf Request DTOs anwenden
29
+ if (!filename.includes("/dto/Request/") || !filename.endsWith("RequestDto.ts")) {
30
+ return {};
31
+ }
32
+
33
+ function getRequestDtoName(filename) {
34
+ return path.basename(filename, ".ts");
35
+ }
36
+
37
+ function getCorrespondingEntityDtoPath(filename) {
38
+ const requestDtoName = getRequestDtoName(filename);
39
+ const entityDtoName = requestDtoName.replace(/RequestDto$/, "EntityDto");
40
+
41
+ // Suche nach der Entity DTO im gleichen Verzeichnis oder in /dto/Entity/
42
+ const baseDir = path.dirname(filename);
43
+ const projectRoot = filename.substring(0, filename.indexOf("/dto/"));
44
+
45
+ // Mögliche Pfade für Entity DTOs
46
+ const possiblePaths = [
47
+ path.join(baseDir, `${entityDtoName}.ts`),
48
+ path.join(projectRoot, "dto", "Entity", `${entityDtoName}.ts`),
49
+ // Weitere mögliche Unterverzeichnisse
50
+ ...findEntityDtoInSubdirectories(path.join(projectRoot, "dto", "Entity"), entityDtoName),
51
+ ];
52
+
53
+ for (const possiblePath of possiblePaths) {
54
+ if (fs.existsSync(possiblePath)) {
55
+ return possiblePath;
56
+ }
57
+ }
58
+
59
+ return null;
60
+ }
61
+
62
+ function findEntityDtoInSubdirectories(baseDir, entityDtoName) {
63
+ const results = [];
64
+
65
+ if (!fs.existsSync(baseDir)) {
66
+ return results;
67
+ }
68
+
69
+ function traverse(dir) {
70
+ try {
71
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
72
+
73
+ for (const entry of entries) {
74
+ const fullPath = path.join(dir, entry.name);
75
+
76
+ if (entry.isDirectory()) {
77
+ traverse(fullPath);
78
+ } else if (entry.isFile() && entry.name === `${entityDtoName}.ts`) {
79
+ results.push(fullPath);
80
+ }
81
+ }
82
+ } catch (error) {
83
+ // Ignoriere Fehler beim Durchsuchen
84
+ }
85
+ }
86
+
87
+ traverse(baseDir);
88
+ return results;
89
+ }
90
+
91
+ function getEntityDtoProperties(entityDtoPath) {
92
+ try {
93
+ const content = fs.readFileSync(entityDtoPath, "utf-8");
94
+ const lines = content.split("\n");
95
+ const properties = {};
96
+
97
+ let inClassBody = false;
98
+ const AUTO_MANAGED_FIELDS = ["id", "createdAt", "updatedAt"];
99
+
100
+ for (const line of lines) {
101
+ if (line.includes("export default class") || line.includes("export class")) {
102
+ inClassBody = true;
103
+ continue;
104
+ }
105
+
106
+ if (inClassBody && line.trim() === "}") {
107
+ break;
108
+ }
109
+
110
+ if (inClassBody && !line.includes("static ") && !line.trim().startsWith("@")) {
111
+ const propertyMatch = line.match(/^\s+(\w+)(\?)?:\s+([^;]+);/);
112
+ if (propertyMatch) {
113
+ const propertyName = propertyMatch[1];
114
+ const isOptional = propertyMatch[2] === "?";
115
+ const propertyType = propertyMatch[3].trim();
116
+
117
+ // Ignoriere auto-managed fields
118
+ if (!AUTO_MANAGED_FIELDS.includes(propertyName) && !propertyName.startsWith("_")) {
119
+ properties[propertyName] = {
120
+ isOptional,
121
+ type: propertyType,
122
+ };
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ return properties;
129
+ } catch (error) {
130
+ return {};
131
+ }
132
+ }
133
+
134
+ function getRequestDtoProperties(classNode) {
135
+ const properties = {};
136
+
137
+ if (!classNode || !classNode.body) {
138
+ return properties;
139
+ }
140
+
141
+ for (const member of classNode.body.body) {
142
+ if (member.type === "PropertyDefinition" || member.type === "ClassProperty") {
143
+ const key = member.key;
144
+ if (key && key.type === "Identifier") {
145
+ const propertyName = key.name;
146
+ const isOptional = member.optional === true;
147
+
148
+ let propertyType = "unknown";
149
+ if (member.typeAnnotation && member.typeAnnotation.typeAnnotation) {
150
+ const typeNode = member.typeAnnotation.typeAnnotation;
151
+ if (typeNode.type === "TSTypeReference" && typeNode.typeName) {
152
+ propertyType = typeNode.typeName.name;
153
+ } else if (typeNode.type === "TSStringKeyword") {
154
+ propertyType = "string";
155
+ } else if (typeNode.type === "TSNumberKeyword") {
156
+ propertyType = "number";
157
+ } else if (typeNode.type === "TSBooleanKeyword") {
158
+ propertyType = "boolean";
159
+ }
160
+ }
161
+
162
+ if (!propertyName.startsWith("_") && propertyName !== "constructor") {
163
+ properties[propertyName] = {
164
+ isOptional,
165
+ type: propertyType,
166
+ node: member,
167
+ };
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ return properties;
174
+ }
175
+
176
+ return {
177
+ ClassDeclaration(node) {
178
+ const entityDtoPath = getCorrespondingEntityDtoPath(filename);
179
+
180
+ if (!entityDtoPath) {
181
+ // Keine korrespondierende Entity DTO gefunden - kein Fehler
182
+ return;
183
+ }
184
+
185
+ const entityDtoProperties = getEntityDtoProperties(entityDtoPath);
186
+ const requestDtoProperties = getRequestDtoProperties(node);
187
+
188
+ // Vergleiche Properties
189
+ for (const [propertyName, requestProp] of Object.entries(requestDtoProperties)) {
190
+ const entityProp = entityDtoProperties[propertyName];
191
+
192
+ if (entityProp) {
193
+ // Property existiert in beiden DTOs
194
+ if (requestProp.isOptional && !entityProp.isOptional) {
195
+ // Property ist im Request DTO optional, aber in Entity DTO required
196
+ context.report({
197
+ node: requestProp.node,
198
+ messageId: "optionalPropertyInRequestDto",
199
+ data: {
200
+ propertyName,
201
+ entityDtoName: getRequestDtoName(filename).replace(/RequestDto$/, "EntityDto"),
202
+ propertyType: requestProp.type,
203
+ },
204
+ });
205
+ }
206
+ }
207
+ }
208
+ },
209
+ };
210
+ },
211
+ };
212
+
213
+ export default {
214
+ rules: {
215
+ "enforce-request-dto-required-fields": enforceRequestDtoRequiredFieldsRule,
216
+ },
217
+ };
@@ -0,0 +1,45 @@
1
+ export default {
2
+ meta: {
3
+ type: "suggestion",
4
+ docs: {
5
+ description: "Enforce consistent Result pattern usage",
6
+ category: "Best Practices",
7
+ recommended: true,
8
+ },
9
+ fixable: null,
10
+ schema: [],
11
+ },
12
+ create(context) {
13
+ return {
14
+ ImportDeclaration(node) {
15
+ if (node.source.value === "@/dto/Result") {
16
+ const specifiers = node.specifiers;
17
+ const hasDefaultImport = specifiers.some(spec => spec.type === "ImportDefaultSpecifier");
18
+ const hasNamedImport = specifiers.some(spec => spec.type === "ImportSpecifier" && spec.imported.name === "Result");
19
+
20
+ if (hasDefaultImport && hasNamedImport) {
21
+ context.report({
22
+ node,
23
+ message: "Duplicate Result import detected. Use either default or named import, not both.",
24
+ fix(fixer) {
25
+ return fixer.replaceText(node, 'import { Result } from "@/dto/Result";');
26
+ }
27
+ });
28
+ }
29
+ }
30
+ },
31
+
32
+ CallExpression(node) {
33
+ if (node.callee.type === "Identifier" && node.callee.name === "Result") {
34
+ const args = node.arguments;
35
+ if (args.length === 0) {
36
+ context.report({
37
+ node,
38
+ message: "Result constructor requires at least one argument."
39
+ });
40
+ }
41
+ }
42
+ }
43
+ };
44
+ }
45
+ };
@@ -0,0 +1,116 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Enforce services load relations with load*EntityDto() methods",
6
+ category: "Best Practices",
7
+ recommended: true,
8
+ },
9
+ messages: {
10
+ useLoadEntityDtoMethods: "Relations müssen mit private async load*EntityDto() Methoden geladen werden. Pattern: loadQualityEntityDto(), loadBindingEntityDto(), etc.",
11
+ convertEntityToDto: "Entities müssen mit EntityDto.fromEntity() zu EntityDto konvertiert werden. Direkte Entity-Zuweisung ohne DTO-Konvertierung ist nicht erlaubt.",
12
+ missingLoadMethod: "Service sollte eine private async load{{relationName}}EntityDto() Methode haben für das Laden von {{relationName}} Relations.",
13
+ },
14
+ schema: [],
15
+ },
16
+ create (context) {
17
+ const filename = context.getFilename();
18
+
19
+ if (!filename.includes("/service/") && !filename.endsWith("Service.ts")) {
20
+ return {};
21
+ }
22
+
23
+ let hasFromRequestDtoCall = false;
24
+ const relationAssignments = [];
25
+ const loadEntityDtoVariables = new Set();
26
+
27
+ return {
28
+ CallExpression (node) {
29
+ if (
30
+ node.callee.type === "MemberExpression" &&
31
+ node.callee.property.name === "fromRequestDto" &&
32
+ node.callee.object.name?.endsWith("EntityDto")
33
+ ) {
34
+ hasFromRequestDtoCall = true;
35
+ }
36
+ },
37
+ VariableDeclarator (node) {
38
+ if (
39
+ node.init &&
40
+ node.init.type === "AwaitExpression" &&
41
+ node.init.argument &&
42
+ node.init.argument.type === "CallExpression" &&
43
+ node.init.argument.callee.type === "MemberExpression" &&
44
+ node.init.argument.callee.property.name.startsWith("load") &&
45
+ node.init.argument.callee.property.name.endsWith("EntityDto")
46
+ ) {
47
+ if (node.id.type === "Identifier") {
48
+ loadEntityDtoVariables.add(node.id.name);
49
+ }
50
+ }
51
+ },
52
+ ArrayPattern (node) {
53
+ const parent = node.parent;
54
+ if (
55
+ parent &&
56
+ parent.type === "VariableDeclarator" &&
57
+ parent.init &&
58
+ parent.init.type === "AwaitExpression" &&
59
+ parent.init.argument &&
60
+ parent.init.argument.type === "CallExpression" &&
61
+ parent.init.argument.callee.type === "MemberExpression" &&
62
+ parent.init.argument.callee.object.name === "Promise" &&
63
+ parent.init.argument.callee.property.name === "all"
64
+ ) {
65
+ node.elements.forEach((element) => {
66
+ if (element && element.type === "Identifier") {
67
+ loadEntityDtoVariables.add(element.name);
68
+ }
69
+ });
70
+ }
71
+ },
72
+ AssignmentExpression (node) {
73
+ if (
74
+ hasFromRequestDtoCall &&
75
+ node.left.type === "MemberExpression" &&
76
+ node.left.object.name?.toLowerCase().includes("dto")
77
+ ) {
78
+ const propertyName = node.left.property.name;
79
+ const rightValue = node.right;
80
+
81
+ const relationKeywords = [
82
+ "quality",
83
+ "binding",
84
+ "itemClass",
85
+ "itemSubclass",
86
+ "inventoryType",
87
+ "type",
88
+ "parent",
89
+ "child",
90
+ ];
91
+
92
+ if (relationKeywords.some((keyword) => propertyName.toLowerCase().includes(keyword))) {
93
+ if (
94
+ rightValue.type === "Identifier" &&
95
+ !loadEntityDtoVariables.has(rightValue.name)
96
+ ) {
97
+ relationAssignments.push({
98
+ node,
99
+ propertyName,
100
+ });
101
+ }
102
+ }
103
+ }
104
+ },
105
+ "Program:exit" () {
106
+ for (const { node } of relationAssignments) {
107
+ context.report({
108
+ messageId: "convertEntityToDto",
109
+ node,
110
+ });
111
+ }
112
+ },
113
+ };
114
+ },
115
+ };
116
+
@@ -0,0 +1,96 @@
1
+ /**
2
+ * ESLint-Regel: enforce-test-coverage
3
+ * Stellt sicher, dass alle Services und Controller entsprechende Test-Dateien haben
4
+ */
5
+
6
+ import fs from "fs";
7
+ import path from "path";
8
+
9
+ /** @type {import('eslint').Rule.RuleModule} */
10
+ const enforceTestCoverageRule = {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description: "Alle Services und Controller müssen entsprechende Test-Dateien haben",
15
+ category: "Testing",
16
+ recommended: true,
17
+ },
18
+ schema: [],
19
+ messages: {
20
+ missingTestFile: "{{fileType}} '{{className}}' hat keine entsprechende Test-Datei. Erwarte: {{expectedTestPath}}",
21
+ testFileExists: "Test-Datei gefunden: {{testPath}}",
22
+ },
23
+ },
24
+ create(context) {
25
+ const filename = context.getFilename();
26
+
27
+ // Nur Services und Controller prüfen
28
+ const isServiceFile = filename.includes("/service/") && filename.endsWith(".ts") && !filename.includes("test");
29
+ const isControllerFile = filename.includes("/controller/") && filename.endsWith("Controller.ts") && !filename.includes("test");
30
+
31
+ if (!isServiceFile && !isControllerFile) return {};
32
+
33
+ function findTestFile(originalFilePath) {
34
+ const dir = path.dirname(originalFilePath);
35
+ const baseName = path.basename(originalFilePath, ".ts");
36
+
37
+ // Mögliche Test-Datei-Patterns
38
+ const testPatterns = [
39
+ path.join(dir, "__tests__", `${baseName}.test.ts`),
40
+ path.join(dir, "__tests__", `${baseName}.spec.ts`),
41
+ path.join(dir, `${baseName}.test.ts`),
42
+ path.join(dir, `${baseName}.spec.ts`),
43
+ ];
44
+
45
+ for (const testPath of testPatterns) {
46
+ try {
47
+ if (fs.existsSync(testPath)) {
48
+ return testPath;
49
+ }
50
+ } catch (error) {
51
+ // Datei nicht gefunden, weiter suchen
52
+ }
53
+ }
54
+
55
+ return null;
56
+ }
57
+
58
+ return {
59
+ ClassDeclaration(node) {
60
+ const className = node.id?.name;
61
+ if (!className) return;
62
+
63
+ // Bestimme Dateityp
64
+ let fileType = "Class";
65
+ if (className.endsWith("Service")) {
66
+ fileType = "Service";
67
+ } else if (className.endsWith("Controller")) {
68
+ fileType = "Controller";
69
+ }
70
+
71
+ // Nur Services und Controller prüfen
72
+ if (fileType === "Class") return;
73
+
74
+ const testFile = findTestFile(filename);
75
+
76
+ if (!testFile) {
77
+ const dir = path.dirname(filename);
78
+ const baseName = path.basename(filename, ".ts");
79
+ const expectedTestPath = path.join(dir, "__tests__", `${baseName}.test.ts`);
80
+
81
+ context.report({
82
+ node,
83
+ messageId: "missingTestFile",
84
+ data: {
85
+ fileType,
86
+ className,
87
+ expectedTestPath: path.relative(process.cwd(), expectedTestPath),
88
+ },
89
+ });
90
+ }
91
+ },
92
+ };
93
+ },
94
+ };
95
+
96
+ export default enforceTestCoverageRule;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @fileoverview Enforce conditional assignment for id, createdAt, updatedAt in toEntity methods
3
+ * @author Echoes of Order Team
4
+ */
5
+
6
+ /** @type {import('eslint').Rule.RuleModule} */
7
+ const enforceToEntityConditionalAssignmentRule = {
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "id, createdAt, and updatedAt must be set conditionally in toEntity methods (if (dto.field) { entity.field = dto.field; })",
12
+ category: "Architecture",
13
+ recommended: true,
14
+ },
15
+ fixable: "code",
16
+ schema: [],
17
+ messages: {
18
+ mustUseConditional: "Property '{{fieldName}}' in toEntity() must be set conditionally: if (dto.{{fieldName}}) { entity.{{fieldName}} = dto.{{fieldName}}; }",
19
+ },
20
+ },
21
+ create(context) {
22
+ const AUTO_MANAGED_FIELDS = ["id", "createdAt", "updatedAt"];
23
+
24
+ function isToEntityMethod(node) {
25
+ return (
26
+ (node.type === "MethodDefinition" || node.type === "Property") &&
27
+ node.key?.name === "toEntity" &&
28
+ node.static === true
29
+ );
30
+ }
31
+
32
+ function isDirectAssignment(node, fieldName) {
33
+ if (
34
+ node.type === "ExpressionStatement" &&
35
+ node.expression?.type === "AssignmentExpression"
36
+ ) {
37
+ const left = node.expression.left;
38
+ const right = node.expression.right;
39
+
40
+ if (
41
+ left?.type === "MemberExpression" &&
42
+ left.object?.name === "entity" &&
43
+ left.property?.name === fieldName &&
44
+ right?.type === "MemberExpression" &&
45
+ right.object?.name === "dto" &&
46
+ right.property?.name === fieldName
47
+ ) {
48
+ return true;
49
+ }
50
+ }
51
+ return false;
52
+ }
53
+
54
+ function isConditionalAssignment(node, fieldName) {
55
+ if (node.type === "IfStatement") {
56
+ const test = node.test;
57
+ if (
58
+ test?.type === "MemberExpression" &&
59
+ test.object?.name === "dto" &&
60
+ test.property?.name === fieldName
61
+ ) {
62
+ const body = node.consequent?.body || [node.consequent];
63
+ const hasCorrectAssignment = body.some((stmt) => {
64
+ if (
65
+ stmt.type === "ExpressionStatement" &&
66
+ stmt.expression?.type === "AssignmentExpression"
67
+ ) {
68
+ const left = stmt.expression.left;
69
+ const right = stmt.expression.right;
70
+ return (
71
+ left?.type === "MemberExpression" &&
72
+ left.object?.name === "entity" &&
73
+ left.property?.name === fieldName &&
74
+ right?.type === "MemberExpression" &&
75
+ right.object?.name === "dto" &&
76
+ right.property?.name === fieldName
77
+ );
78
+ }
79
+ return false;
80
+ });
81
+ return hasCorrectAssignment;
82
+ }
83
+ }
84
+ return false;
85
+ }
86
+
87
+ return {
88
+ MethodDefinition(node) {
89
+ if (!isToEntityMethod(node)) {
90
+ return;
91
+ }
92
+
93
+ const body = node.value?.body?.body;
94
+ if (!body || !Array.isArray(body)) {
95
+ return;
96
+ }
97
+
98
+ AUTO_MANAGED_FIELDS.forEach((fieldName) => {
99
+ let hasDirectAssignment = false;
100
+ let hasConditionalAssignment = false;
101
+
102
+ body.forEach((statement) => {
103
+ if (isDirectAssignment(statement, fieldName)) {
104
+ hasDirectAssignment = true;
105
+ context.report({
106
+ node: statement,
107
+ messageId: "mustUseConditional",
108
+ data: { fieldName },
109
+ fix(fixer) {
110
+ const assignmentText = `if (dto.${fieldName}) {\n entity.${fieldName} = dto.${fieldName};\n }`;
111
+ return fixer.replaceText(statement, assignmentText);
112
+ },
113
+ });
114
+ }
115
+
116
+ if (isConditionalAssignment(statement, fieldName)) {
117
+ hasConditionalAssignment = true;
118
+ }
119
+ });
120
+ });
121
+ },
122
+ };
123
+ },
124
+ };
125
+
126
+ export default {
127
+ rules: {
128
+ "enforce-toentity-conditional-assignment": enforceToEntityConditionalAssignmentRule,
129
+ },
130
+ };
131
+
132
+