@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,49 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Enforce only one class per file",
6
+ category: "Best Practices",
7
+ },
8
+ fixable: null,
9
+ schema: [],
10
+ messages: {
11
+ tooManyClasses: "Only one class per file is allowed. Found {{count}} classes: {{names}}. Please split into separate files.",
12
+ },
13
+ },
14
+
15
+ create(context) {
16
+ const classes = [];
17
+
18
+ function checkClasses() {
19
+ if (classes.length > 1) {
20
+ const classNames = classes.map(d => d.name);
21
+
22
+ // Report on the first class found
23
+ const firstClass = classes[0];
24
+
25
+ context.report({
26
+ node: firstClass.node,
27
+ messageId: "tooManyClasses",
28
+ data: {
29
+ count: classes.length,
30
+ names: classNames.join(", "),
31
+ },
32
+ });
33
+ }
34
+ }
35
+
36
+ return {
37
+ ClassDeclaration(node) {
38
+ classes.push({
39
+ node,
40
+ name: node.id ? node.id.name : "Anonymous",
41
+ });
42
+ },
43
+
44
+ "Program:exit"() {
45
+ checkClasses();
46
+ },
47
+ };
48
+ },
49
+ };
@@ -0,0 +1,146 @@
1
+ /**
2
+ * ESLint Custom Rule: multiline-formatting
3
+ *
4
+ * Erzwingt und fixt automatisch Zeilenumbrüche bei:
5
+ * 1. Langen Method-Chains (.filter().map())
6
+ * 2. Langen Property-Zuweisungen
7
+ * 3. Objekten mit mehreren Properties
8
+ *
9
+ * ❌ Falsch:
10
+ * items: result.items.filter((item): item is ItemEntity => item !== null).map((item) => ItemEntityDto.fromEntity(item)),
11
+ *
12
+ * ✅ Richtig:
13
+ * items: result.items
14
+ * .filter((item): item is ItemEntity => item !== null)
15
+ * .map((item) => ItemEntityDto.fromEntity(item)),
16
+ */
17
+
18
+ export default {
19
+ meta: {
20
+ type: "layout",
21
+ docs: {
22
+ description: "Automatische Zeilenumbrüche bei langen Ausdrücken",
23
+ category: "Stylistic Issues",
24
+ recommended: true,
25
+ },
26
+ fixable: "whitespace",
27
+ messages: {
28
+ tooLongLine: "Diese Zeile ist zu lang ({{length}} Zeichen, max {{max}}). Breche sie um.",
29
+ methodChainShouldBreak: "Method-Chains sollten auf separate Zeilen umgebrochen werden.",
30
+ },
31
+ schema: [{
32
+ type: "object",
33
+ properties: {
34
+ maxLength: {
35
+ type: "number",
36
+ default: 120,
37
+ },
38
+ },
39
+ additionalProperties: false,
40
+ }],
41
+ },
42
+
43
+ create (context) {
44
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
45
+ const maxLength = context.options[0]?.maxLength ?? 120;
46
+
47
+ function getLineLength (node) {
48
+ const start = node.loc.start;
49
+ const end = node.loc.end;
50
+
51
+ if (start.line === end.line) {
52
+ return sourceCode.lines[start.line - 1]?.length ?? 0;
53
+ }
54
+
55
+ return 0;
56
+ }
57
+
58
+ function fixMethodChain (fixer, node) {
59
+ const text = sourceCode.getText(node);
60
+ const fixes = [];
61
+
62
+ if (node.type === "CallExpression" && node.callee.type === "MemberExpression") {
63
+ const chain = [];
64
+ let current = node;
65
+
66
+ while (current.type === "CallExpression" && current.callee.type === "MemberExpression") {
67
+ chain.unshift(current);
68
+ current = current.callee.object;
69
+ }
70
+
71
+ if (chain.length > 1) {
72
+ const baseObject = sourceCode.getText(current);
73
+ const indent = " ".repeat(node.loc.start.column);
74
+
75
+ let newCode = baseObject;
76
+
77
+ for (const call of chain) {
78
+ const methodName = call.callee.property.name;
79
+ const args = call.arguments.map((arg) => sourceCode.getText(arg)).join(", ");
80
+ newCode += `\n${indent} .${methodName}(${args})`;
81
+ }
82
+
83
+ return fixer.replaceText(node, newCode);
84
+ }
85
+ }
86
+
87
+ return null;
88
+ }
89
+
90
+ return {
91
+ CallExpression (node) {
92
+ const lineLength = getLineLength(node);
93
+
94
+ if (lineLength > maxLength && node.callee.type === "MemberExpression") {
95
+ let current = node;
96
+ let chainLength = 0;
97
+
98
+ while (current.type === "CallExpression" && current.callee.type === "MemberExpression") {
99
+ chainLength++;
100
+ current = current.callee.object;
101
+ }
102
+
103
+ if (chainLength > 1) {
104
+ context.report({
105
+ node,
106
+ messageId: "methodChainShouldBreak",
107
+ fix (fixer) {
108
+ return fixMethodChain(fixer, node);
109
+ },
110
+ });
111
+ }
112
+ }
113
+ },
114
+
115
+ Property (node) {
116
+ if (node.value && node.value.type === "CallExpression") {
117
+ const lineLength = getLineLength(node);
118
+
119
+ if (lineLength > maxLength) {
120
+ const valueNode = node.value;
121
+
122
+ if (valueNode.callee.type === "MemberExpression") {
123
+ let current = valueNode;
124
+ let chainLength = 0;
125
+
126
+ while (current.type === "CallExpression" && current.callee.type === "MemberExpression") {
127
+ chainLength++;
128
+ current = current.callee.object;
129
+ }
130
+
131
+ if (chainLength > 1) {
132
+ context.report({
133
+ node: valueNode,
134
+ messageId: "methodChainShouldBreak",
135
+ fix (fixer) {
136
+ return fixMethodChain(fixer, valueNode);
137
+ },
138
+ });
139
+ }
140
+ }
141
+ }
142
+ }
143
+ },
144
+ };
145
+ },
146
+ };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @fileoverview Verbot von Leerzeilen zwischen Dekoratoren und Properties
3
+ * @author Echoes of Order Team
4
+ */
5
+
6
+ export default {
7
+ meta: {
8
+ type: "layout",
9
+ docs: {
10
+ description: "Verbot von Leerzeilen zwischen Dekoratoren und Properties",
11
+ category: "Stylistic Issues",
12
+ recommended: false,
13
+ },
14
+ fixable: "whitespace",
15
+ schema: [],
16
+ messages: {
17
+ noBlankLinesBetweenDecoratorsAndProperties:
18
+ "Leerzeilen zwischen Dekoratoren und Properties sind nicht erlaubt. Entferne die Leerzeile(n)."
19
+ }
20
+ },
21
+
22
+ create(context) {
23
+ const sourceCode = context.getSourceCode();
24
+
25
+ function checkForBlankLinesBetweenDecoratorsAndProperties(node) {
26
+ // Nur für Property-Definitionen prüfen
27
+ if (node.type !== "PropertyDefinition" && node.type !== "MethodDefinition") {
28
+ return;
29
+ }
30
+
31
+ // Prüfen ob das Property/Method Dekoratoren hat
32
+ if (!node.decorators || node.decorators.length === 0) {
33
+ return;
34
+ }
35
+
36
+ const lastDecorator = node.decorators[node.decorators.length - 1];
37
+ const propertyStart = node.key ? node.key.loc.start : node.loc.start;
38
+
39
+ // Alle Tokens zwischen dem letzten Dekorator und dem Property finden
40
+ const tokens = sourceCode.getTokensBetween(lastDecorator, node);
41
+
42
+ // Nach Leerzeilen suchen
43
+ let hasBlankLines = false;
44
+
45
+ for (let i = 0; i < tokens.length; i++) {
46
+ const token = tokens[i];
47
+
48
+ if (token.type === "Punctuator" && token.value === "@") {
49
+ // Wenn wir einen weiteren Dekorator finden, stoppen
50
+ break;
51
+ }
52
+
53
+ // Nach Leerzeilen suchen
54
+ if (token.type === "Whitespace" || token.type === "Line") {
55
+ const lines = token.value.split("\n");
56
+ if (lines.length > 2) { // Mehr als eine Leerzeile
57
+ hasBlankLines = true;
58
+ }
59
+ }
60
+ }
61
+
62
+ // Alternative Prüfung über Zeilen
63
+ const lastDecoratorLine = lastDecorator.loc.end.line;
64
+ const propertyLine = propertyStart.line;
65
+
66
+ if (propertyLine - lastDecoratorLine > 1) {
67
+ hasBlankLines = true;
68
+ }
69
+
70
+ if (hasBlankLines) {
71
+ context.report({
72
+ node,
73
+ messageId: "noBlankLinesBetweenDecoratorsAndProperties",
74
+ fix(fixer) {
75
+ // Alle Leerzeilen zwischen Dekorator und Property entfernen
76
+ const range = [
77
+ lastDecorator.range[1],
78
+ node.range[0]
79
+ ];
80
+
81
+ const text = sourceCode.text.slice(range[0], range[1]);
82
+ const cleanedText = text.replace(/\n\s*\n/g, "\n");
83
+
84
+ return fixer.replaceTextRange(range, cleanedText);
85
+ }
86
+ });
87
+ }
88
+ }
89
+
90
+ return {
91
+ PropertyDefinition: checkForBlankLinesBetweenDecoratorsAndProperties,
92
+ MethodDefinition: checkForBlankLinesBetweenDecoratorsAndProperties
93
+ };
94
+ }
95
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * No Comments Rules
3
+ *
4
+ * Verbot von Kommentaren im Code
5
+ */
6
+
7
+ export default {
8
+ rules: {
9
+ // No-Comments-Regel
10
+ "no-comments/no-comments": {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description: "Disallow comments in code",
15
+ category: "Stylistic Issues",
16
+ recommended: false,
17
+ },
18
+ fixable: null,
19
+ schema: [],
20
+ },
21
+ create (context) {
22
+ const sourceCode = context.getSourceCode();
23
+ return {
24
+ Program () {
25
+ const comments = sourceCode.getAllComments();
26
+ comments.forEach((comment) => {
27
+ const value = comment.value.trim();
28
+ if (value.startsWith("eslint-disable")
29
+ || value.startsWith("eslint-enable")
30
+ || value.startsWith("@ts-ignore")
31
+ || value.startsWith("@ts-expect-error")
32
+ || value.startsWith("@ts-nocheck")
33
+ || value.startsWith("@ts-check")
34
+ || value.includes("prettier-ignore")
35
+ || value.includes("istanbul ignore")
36
+ || value.includes("c8 ignore")
37
+ || value.includes("coverage ignore")
38
+ || value.includes("codecov ignore")
39
+ || value.startsWith("SPDX-License-Identifier")
40
+ || value.startsWith("Copyright")
41
+ || value.startsWith("(c)")
42
+ || value.startsWith("MIT License")
43
+ || value.includes("Apache License")
44
+ || value.includes("GPL")
45
+ || value.includes("BSD")
46
+ || value.includes("ISC License")
47
+ ) {
48
+ return;
49
+ }
50
+ context.report({
51
+ node: comment,
52
+ message: "Comments are not allowed in code. Code should be self-explanatory.",
53
+ });
54
+ });
55
+ },
56
+ };
57
+ },
58
+ },
59
+ },
60
+ };
61
+
62
+
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @fileoverview Verbietet Konstruktoren in DTO-Klassen
3
+ * DTOs sind reine Datencontainer und sollten keine Konstruktoren haben.
4
+ * Sie werden automatisch von class-transformer aus JSON erstellt.
5
+ */
6
+
7
+ export default {
8
+ meta: {
9
+ type: "error",
10
+ docs: {
11
+ description: "Forbid constructors in DTO classes - DTOs should be pure data containers",
12
+ category: "Best Practices",
13
+ recommended: true,
14
+ },
15
+ fixable: "code",
16
+ schema: [],
17
+ messages: {
18
+ noDtoConstructor: "DTO class '{{className}}' must not have a constructor. DTOs are pure data containers that are automatically instantiated by class-transformer from JSON. Remove the constructor and let the framework handle object creation.",
19
+ constructorRemoved: "Constructor removed from DTO class '{{className}}'",
20
+ },
21
+ },
22
+
23
+ create(context) {
24
+ const filename = context.getFilename();
25
+
26
+ // Ignoriere Test-Dateien und Non-DTO-Dateien
27
+ if (!filename.includes("/dto/") ||
28
+ filename.includes("test") ||
29
+ filename.includes("spec") ||
30
+ filename.includes("__tests__") ||
31
+ filename.endsWith(".test.ts") ||
32
+ filename.endsWith(".spec.ts")) {
33
+ return {};
34
+ }
35
+
36
+ function isDtoClass(className) {
37
+ return (
38
+ className.endsWith("Dto") ||
39
+ className.endsWith("DTO") ||
40
+ className.includes("Request") ||
41
+ className.includes("Response") ||
42
+ className.includes("Create") ||
43
+ className.includes("Update") ||
44
+ className.includes("Delete")
45
+ );
46
+ }
47
+
48
+ function isInDtoClass(node) {
49
+ let parent = node.parent;
50
+
51
+ // Traverse up to find the class declaration
52
+ while (parent) {
53
+ if (parent.type === "ClassDeclaration" ||
54
+ (parent.type === "ExportDefaultDeclaration" && parent.declaration?.type === "ClassDeclaration")) {
55
+
56
+ const classNode = parent.type === "ClassDeclaration" ? parent : parent.declaration;
57
+ const className = classNode.id?.name;
58
+
59
+ if (className && isDtoClass(className)) {
60
+ return { isDto: true, className, classNode };
61
+ }
62
+ break;
63
+ }
64
+ parent = parent.parent;
65
+ }
66
+
67
+ return { isDto: false, className: null, classNode: null };
68
+ }
69
+
70
+ return {
71
+ MethodDefinition(node) {
72
+ // Prüfe nur Konstruktoren
73
+ if (node.kind !== "constructor") {
74
+ return;
75
+ }
76
+
77
+ const { isDto, className } = isInDtoClass(node);
78
+
79
+ if (isDto) {
80
+ context.report({
81
+ node,
82
+ messageId: "noDtoConstructor",
83
+ data: {
84
+ className: className || "unknown"
85
+ },
86
+ fix(fixer) {
87
+ const sourceCode = context.getSourceCode();
88
+
89
+ // Finde den Konstruktor-Block
90
+ const constructorStart = node.range[0];
91
+ const constructorEnd = node.range[1];
92
+
93
+ // Prüfe, ob es Leerzeilen vor/nach dem Konstruktor gibt
94
+ const beforeConstructor = sourceCode.getText().substring(0, constructorStart);
95
+ const afterConstructor = sourceCode.getText().substring(constructorEnd);
96
+
97
+ // Entferne auch überflüssige Leerzeilen
98
+ let startPos = constructorStart;
99
+ let endPos = constructorEnd;
100
+
101
+ // Prüfe auf Leerzeilen vor dem Konstruktor
102
+ const lines = beforeConstructor.split('\n');
103
+ if (lines.length > 1 && lines[lines.length - 1].trim() === '') {
104
+ // Finde die Position der letzten nicht-leeren Zeile vor dem Konstruktor
105
+ for (let i = lines.length - 2; i >= 0; i--) {
106
+ if (lines[i].trim() !== '') {
107
+ break;
108
+ }
109
+ startPos -= (lines[lines.length - 1 - (lines.length - 2 - i)].length + 1);
110
+ }
111
+ }
112
+
113
+ // Prüfe auf Leerzeilen nach dem Konstruktor
114
+ const afterLines = afterConstructor.split('\n');
115
+ if (afterLines.length > 1 && afterLines[0].trim() === '') {
116
+ endPos += afterLines[0].length + 1;
117
+ }
118
+
119
+ return fixer.removeRange([startPos, endPos]);
120
+ },
121
+ });
122
+ }
123
+ }
124
+ };
125
+ },
126
+ };
@@ -0,0 +1,220 @@
1
+ /**
2
+ * ESLint-Regel: no-dto-default-values
3
+ * Verbietet Default Values in DTO-Klassen
4
+ * Verbesserte Version für TypeScript
5
+ */
6
+
7
+ /** @type {import('eslint').Rule.RuleModule} */
8
+ const noDtoDefaultValuesRule = {
9
+ meta: {
10
+ type: "problem",
11
+ docs: {
12
+ description: "DTO-Klassen dürfen keine Default Values haben",
13
+ category: "Best Practices",
14
+ recommended: true,
15
+ },
16
+ fixable: "code",
17
+ schema: [],
18
+ messages: {
19
+ noDefaultValue: "DTO property '{{propertyName}}' darf keinen Default Value haben. Verwende stattdessen explizite Typ-Annotation ohne Initialisierung.",
20
+ noDefaultValueInConstructor: "DTO-Properties dürfen nicht im Constructor initialisiert werden. Verwende stattdessen die create()-Methode.",
21
+ },
22
+ },
23
+ create(context) {
24
+ function isDtoClass(node) {
25
+ return node.id && node.id.name && node.id.name.endsWith('Dto');
26
+ }
27
+
28
+ function hasDefaultValue(member) {
29
+ // Für Getter: Prüfe Return-Statements im Body
30
+ if (member.type === "MethodDefinition" && member.kind === "get" && member.value?.body) {
31
+ const returnStatements = member.value.body.body.filter(stmt =>
32
+ stmt.type === "ReturnStatement" && stmt.argument
33
+ );
34
+
35
+ // Wenn es Returns mit Literal-Werten gibt (außer undefined), ist das ein Default
36
+ return returnStatements.some(stmt => {
37
+ const arg = stmt.argument;
38
+ return arg.type === "ObjectExpression" ||
39
+ (arg.type === "Literal" && arg.value !== undefined) ||
40
+ (arg.type === "ArrayExpression");
41
+ });
42
+ }
43
+
44
+ // Für reguläre Properties: Prüfe ob value vorhanden ist
45
+ return member.value !== null && member.value !== undefined;
46
+ }
47
+
48
+ function isPrivateProperty(propertyName) {
49
+ return propertyName.startsWith('_');
50
+ }
51
+
52
+ function inferTypeFromValue(value) {
53
+ if (!value) return "unknown";
54
+ switch (value.type) {
55
+ case "Literal":
56
+ if (typeof value.value === "string") return "string";
57
+ if (typeof value.value === "number") return "number";
58
+ if (typeof value.value === "boolean") return "boolean";
59
+ return "unknown";
60
+ case "ArrayExpression": return "any[]";
61
+ case "ObjectExpression": return "object";
62
+ case "NewExpression": return "object";
63
+ case "Identifier":
64
+ if (value.name === "true" || value.name === "false") return "boolean";
65
+ if (value.name === "null") return "null";
66
+ if (value.name === "undefined") return "undefined";
67
+ return "unknown";
68
+ default: return "unknown";
69
+ }
70
+ }
71
+
72
+ function checkPropertyForDefaultValue(member) {
73
+ if (!member.key?.name) return;
74
+
75
+ const propertyName = member.key.name;
76
+
77
+ // Überspringe private Properties und statische Properties
78
+ if (isPrivateProperty(propertyName) || member.static) {
79
+ return;
80
+ }
81
+
82
+ // Für Getter: Prüfe ob der Body einen Default-Wert zurückgibt
83
+ if (member.type === "MethodDefinition" && member.kind === "get" && member.value?.body) {
84
+ const returnStatements = member.value.body.body.filter(stmt =>
85
+ stmt.type === "ReturnStatement" && stmt.argument
86
+ );
87
+
88
+ // Wenn alle Returns Literal-Werte haben (außer undefined), ist das ein Default
89
+ const hasDefaultReturn = returnStatements.some(stmt => {
90
+ const arg = stmt.argument;
91
+ return arg.type === "ObjectExpression" ||
92
+ (arg.type === "Literal" && arg.value !== undefined) ||
93
+ (arg.type === "ArrayExpression");
94
+ });
95
+
96
+ if (hasDefaultReturn) {
97
+ context.report({
98
+ node: member,
99
+ messageId: "noDefaultValue",
100
+ data: { propertyName },
101
+ fix(fixer) {
102
+ // Für Getter: Entferne die Returns mit Default-Werten
103
+ const statementsToRemove = returnStatements.filter(stmt => {
104
+ const arg = stmt.argument;
105
+ return arg.type === "ObjectExpression" ||
106
+ (arg.type === "Literal" && arg.value !== undefined) ||
107
+ (arg.type === "ArrayExpression");
108
+ });
109
+
110
+ if (statementsToRemove.length > 0) {
111
+ const ranges = statementsToRemove.map(stmt => stmt.range);
112
+ // Entferne die Statements von hinten nach vorne
113
+ return ranges.reverse().map(range => fixer.removeRange(range));
114
+ }
115
+ return null;
116
+ },
117
+ });
118
+ }
119
+ return;
120
+ }
121
+
122
+ // Prüfe ob Default Value vorhanden ist (für reguläre Properties)
123
+ if (hasDefaultValue(member)) {
124
+ context.report({
125
+ node: member,
126
+ messageId: "noDefaultValue",
127
+ data: { propertyName },
128
+ fix(fixer) {
129
+ if (member.typeAnnotation) {
130
+ // Hat bereits Type Annotation, entferne nur den Default Value
131
+ const valueStart = member.value.range[0];
132
+ // Finde das "=" Zeichen vor dem Wert
133
+ let equalPos = valueStart - 1;
134
+ const sourceCode = context.getSourceCode();
135
+ const text = sourceCode.getText();
136
+ while (equalPos > 0 && text[equalPos] !== '=') {
137
+ equalPos--;
138
+ }
139
+ // Entferne alles von "=" bis zum Ende des Wertes (inklusive Whitespace)
140
+ return fixer.removeRange([equalPos - 1, member.value.range[1]]);
141
+ } else {
142
+ // Keine Type Annotation, infere Typ und ersetze
143
+ const inferredType = inferTypeFromValue(member.value);
144
+ if (inferredType !== "unknown") {
145
+ return fixer.replaceText(member, `${propertyName}: ${inferredType};`);
146
+ }
147
+ }
148
+ return null;
149
+ },
150
+ });
151
+ }
152
+ }
153
+
154
+ return {
155
+ ClassDeclaration(node) {
156
+ if (!isDtoClass(node)) return;
157
+
158
+ node.body.body.forEach(member => {
159
+ // Unterstütze verschiedene Property-Typen für TypeScript
160
+ if ((member.type === "PropertyDefinition" ||
161
+ member.type === "ClassProperty" ||
162
+ member.type === "TSPropertySignature" ||
163
+ (member.type === "MethodDefinition" && member.kind === "get")) &&
164
+ member.key?.name &&
165
+ member.kind !== "method" &&
166
+ member.kind !== "constructor") {
167
+ checkPropertyForDefaultValue(member);
168
+ }
169
+ });
170
+ },
171
+
172
+ ClassExpression(node) {
173
+ if (!isDtoClass(node)) return;
174
+
175
+ node.body.body.forEach(member => {
176
+ if ((member.type === "PropertyDefinition" ||
177
+ member.type === "ClassProperty" ||
178
+ member.type === "TSPropertySignature" ||
179
+ (member.type === "MethodDefinition" && member.kind === "get")) &&
180
+ member.key?.name &&
181
+ member.kind !== "method" &&
182
+ member.kind !== "constructor") {
183
+ checkPropertyForDefaultValue(member);
184
+ }
185
+ });
186
+ },
187
+
188
+ MethodDefinition(node) {
189
+ if (node.kind === "constructor") {
190
+ const classNode = node.parent.parent;
191
+ if (!isDtoClass(classNode)) return;
192
+
193
+ if (node.value.body) {
194
+ node.value.body.body.forEach(stmt => {
195
+ if (stmt.type === "ExpressionStatement" &&
196
+ stmt.expression.type === "AssignmentExpression" &&
197
+ stmt.expression.left.type === "MemberExpression" &&
198
+ stmt.expression.left.object.type === "ThisExpression") {
199
+ const propertyName = stmt.expression.left.property.name;
200
+ if (!isPrivateProperty(propertyName)) {
201
+ context.report({
202
+ node: stmt,
203
+ messageId: "noDefaultValueInConstructor",
204
+ data: { propertyName },
205
+ });
206
+ }
207
+ }
208
+ });
209
+ }
210
+ }
211
+ },
212
+ };
213
+ },
214
+ };
215
+
216
+ export default {
217
+ rules: {
218
+ "no-dto-default-values": noDtoDefaultValuesRule,
219
+ },
220
+ };