@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,141 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Enforce that Entity DTOs extend BaseEntityDto to prevent circular dependencies",
6
+ category: "Best Practices",
7
+ recommended: true,
8
+ },
9
+ messages: {
10
+ missingBaseClass: "Entity-DTO '{{className}}' muss BaseEntityDto erweitern, um zirkuläre Abhängigkeiten zu vermeiden",
11
+ invalidBaseClass: "Entity-DTO '{{className}}' muss genau BaseEntityDto erweitern, nicht '{{extendedClass}}'",
12
+ missingBaseImport: "Entity-DTO '{{className}}' muss BaseEntityDto importieren: import { BaseEntityDto } from '@/dto/BaseEntityDto'",
13
+ },
14
+ fixable: "code",
15
+ schema: [],
16
+ },
17
+
18
+ create (context) {
19
+ const filename = context.getFilename();
20
+
21
+ // Skip non-DTO files
22
+ if (!filename.includes("/dto/Entity/")) {
23
+ return {};
24
+ }
25
+
26
+ // Skip BaseEntityDto itself
27
+ if (filename.endsWith("/BaseEntityDto.ts")) {
28
+ return {};
29
+ }
30
+
31
+ // Skip test files
32
+ if (filename.includes(".test.") || filename.includes(".spec.") || filename.includes("__tests__")) {
33
+ return {};
34
+ }
35
+
36
+ let hasBaseImport = false;
37
+ let className = null;
38
+ let hasFromEntity = false;
39
+
40
+ return {
41
+ ImportDeclaration (node) {
42
+ // Check for BaseEntityDto import (absolute or relative)
43
+ const isBaseImport = node.source.value === "@/dto/BaseEntityDto" ||
44
+ node.source.value.endsWith("/BaseEntityDto") ||
45
+ node.source.value === "./BaseEntityDto";
46
+
47
+ if (isBaseImport) {
48
+ const hasBaseEntityDto = node.specifiers.some(
49
+ (spec) => spec.type === "ImportSpecifier" && spec.imported.name === "BaseEntityDto",
50
+ );
51
+ if (hasBaseEntityDto) {
52
+ hasBaseImport = true;
53
+ }
54
+ }
55
+ },
56
+
57
+ ClassDeclaration (node) {
58
+ // Only check classes that are Entity DTOs
59
+ if (!node.id || !node.id.name.endsWith("EntityDto")) {
60
+ return;
61
+ }
62
+
63
+ className = node.id.name;
64
+
65
+ // Check if class has fromEntity method
66
+ hasFromEntity = node.body.body.some(
67
+ (member) => member.type === "MethodDefinition"
68
+ && member.static
69
+ && member.key.name === "fromEntity",
70
+ );
71
+
72
+ // Only enforce if class has fromEntity method
73
+ if (!hasFromEntity) {
74
+ return;
75
+ }
76
+
77
+ // Check if class extends BaseEntityDto
78
+ if (!node.superClass) {
79
+ context.report({
80
+ node,
81
+ messageId: "missingBaseClass",
82
+ data: { className },
83
+ fix (fixer) {
84
+ const fixes = [];
85
+
86
+ // Add import if missing
87
+ if (!hasBaseImport) {
88
+ const sourceCode = context.getSourceCode();
89
+ const firstImport = sourceCode.ast.body.find((n) => n.type === "ImportDeclaration");
90
+ if (firstImport) {
91
+ fixes.push(
92
+ fixer.insertTextBefore(
93
+ firstImport,
94
+ "import { BaseEntityDto } from \"@/dto/BaseEntityDto\";\n",
95
+ ),
96
+ );
97
+ }
98
+ }
99
+
100
+ // Add extends clause
101
+ const classKeyword = context.getSourceCode().getFirstToken(node);
102
+ const className = context.getSourceCode().getFirstToken(node, 1);
103
+ fixes.push(fixer.insertTextAfter(className, " extends BaseEntityDto"));
104
+
105
+ return fixes;
106
+ },
107
+ });
108
+ } else if (node.superClass.name !== "BaseEntityDto") {
109
+ context.report({
110
+ node: node.superClass,
111
+ messageId: "invalidBaseClass",
112
+ data: {
113
+ className,
114
+ extendedClass: node.superClass.name,
115
+ },
116
+ });
117
+ } else if (!hasBaseImport) {
118
+ // Class extends BaseEntityDto but import is missing
119
+ context.report({
120
+ node,
121
+ messageId: "missingBaseImport",
122
+ data: { className },
123
+ fix (fixer) {
124
+ const sourceCode = context.getSourceCode();
125
+ const firstImport = sourceCode.ast.body.find((n) => n.type === "ImportDeclaration");
126
+ if (firstImport) {
127
+ return fixer.insertTextBefore(
128
+ firstImport,
129
+ "import { BaseEntityDto } from \"@/dto/BaseEntityDto\";\n",
130
+ );
131
+ }
132
+
133
+ return null;
134
+ },
135
+ });
136
+ }
137
+ },
138
+ };
139
+ },
140
+ };
141
+
@@ -0,0 +1,113 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Enforce correct structure of EntityDto.fromRequestDto() methods",
6
+ category: "Best Practices",
7
+ recommended: true,
8
+ },
9
+ messages: {
10
+ wrongReturnType: "fromRequestDto() muss {{entityDtoName}} zurückgeben, nicht Entity. Das Pattern ist: EntityDto → toEntity() → save().",
11
+ wrongParameterType: "fromRequestDto() muss {{requestDtoName}} als Parameter haben für typsichere Konvertierung.",
12
+ noRelationsAllowed: "fromRequestDto() darf keine Relations setzen. Relations werden im Service mit load*EntityDto() Methoden geladen. Nur direkte Properties sind erlaubt.",
13
+ },
14
+ schema: [],
15
+ },
16
+ create (context) {
17
+ const filename = context.getFilename();
18
+
19
+ if (!filename.includes("/dto/Entity/") || !filename.endsWith("EntityDto.ts")) {
20
+ return {};
21
+ }
22
+
23
+ return {
24
+ MethodDefinition (node) {
25
+ if (
26
+ node.static &&
27
+ node.key.name === "fromRequestDto" &&
28
+ node.value.type === "FunctionExpression"
29
+ ) {
30
+ const returnType = node.value.returnType;
31
+ const params = node.value.params;
32
+
33
+ if (returnType && returnType.typeAnnotation) {
34
+ const returnTypeName = context.getSourceCode().getText(returnType.typeAnnotation);
35
+
36
+ if (returnTypeName.includes("Entity") && !returnTypeName.includes("Dto")) {
37
+ const className = context.getSourceCode().getText(node.parent.parent.id);
38
+ context.report({
39
+ messageId: "wrongReturnType",
40
+ node: returnType,
41
+ data: {
42
+ entityDtoName: className,
43
+ },
44
+ });
45
+ }
46
+ }
47
+
48
+ if (params.length > 0 && params[0].typeAnnotation) {
49
+ const typeAnnotation = params[0].typeAnnotation.typeAnnotation;
50
+ const isInlineInterface = typeAnnotation.type === "TSTypeLiteral";
51
+ const paramTypeName = context.getSourceCode().getText(typeAnnotation);
52
+
53
+ if (!isInlineInterface && !paramTypeName.includes("RequestDto") && !paramTypeName.includes("import(")) {
54
+ context.report({
55
+ messageId: "wrongParameterType",
56
+ node: params[0],
57
+ data: {
58
+ requestDtoName: paramTypeName,
59
+ },
60
+ });
61
+ }
62
+ }
63
+
64
+ const bodyStatements = node.value.body.body;
65
+ for (const statement of bodyStatements) {
66
+ if (statement.type === "ExpressionStatement") {
67
+ const expr = statement.expression;
68
+
69
+ if (
70
+ expr.type === "AssignmentExpression" &&
71
+ expr.left.type === "MemberExpression" &&
72
+ expr.left.object.name === "dto"
73
+ ) {
74
+ const propertyName = expr.left.property.name;
75
+
76
+ const exactRelationNames = [
77
+ "quality",
78
+ "binding",
79
+ "itemClass",
80
+ "itemSubclass",
81
+ "inventoryType",
82
+ "parent",
83
+ "child",
84
+ "relation",
85
+ "playableClass",
86
+ "playableRace",
87
+ "playableFaction",
88
+ "character",
89
+ "account",
90
+ "stats",
91
+ "translations",
92
+ "abilities",
93
+ "baseStats",
94
+ "raceAbilities",
95
+ ];
96
+
97
+ const isRelation = exactRelationNames.includes(propertyName);
98
+
99
+ if (isRelation) {
100
+ context.report({
101
+ messageId: "noRelationsAllowed",
102
+ node: statement,
103
+ });
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ },
110
+ };
111
+ },
112
+ };
113
+
@@ -0,0 +1,69 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Entity-DTOs müssen fromEntity-Methoden bereitstellen",
6
+ category: "Architecture",
7
+ recommended: true,
8
+ },
9
+ schema: [],
10
+ messages: {
11
+ missingFromEntityMethod: "Entity-DTO-Klasse '{{dtoName}}' muss eine statische fromEntity-Methode bereitstellen",
12
+ forbiddenCreateMethod: "Entity-DTO-Klasse '{{dtoName}}' darf keine create-Methode haben. Verwende stattdessen fromEntity-Methoden.",
13
+ },
14
+ },
15
+ create(context) {
16
+ const filename = context.getFilename();
17
+
18
+ // Prüfe, ob es sich um eine Entity-DTO-Datei handelt
19
+ const isEntityDtoFile = filename.includes("/dto/Entity/");
20
+
21
+ if (!isEntityDtoFile) {
22
+ return {};
23
+ }
24
+
25
+ let classNode = null;
26
+ let dtoClassName = "";
27
+
28
+ return {
29
+ ClassDeclaration(node) {
30
+ classNode = node;
31
+ dtoClassName = node.id?.name;
32
+ },
33
+ ExportDefaultDeclaration(node) {
34
+ if (node.declaration.type === "ClassDeclaration") {
35
+ classNode = node.declaration;
36
+ dtoClassName = node.declaration.id?.name;
37
+ }
38
+ },
39
+ "Program:exit"(node) {
40
+ if (!classNode || !dtoClassName) return;
41
+
42
+ const methods = classNode.body.body.filter(member =>
43
+ member.type === "MethodDefinition" &&
44
+ member.static === true
45
+ );
46
+
47
+ const hasFromEntityMethod = methods.some(method =>
48
+ method.key?.name === "fromEntity"
49
+ );
50
+
51
+ const hasCreateMethod = methods.some(method =>
52
+ method.key?.name === "create"
53
+ );
54
+
55
+ // Request-DTOs sind von der fromEntity-Regel ausgenommen
56
+ const isRequestDto = filename.includes("/dto/Request/");
57
+
58
+ if (!isRequestDto) {
59
+ if (!hasFromEntityMethod) {
60
+ context.report({ node: classNode, messageId: "missingFromEntityMethod", data: { dtoName: dtoClassName } });
61
+ }
62
+ if (hasCreateMethod) {
63
+ context.report({ node: classNode, messageId: "forbiddenCreateMethod", data: { dtoName: dtoClassName } });
64
+ }
65
+ }
66
+ },
67
+ };
68
+ },
69
+ };
@@ -0,0 +1,69 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Entity-DTOs müssen fromEntity-Methoden bereitstellen",
6
+ category: "Architecture",
7
+ recommended: true,
8
+ },
9
+ schema: [],
10
+ messages: {
11
+ missingFromEntityMethod: "Entity-DTO-Klasse '{{dtoName}}' muss eine statische fromEntity-Methode bereitstellen",
12
+ forbiddenCreateMethod: "Entity-DTO-Klasse '{{dtoName}}' darf keine create-Methode haben. Verwende stattdessen fromEntity-Methoden.",
13
+ },
14
+ },
15
+ create(context) {
16
+ const filename = context.getFilename();
17
+
18
+ // Prüfe, ob es sich um eine Entity-DTO-Datei handelt
19
+ const isEntityDtoFile = filename.includes("/dto/Entity/");
20
+
21
+ if (!isEntityDtoFile) {
22
+ return {};
23
+ }
24
+
25
+ let classNode = null;
26
+ let dtoClassName = "";
27
+
28
+ return {
29
+ ClassDeclaration(node) {
30
+ classNode = node;
31
+ dtoClassName = node.id?.name;
32
+ },
33
+ ExportDefaultDeclaration(node) {
34
+ if (node.declaration.type === "ClassDeclaration") {
35
+ classNode = node.declaration;
36
+ dtoClassName = node.declaration.id?.name;
37
+ }
38
+ },
39
+ "Program:exit"(node) {
40
+ if (!classNode || !dtoClassName) return;
41
+
42
+ const methods = classNode.body.body.filter(member =>
43
+ member.type === "MethodDefinition" &&
44
+ member.static === true
45
+ );
46
+
47
+ const hasFromEntityMethod = methods.some(method =>
48
+ method.key?.name === "fromEntity"
49
+ );
50
+
51
+ const hasCreateMethod = methods.some(method =>
52
+ method.key?.name === "create"
53
+ );
54
+
55
+ // Request-DTOs sind von der fromEntity-Regel ausgenommen
56
+ const isRequestDto = filename.includes("/dto/Request/");
57
+
58
+ if (!isRequestDto) {
59
+ if (!hasFromEntityMethod) {
60
+ context.report({ node: classNode, messageId: "missingFromEntityMethod", data: { dtoName: dtoClassName } });
61
+ }
62
+ if (hasCreateMethod) {
63
+ context.report({ node: classNode, messageId: "forbiddenCreateMethod", data: { dtoName: dtoClassName } });
64
+ }
65
+ }
66
+ },
67
+ };
68
+ },
69
+ };
@@ -0,0 +1,262 @@
1
+ /**
2
+ * @fileoverview Enforce correct structure for fromRequestDto methods in Entity DTOs
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 enforceEntityDtoFromRequestDtoStructureRule = {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description: "Enforce correct structure for fromRequestDto methods in Entity DTOs",
15
+ category: "Architecture",
16
+ recommended: true,
17
+ },
18
+ fixable: null,
19
+ schema: [],
20
+ messages: {
21
+ invalidParameterName: "fromRequestDto-Parameter muss 'requestDto' heissen, nicht '{{actualName}}'",
22
+ invalidParameterType: "fromRequestDto-Parameter muss vom Typ '{{expectedType}}' sein, nicht '{{actualType}}'",
23
+ missingDtoInstantiation: "fromRequestDto muss das EntityDto instanziieren: const dto = new {{className}}();",
24
+ wrongVariableName: "fromRequestDto muss die Variable 'dto' verwenden, nicht '{{actualName}}'. Verwende: const dto = new {{className}}();",
25
+ missingPropertyAssignment: "Property '{{propertyName}}' fehlt in fromRequestDto-Methode. Fuege hinzu: dto.{{propertyName}} = requestDto.{{propertyName}};",
26
+ autoFieldDirectAssignment: "Property '{{fieldName}}' darf nicht direkt zugewiesen werden. Verwende conditional assignment: if (requestDto.{{fieldName}}) { dto.{{fieldName}} = requestDto.{{fieldName}}; }",
27
+ invalidReturnType: "fromRequestDto muss '{{className}}' zurueckgeben, nicht '{{actualType}}'",
28
+ missingReturn: "fromRequestDto muss 'dto' zurueckgeben: return dto;",
29
+ },
30
+ },
31
+
32
+ create(context) {
33
+ const filename = context.getFilename();
34
+ const AUTO_MANAGED_FIELDS = ["id", "createdAt", "updatedAt"];
35
+
36
+ if (!filename.includes("/dto/Entity/") || filename.endsWith("/BaseEntityDto.ts")) {
37
+ return {};
38
+ }
39
+
40
+ function getEntityDtoClassName(filename) {
41
+ return path.basename(filename, ".ts");
42
+ }
43
+
44
+ function getExpectedRequestDtoName(entityDtoName) {
45
+ const baseRequestDtoName = entityDtoName.replace(/EntityDto$/, "RequestDto");
46
+ return {
47
+ base: baseRequestDtoName,
48
+ create: `Create${baseRequestDtoName}`,
49
+ update: `Update${baseRequestDtoName}`,
50
+ };
51
+ }
52
+
53
+ function isValidRequestDtoType(actualType, expectedNames) {
54
+ return actualType === expectedNames.base ||
55
+ actualType === expectedNames.create ||
56
+ actualType === expectedNames.update;
57
+ }
58
+
59
+ function getEntityDtoProperties(filename) {
60
+ try {
61
+ const content = fs.readFileSync(filename, "utf-8");
62
+ const lines = content.split("\n");
63
+ const properties = [];
64
+ let inDecorator = false;
65
+ let decoratorBraceCount = 0;
66
+
67
+ for (let i = 0; i < lines.length; i++) {
68
+ const line = lines[i];
69
+
70
+ if (line.trim().startsWith("@")) {
71
+ if (line.includes("(")) {
72
+ inDecorator = true;
73
+ decoratorBraceCount = (line.match(/\(/g) || []).length - (line.match(/\)/g) || []).length;
74
+ }
75
+ }
76
+
77
+ if (inDecorator) {
78
+ decoratorBraceCount += (line.match(/\(/g) || []).length - (line.match(/\)/g) || []).length;
79
+ if (decoratorBraceCount === 0) {
80
+ inDecorator = false;
81
+ }
82
+ continue;
83
+ }
84
+
85
+ const propertyMatch = line.match(/^\s+(\w+)(\?)?:\s+([^;]+);/);
86
+ if (propertyMatch && !line.trim().startsWith("@") && !line.includes("static ") && !line.includes("get ")) {
87
+ const propertyName = propertyMatch[1];
88
+ const propertyType = propertyMatch[3].trim();
89
+
90
+ // Skip relations (EntityDto properties, arrays of EntityDto)
91
+ const isRelation = propertyType.includes("EntityDto") ||
92
+ propertyType.includes("EntityDto[]") ||
93
+ propertyType.includes("Array<") ||
94
+ propertyType.match(/\[\s*\]/);
95
+
96
+ if (propertyName !== "static" && !propertyName.startsWith("_") && !isRelation) {
97
+ properties.push({ name: propertyName });
98
+ }
99
+ }
100
+ }
101
+
102
+ return properties;
103
+ } catch (error) {
104
+ return [];
105
+ }
106
+ }
107
+
108
+ function isDirectAssignment(node, fieldName) {
109
+ if (node.type === "ExpressionStatement" && node.expression?.type === "AssignmentExpression") {
110
+ const left = node.expression.left;
111
+ return left?.type === "MemberExpression" && left.object?.name === "dto" && left.property?.name === fieldName;
112
+ }
113
+ return false;
114
+ }
115
+
116
+ function getAssignedProperties(body) {
117
+ const assigned = new Set();
118
+
119
+ function traverse(statements) {
120
+ if (!Array.isArray(statements)) return;
121
+
122
+ for (const stmt of statements) {
123
+ if (stmt.type === "ExpressionStatement" && stmt.expression?.type === "AssignmentExpression" && stmt.expression.left?.type === "MemberExpression" && stmt.expression.left.object?.name === "dto") {
124
+ assigned.add(stmt.expression.left.property.name);
125
+ }
126
+
127
+ if (stmt.type === "IfStatement") {
128
+ traverse(stmt.consequent?.body || [stmt.consequent]);
129
+ traverse(stmt.alternate?.body || [stmt.alternate]);
130
+ }
131
+
132
+ if (stmt.type === "BlockStatement") {
133
+ traverse(stmt.body);
134
+ }
135
+ }
136
+ }
137
+
138
+ traverse(body);
139
+ return assigned;
140
+ }
141
+
142
+ function isFromRequestDtoMethod(node) {
143
+ return (
144
+ (node.type === "MethodDefinition" || node.type === "Property") &&
145
+ node.key?.name === "fromRequestDto" &&
146
+ node.static === true
147
+ );
148
+ }
149
+
150
+ return {
151
+ MethodDefinition(node) {
152
+ if (!isFromRequestDtoMethod(node)) {
153
+ return;
154
+ }
155
+
156
+ const className = getEntityDtoClassName(filename);
157
+ const expectedRequestDtoName = getExpectedRequestDtoName(className);
158
+
159
+ const params = node.value.params;
160
+ if (params.length !== 1) {
161
+ return;
162
+ }
163
+
164
+ const param = params[0];
165
+
166
+ if (param.name !== "requestDto") {
167
+ context.report({ node: param, messageId: "invalidParameterName", data: { actualName: param.name } });
168
+ }
169
+
170
+ if (param.typeAnnotation) {
171
+ const typeAnnotation = param.typeAnnotation.typeAnnotation;
172
+ let actualType = "";
173
+
174
+ if (typeAnnotation.type === "TSTypeReference") {
175
+ actualType = typeAnnotation.typeName.name;
176
+ } else if (typeAnnotation.type === "TSImportType") {
177
+ const importPath = typeAnnotation.argument?.value;
178
+ if (importPath) {
179
+ const match = importPath.match(/\/([^/]+)$/);
180
+ actualType = match ? match[1] : "";
181
+ }
182
+ }
183
+
184
+ if (!isValidRequestDtoType(actualType, expectedRequestDtoName)) {
185
+ const expectedTypesList = `${expectedRequestDtoName.base}, ${expectedRequestDtoName.create}, or ${expectedRequestDtoName.update}`;
186
+ context.report({ node: param.typeAnnotation, messageId: "invalidParameterType", data: { expectedType: expectedTypesList, actualType: actualType || "unknown" } });
187
+ }
188
+ }
189
+
190
+ const body = node.value.body?.body;
191
+ if (!body || !Array.isArray(body)) {
192
+ return;
193
+ }
194
+
195
+ let dtoVarName = null;
196
+ const hasDtoInstantiation = body.some((stmt) => {
197
+ if (stmt.type === "VariableDeclaration") {
198
+ return stmt.declarations.some((decl) => {
199
+ if (decl.init?.type === "NewExpression" && decl.init.callee?.name === className) {
200
+ if (decl.id?.name !== "dto") {
201
+ context.report({ node: decl.id, messageId: "wrongVariableName", data: { actualName: decl.id?.name || "unknown", className } });
202
+ return false;
203
+ }
204
+ dtoVarName = "dto";
205
+ return true;
206
+ }
207
+ return false;
208
+ });
209
+ }
210
+ return false;
211
+ });
212
+
213
+ if (!hasDtoInstantiation) {
214
+ context.report({ node: node.value.body, messageId: "missingDtoInstantiation", data: { className } });
215
+ }
216
+
217
+ const dtoProperties = getEntityDtoProperties(filename);
218
+ const requiredProperties = dtoProperties.filter((prop) => !AUTO_MANAGED_FIELDS.includes(prop.name));
219
+
220
+ const assignedProperties = getAssignedProperties(body);
221
+
222
+ requiredProperties.forEach((prop) => {
223
+ if (!assignedProperties.has(prop.name)) {
224
+ context.report({ node: node.value.body, messageId: "missingPropertyAssignment", data: { propertyName: prop.name } });
225
+ }
226
+ });
227
+
228
+ AUTO_MANAGED_FIELDS.forEach((fieldName) => {
229
+ body.forEach((statement) => {
230
+ if (isDirectAssignment(statement, fieldName)) {
231
+ context.report({ node: statement, messageId: "autoFieldDirectAssignment", data: { fieldName } });
232
+ }
233
+ });
234
+ });
235
+
236
+ const returnStatement = body.find((stmt) => stmt.type === "ReturnStatement");
237
+ if (!returnStatement || returnStatement.argument?.name !== "dto") {
238
+ context.report({ node: node.value.body, messageId: "missingReturn" });
239
+ }
240
+
241
+ if (node.value.returnType) {
242
+ const returnTypeAnnotation = node.value.returnType.typeAnnotation;
243
+ let actualReturnType = "";
244
+
245
+ if (returnTypeAnnotation.type === "TSTypeReference") {
246
+ actualReturnType = returnTypeAnnotation.typeName.name;
247
+ }
248
+
249
+ if (actualReturnType !== className) {
250
+ context.report({ node: node.value.returnType, messageId: "invalidReturnType", data: { className, actualType: actualReturnType || "unknown" } });
251
+ }
252
+ }
253
+ },
254
+ };
255
+ },
256
+ };
257
+
258
+ export default {
259
+ rules: {
260
+ "enforce-entity-dto-fromrequestdto-structure": enforceEntityDtoFromRequestDtoStructureRule,
261
+ },
262
+ };