@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,180 @@
1
+ /**
2
+ * @fileoverview Enforce that Entities can only be initialized in DTO methods (fromEntity, fromEntityArray, toEntity, fromRequestDto)
3
+ * @author Echoes of Order Team
4
+ */
5
+
6
+ //------------------------------------------------------------------------------
7
+ // Rule Definition
8
+ //------------------------------------------------------------------------------
9
+
10
+ /** @type {import('eslint').Rule.RuleModule} */
11
+ export default {
12
+ meta: {
13
+ type: "problem",
14
+ docs: {
15
+ description: "Entities dürfen nur in DTO-Methoden manuell initialisiert werden (fromEntity, fromEntityArray, toEntity, fromRequestDto)",
16
+ category: "Architecture",
17
+ recommended: true,
18
+ },
19
+ fixable: null,
20
+ schema: [],
21
+ messages: {
22
+ entityInitializationForbidden: "Manuelle Entity-Initialisierung 'new {{entityName}}()' ist nur in DTO-Methoden erlaubt: fromEntity, fromEntityArray, toEntity, fromRequestDto",
23
+ entityPropertyAssignmentForbidden: "Manuelle Property-Zuweisung an Entity '{{entityName}}' ist nur in DTO-Methoden erlaubt: fromEntity, fromEntityArray, toEntity, fromRequestDto",
24
+ useRepositoryCreate: "Verwende repository.create() oder DTO-Methoden statt manueller Entity-Initialisierung",
25
+ },
26
+ },
27
+
28
+ create(context) {
29
+ //--------------------------------------------------------------------------
30
+ // Helpers
31
+ //--------------------------------------------------------------------------
32
+
33
+ const filename = context.getFilename();
34
+ const isServiceFile = (filename.includes("/service/") && filename.endsWith(".ts") && !filename.includes("test")) || filename.includes("test-fixtures");
35
+ const isDtoFile = filename.includes("/dto/") && filename.endsWith(".ts");
36
+ const isEntityFile = filename.includes("/entity/") && filename.endsWith(".ts");
37
+ const isTestFixture = filename.includes("test-fixtures");
38
+ const isInvalidTestFixture = filename.includes("InvalidEntityFactoryPattern");
39
+
40
+ /**
41
+ * Check if we are in a fromEntity method
42
+ * @param {ASTNode} node - The current node
43
+ * @returns {boolean} True if in fromEntity method
44
+ */
45
+ function isInFromEntityMethod(node) {
46
+ let current = node;
47
+ while (current && current.parent) {
48
+ if (current.type === "MethodDefinition" &&
49
+ current.static === true &&
50
+ current.key?.name === "fromEntity") {
51
+ return true;
52
+ }
53
+ current = current.parent;
54
+ }
55
+ return false;
56
+ }
57
+
58
+ /**
59
+ * Check if we are in a fromEntityArray method
60
+ * @param {ASTNode} node - The current node
61
+ * @returns {boolean} True if in fromEntityArray method
62
+ */
63
+ function isInFromEntityArrayMethod(node) {
64
+ let current = node;
65
+ while (current && current.parent) {
66
+ if (current.type === "MethodDefinition" &&
67
+ current.static === true &&
68
+ current.key?.name === "fromEntityArray") {
69
+ return true;
70
+ }
71
+ current = current.parent;
72
+ }
73
+ return false;
74
+ }
75
+
76
+ /**
77
+ * Check if we are in a toEntity method
78
+ * @param {ASTNode} node - The current node
79
+ * @returns {boolean} True if in toEntity method
80
+ */
81
+ function isInToEntityMethod(node) {
82
+ let current = node;
83
+ while (current && current.parent) {
84
+ if (current.type === "MethodDefinition" &&
85
+ current.static === true &&
86
+ current.key?.name === "toEntity") {
87
+ return true;
88
+ }
89
+ current = current.parent;
90
+ }
91
+ return false;
92
+ }
93
+
94
+ /**
95
+ * Check if we are in a fromRequestDto method
96
+ * @param {ASTNode} node - The current node
97
+ * @returns {boolean} True if in fromRequestDto method
98
+ */
99
+ function isInFromRequestDtoMethod(node) {
100
+ let current = node;
101
+ while (current && current.parent) {
102
+ if (current.type === "MethodDefinition" &&
103
+ current.static === true &&
104
+ current.key?.name === "fromRequestDto") {
105
+ return true;
106
+ }
107
+ current = current.parent;
108
+ }
109
+ return false;
110
+ }
111
+
112
+ /**
113
+ * Check if we are in a DTO fromEntity method
114
+ * @param {ASTNode} node - The current node
115
+ * @returns {boolean} True if in DTO fromEntity method
116
+ */
117
+ function isInDtoFromEntityMethod(node) {
118
+ return isDtoFile && (isInFromEntityMethod(node) || isInFromEntityArrayMethod(node) || isInToEntityMethod(node) || isInFromRequestDtoMethod(node));
119
+ }
120
+
121
+ //--------------------------------------------------------------------------
122
+ // Public
123
+ //--------------------------------------------------------------------------
124
+
125
+ return {
126
+ // Überwache new Entity() Aufrufe
127
+ NewExpression(node) {
128
+ // Ignoriere Test-Fixtures, außer für InvalidEntityFactoryPattern
129
+ if (isTestFixture && !isInvalidTestFixture) {
130
+ return;
131
+ }
132
+
133
+ const entityName = node.callee?.name;
134
+
135
+ // Prüfe ob es eine Entity ist (Name endet mit "Entity")
136
+ if (entityName && entityName.endsWith("Entity")) {
137
+ // Erlaube nur in DTO fromEntity-Methoden
138
+ if (!isInDtoFromEntityMethod(node)) {
139
+ context.report({
140
+ node,
141
+ messageId: "entityInitializationForbidden",
142
+ data: {
143
+ entityName,
144
+ },
145
+ });
146
+ }
147
+ }
148
+ },
149
+
150
+ // Überwache Property-Zuweisungen an Entities
151
+ AssignmentExpression(node) {
152
+ // Ignoriere Test-Fixtures, außer für InvalidEntityFactoryPattern
153
+ if (isTestFixture && !isInvalidTestFixture) {
154
+ return;
155
+ }
156
+
157
+ if (isServiceFile || isEntityFile) {
158
+ // Prüfe ob links eine Entity-Variable ist (kann auch camelCase sein)
159
+ if (node.left.type === "MemberExpression" &&
160
+ node.left.object?.name &&
161
+ (node.left.object.name.endsWith("Entity") ||
162
+ node.left.object.name.match(/^[a-z][a-zA-Z]*Entity$/))) {
163
+
164
+ // Erlaube nur in DTO fromEntity-Methoden
165
+ if (!isInDtoFromEntityMethod(node)) {
166
+ context.report({
167
+ node,
168
+ messageId: "entityPropertyAssignmentForbidden",
169
+ data: {
170
+ entityName: node.left.object.name,
171
+ },
172
+ });
173
+ }
174
+ }
175
+ }
176
+ },
177
+ };
178
+ },
179
+ };
180
+
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @fileoverview Enforce that Entity instantiation (new Entity()) is only allowed within toEntity methods
3
+ * @author Echoes of Order Team
4
+ */
5
+
6
+ //------------------------------------------------------------------------------
7
+ // Rule Definition
8
+ //------------------------------------------------------------------------------
9
+
10
+ /** @type {import('eslint').Rule.RuleModule} */
11
+ export default {
12
+ rules: {
13
+ "enforce-entity-instantiation-in-toentity": {
14
+ meta: {
15
+ type: "problem",
16
+ docs: {
17
+ description: "Enforce that new Entity() is only allowed within toEntity methods of Entity DTOs",
18
+ category: "Best Practices",
19
+ recommended: true,
20
+ },
21
+ fixable: null,
22
+ schema: [],
23
+ messages: {
24
+ newEntityOutsideToEntity: "new {{entityName}}() is only allowed within the toEntity method of the corresponding Entity DTO. Use repository.create() or DTO methods instead.",
25
+ },
26
+ },
27
+
28
+ create(context) {
29
+ const entityDtos = new Map();
30
+
31
+ /**
32
+ * Check if filename is an Entity DTO file
33
+ */
34
+ function isEntityDtoFile(filename) {
35
+ return (
36
+ (filename.includes("/dto/Entity/") || filename.includes("/test-fixtures/")) &&
37
+ (filename.endsWith("EntityDto.ts") || filename.includes("EntityDto"))
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Get DTO name from Entity name
43
+ */
44
+ function getDtoNameFromEntity(entityName) {
45
+ return entityName.replace("Entity", "EntityDto");
46
+ }
47
+
48
+ /**
49
+ * Check if node is inside a toEntity method
50
+ */
51
+ function isInsideToEntityMethod(node) {
52
+ let parent = node.parent;
53
+ while (parent) {
54
+ if (parent.type === "MethodDefinition" || parent.type === "Property") {
55
+ if (parent.key && parent.key.name === "toEntity") {
56
+ return true;
57
+ }
58
+ }
59
+ parent = parent.parent;
60
+ }
61
+ return false;
62
+ }
63
+
64
+ /**
65
+ * Check if node is inside a fromRequestDto method
66
+ */
67
+ function isInsideFromRequestDtoMethod(node) {
68
+ let parent = node.parent;
69
+ while (parent) {
70
+ if (parent.type === "MethodDefinition" || parent.type === "Property") {
71
+ if (parent.key && parent.key.name === "fromRequestDto") {
72
+ return true;
73
+ }
74
+ }
75
+ parent = parent.parent;
76
+ }
77
+ return false;
78
+ }
79
+
80
+ return {
81
+ ClassDeclaration(node) {
82
+ const filename = context.getFilename();
83
+ const className = node.id.name;
84
+
85
+ if (isEntityDtoFile(filename) && className.endsWith("EntityDto")) {
86
+ // Track Entity DTOs for later validation
87
+ entityDtos.set(className, {
88
+ node,
89
+ filename,
90
+ });
91
+ }
92
+ },
93
+
94
+ NewExpression(node) {
95
+ const filename = context.getFilename();
96
+ const entityName = node.callee.name;
97
+
98
+ // Only check Entity instantiations (new XxxEntity())
99
+ if (entityName && entityName.endsWith("Entity")) {
100
+ const dtoName = getDtoNameFromEntity(entityName);
101
+ const dtoInfo = entityDtos.get(dtoName);
102
+
103
+ // If we're in an Entity DTO file and instantiating the corresponding Entity
104
+ if (dtoInfo) {
105
+ const isInToEntity = isInsideToEntityMethod(node);
106
+ const isInFromRequestDto = isInsideFromRequestDtoMethod(node);
107
+
108
+ // Allow in toEntity and fromRequestDto methods
109
+ if (!isInToEntity && !isInFromRequestDto) {
110
+ context.report({
111
+ node,
112
+ messageId: "newEntityOutsideToEntity",
113
+ data: { entityName },
114
+ });
115
+ }
116
+ }
117
+ }
118
+ },
119
+ };
120
+ },
121
+ },
122
+ },
123
+ };
124
+
125
+
@@ -0,0 +1,95 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Enforce enum types (PlayableRaceName, PlayableClassName, PlayableFactionName) for playable entity properties instead of string",
6
+ category: "Best Practices",
7
+ recommended: true,
8
+ },
9
+ messages: {
10
+ useEnumType: "Property '{{propertyName}}' must use enum type '{{enumType}}' instead of 'string'. This ensures type safety and validation at compile time.",
11
+ },
12
+ fixable: "code",
13
+ schema: [],
14
+ },
15
+
16
+ create (context) {
17
+ const enumMapping = {
18
+ playableClass: "PlayableClassName",
19
+ playableFaction: "PlayableFactionName",
20
+ playableRace: "PlayableRaceName",
21
+ };
22
+
23
+ function shouldSkipFile (filename) {
24
+ return filename.includes("Enum.ts") ||
25
+ filename.includes("enum/") ||
26
+ filename.includes(".test.ts") ||
27
+ filename.includes(".spec.ts");
28
+ }
29
+
30
+ function hasEnumImport (context, enumName) {
31
+ const sourceCode = context.getSourceCode();
32
+ const text = sourceCode.getText();
33
+
34
+ return text.includes(`import ${enumName}`) ||
35
+ text.includes(`import { ${enumName} }`) ||
36
+ text.includes(`import type { ${enumName} }`);
37
+ }
38
+
39
+ function createEnumImport (enumName) {
40
+ return `import ${enumName} from "@/enum/${enumName}";\n`;
41
+ }
42
+
43
+ return {
44
+ Identifier (node) {
45
+ const name = node.name;
46
+
47
+ if (!(name in enumMapping)) {
48
+ return;
49
+ }
50
+
51
+ const filename = context.getFilename();
52
+ if (shouldSkipFile(filename)) {
53
+ return;
54
+ }
55
+
56
+ if (!node.typeAnnotation || !node.typeAnnotation.typeAnnotation) {
57
+ return;
58
+ }
59
+
60
+ const typeAnnotation = node.typeAnnotation.typeAnnotation;
61
+
62
+ if (typeAnnotation.type !== "TSStringKeyword") {
63
+ return;
64
+ }
65
+
66
+ const enumType = enumMapping[name];
67
+
68
+ context.report({
69
+ messageId: "useEnumType",
70
+ node: node.typeAnnotation,
71
+ data: {
72
+ enumType,
73
+ propertyName: name,
74
+ },
75
+ fix (fixer) {
76
+ const fixes = [];
77
+
78
+ fixes.push(fixer.replaceText(typeAnnotation, enumType));
79
+
80
+ if (!hasEnumImport(context, enumType)) {
81
+ const sourceCode = context.getSourceCode();
82
+ const firstImport = sourceCode.ast.body.find((n) => n.type === "ImportDeclaration");
83
+
84
+ if (firstImport) {
85
+ fixes.push(fixer.insertTextBefore(firstImport, createEnumImport(enumType)));
86
+ }
87
+ }
88
+
89
+ return fixes;
90
+ },
91
+ });
92
+ },
93
+ };
94
+ },
95
+ };
@@ -0,0 +1,257 @@
1
+ /**
2
+ * ESLint-Regel: enforce-error-handling
3
+ * Erzwingt korrekte Error-Handling-Patterns in Services und Controllern
4
+ */
5
+
6
+ /** @type {import('eslint').Rule.RuleModule} */
7
+ const enforceErrorHandlingRule = {
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "Erzwingt korrekte Error-Handling-Patterns und englische Error-Messages",
12
+ category: "Error Handling",
13
+ recommended: true,
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ missingTryCatch: "Async-Methode '{{methodName}}' muss try-catch für Error-Handling verwenden.",
18
+ genericErrorThrow: "Verwende spezifische Error-Klassen statt generischem 'throw new Error()'.",
19
+ missingErrorLogging: "Error muss geloggt werden bevor Result.failure() zurückgegeben wird.",
20
+ germanErrorMessage: "Error-Messages müssen auf Englisch sein. Deutsche Texte sind nicht erlaubt.",
21
+ unhandledPromise: "Promise muss mit .catch() oder try-catch behandelt werden.",
22
+ emptyCatchBlock: "Catch-Block muss einen Error-Parameter haben und den Fehler loggen.",
23
+ missingErrorLoggingInCatch: "Error muss im catch-Block geloggt werden.",
24
+ },
25
+ },
26
+ create(context) {
27
+ const filename = context.getFilename();
28
+ const isServiceFile = filename.includes("/service/") && filename.endsWith(".ts");
29
+ const isControllerFile = filename.includes("/controller/") && filename.endsWith("Controller.ts");
30
+ const isTestFile = filename.includes(".test.") || filename.includes(".spec.");
31
+
32
+ if (!isServiceFile && !isControllerFile || isTestFile) return {};
33
+
34
+ // Deutsche Indikatoren für Error-Messages
35
+ const germanIndicators = [
36
+ "fehler", "nicht", "beim", "für", "der", "die", "das",
37
+ "ist", "sind", "wurde", "werden", "haben", "hat",
38
+ "ungültig", "unbekannt", "gefunden", "existiert",
39
+ "berechtigung", "zugriff", "verweigert", "fehlgeschlagen"
40
+ ];
41
+
42
+ function containsGermanText(text) {
43
+ if (!text || typeof text !== "string") return false;
44
+ const lowerText = text.toLowerCase();
45
+ return germanIndicators.some(indicator => lowerText.includes(indicator));
46
+ }
47
+
48
+ function isAsyncMethod(node) {
49
+ return node.type === "MethodDefinition" && node.value?.async === true;
50
+ }
51
+
52
+ function hasTryCatchBlock(methodNode) {
53
+ const body = methodNode.value?.body;
54
+ if (!body || body.type !== "BlockStatement") return false;
55
+
56
+ return body.body.some(statement => statement.type === "TryStatement");
57
+ }
58
+
59
+ function hasValidErrorLoggingInCatch(tryStatement) {
60
+ if (!tryStatement || tryStatement.type !== "TryStatement") return false;
61
+ if (!tryStatement.handler) return false;
62
+
63
+ const catchBlock = tryStatement.handler;
64
+ const catchBody = catchBlock.body;
65
+
66
+ // Prüfe, ob der catch Block einen Parameter hat
67
+ if (!catchBlock.param) {
68
+ return false; // Leere catch Blöcke sind nicht erlaubt
69
+ }
70
+
71
+ // Prüfe, ob im catch Block ein logger.error() Aufruf vorhanden ist
72
+ if (catchBody.type !== "BlockStatement") return false;
73
+
74
+ return catchBody.body.some(statement => {
75
+ if (statement.type === "ExpressionStatement" &&
76
+ statement.expression?.type === "CallExpression" &&
77
+ statement.expression.callee?.type === "MemberExpression" &&
78
+ statement.expression.callee.property?.name === "error") {
79
+
80
+ // Prüfe auf logger.error oder this.logger.error
81
+ const calleeObject = statement.expression.callee.object;
82
+ if ((calleeObject?.name === "logger") ||
83
+ (calleeObject?.type === "MemberExpression" &&
84
+ calleeObject.object?.type === "ThisExpression" &&
85
+ calleeObject.property?.name === "logger")) {
86
+ return true;
87
+ }
88
+ }
89
+ return false;
90
+ });
91
+ }
92
+
93
+ function hasErrorLogging(node) {
94
+ // Prüfe, ob vor Return-Statement ein logger.error() Aufruf steht
95
+ const parent = node.parent;
96
+ if (parent?.type !== "BlockStatement") return false;
97
+
98
+ const statements = parent.body;
99
+ const returnIndex = statements.indexOf(node);
100
+
101
+ // Prüfe die vorangehenden Statements auf logger.error oder this.logger.error
102
+ for (let i = returnIndex - 1; i >= 0; i--) {
103
+ const stmt = statements[i];
104
+ if (stmt.type === "ExpressionStatement" &&
105
+ stmt.expression?.type === "CallExpression" &&
106
+ stmt.expression.callee?.type === "MemberExpression" &&
107
+ stmt.expression.callee.property?.name === "error") {
108
+
109
+ // Prüfe auf logger.error oder this.logger.error
110
+ const calleeObject = stmt.expression.callee.object;
111
+ if ((calleeObject?.name === "logger") ||
112
+ (calleeObject?.type === "MemberExpression" &&
113
+ calleeObject.object?.type === "ThisExpression" &&
114
+ calleeObject.property?.name === "logger")) {
115
+ return true;
116
+ }
117
+ }
118
+ }
119
+ return false;
120
+ }
121
+
122
+ return {
123
+ // Prüfe async Methoden auf try-catch
124
+ MethodDefinition(node) {
125
+ if (isAsyncMethod(node) && !hasTryCatchBlock(node)) {
126
+ const methodName = node.key?.name || "unknown";
127
+ context.report({
128
+ node,
129
+ messageId: "missingTryCatch",
130
+ data: { methodName },
131
+ });
132
+ }
133
+ },
134
+
135
+ // Prüfe throw new Error() auf spezifische Error-Klassen
136
+ ThrowStatement(node) {
137
+ if (node.argument?.type === "NewExpression" &&
138
+ node.argument.callee?.name === "Error") {
139
+ context.report({
140
+ node,
141
+ messageId: "genericErrorThrow",
142
+ });
143
+ }
144
+
145
+ // Prüfe Error-Messages auf deutsche Texte
146
+ if (node.argument?.arguments?.[0]?.type === "Literal") {
147
+ const message = node.argument.arguments[0].value;
148
+ if (containsGermanText(message)) {
149
+ context.report({
150
+ node: node.argument.arguments[0],
151
+ messageId: "germanErrorMessage",
152
+ });
153
+ }
154
+ }
155
+ },
156
+
157
+ // Prüfe Result.failure() auf Error-Logging
158
+ ReturnStatement(node) {
159
+ if (node.argument?.type === "CallExpression" &&
160
+ node.argument.callee?.type === "MemberExpression" &&
161
+ node.argument.callee.object?.name === "Result" &&
162
+ node.argument.callee.property?.name === "failure") {
163
+
164
+ if (!hasErrorLogging(node)) {
165
+ context.report({
166
+ node,
167
+ messageId: "missingErrorLogging",
168
+ });
169
+ }
170
+ }
171
+ },
172
+
173
+ // Prüfe Promise-Aufrufe auf Fehlerbehandlung
174
+ CallExpression(node) {
175
+ // Prüfe auf unbehandelte Promise-Aufrufe
176
+ if (node.callee?.type === "MemberExpression") {
177
+ const method = node.callee.property?.name;
178
+ const isPromiseMethod = ["then", "catch", "finally"].includes(method);
179
+
180
+ if (isPromiseMethod && method !== "catch") {
181
+ // Prüfe, ob .catch() in der Chain vorhanden ist
182
+ let currentNode = node.parent;
183
+ let hasCatch = false;
184
+
185
+ while (currentNode?.type === "CallExpression" &&
186
+ currentNode.callee?.type === "MemberExpression") {
187
+ if (currentNode.callee.property?.name === "catch") {
188
+ hasCatch = true;
189
+ break;
190
+ }
191
+ currentNode = currentNode.parent;
192
+ }
193
+
194
+ if (!hasCatch && method === "then") {
195
+ context.report({
196
+ node,
197
+ messageId: "unhandledPromise",
198
+ });
199
+ }
200
+ }
201
+ }
202
+ },
203
+
204
+ // Prüfe Error-Konstruktor-Aufrufe auf deutsche Messages
205
+ NewExpression(node) {
206
+ const errorTypes = ["Error", "TypeError", "ReferenceError", "SyntaxError"];
207
+ if (errorTypes.includes(node.callee?.name) &&
208
+ node.arguments?.[0]?.type === "Literal") {
209
+
210
+ const message = node.arguments[0].value;
211
+ if (containsGermanText(message)) {
212
+ context.report({
213
+ node: node.arguments[0],
214
+ messageId: "germanErrorMessage",
215
+ });
216
+ }
217
+ }
218
+ },
219
+
220
+ // Prüfe TryStatement auf korrekte Error-Behandlung
221
+ TryStatement(node) {
222
+ if (!node.handler) {
223
+ context.report({
224
+ node,
225
+ messageId: "emptyCatchBlock",
226
+ });
227
+ return;
228
+ }
229
+
230
+ const catchBlock = node.handler;
231
+
232
+ // Prüfe, ob der catch Block einen Parameter hat
233
+ if (!catchBlock.param) {
234
+ context.report({
235
+ node: catchBlock,
236
+ messageId: "emptyCatchBlock",
237
+ });
238
+ return;
239
+ }
240
+
241
+ // Prüfe, ob der Fehler geloggt wird
242
+ if (!hasValidErrorLoggingInCatch(node)) {
243
+ context.report({
244
+ node: catchBlock,
245
+ messageId: "missingErrorLoggingInCatch",
246
+ });
247
+ }
248
+ },
249
+ };
250
+ },
251
+ };
252
+
253
+ export default {
254
+ rules: {
255
+ "enforce-error-handling": enforceErrorHandlingRule,
256
+ },
257
+ };