@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,127 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const noDtoDuplicatesRule = {
5
+ meta: {
6
+ type: "problem",
7
+ docs: {
8
+ description: "Prevents duplicate DTO class names across the dto directory",
9
+ category: "Architecture",
10
+ recommended: true,
11
+ },
12
+ schema: [],
13
+ messages: {
14
+ dtoDuplicateFound: "DTO-Klasse '{{className}}' existiert bereits in '{{existingFile}}'. Duplikate sind nicht erlaubt. Aktuelle Datei: '{{currentFile}}'. Entscheide selbst, welche Datei wir behalten! Lösche alle unnötigen - passe ggf. den Namen der Datei+Klasse an und korrigiere die Imports.",
15
+ },
16
+ },
17
+ create(context) {
18
+ return {
19
+ ClassDeclaration(node) {
20
+ const filename = context.getFilename();
21
+ const className = node.id?.name;
22
+
23
+ if (!className) return;
24
+
25
+ // Prüfe nur Dateien im dto-Verzeichnis
26
+ if (!filename.includes("/dto/") || !filename.endsWith(".ts")) {
27
+ return;
28
+ }
29
+
30
+ // Prüfe, ob es sich um eine DTO-Klasse handelt (endet mit "Dto")
31
+ if (!className.endsWith("Dto")) {
32
+ return;
33
+ }
34
+
35
+ // In Tests verwenden wir gemockte Dateien
36
+
37
+ // Suche nach anderen Dateien mit dem gleichen Klassennamen
38
+ const dtoDirectory = findDtoDirectory(filename);
39
+ if (!dtoDirectory) {
40
+ return;
41
+ }
42
+
43
+ const duplicateFiles = findDuplicateFiles(dtoDirectory, className, filename);
44
+
45
+ if (duplicateFiles.length > 0) {
46
+ // Berichte den ersten gefundenen Duplikat
47
+ const existingFile = duplicateFiles[0];
48
+ context.report({
49
+ node: node,
50
+ messageId: "dtoDuplicateFound",
51
+ data: {
52
+ className: className,
53
+ currentFile: filename,
54
+ existingFile: existingFile
55
+ },
56
+ });
57
+ }
58
+ },
59
+ };
60
+ },
61
+ };
62
+
63
+ // Hilfsmethode um das dto-Verzeichnis zu finden
64
+ function findDtoDirectory(filename) {
65
+ const pathParts = filename.split(path.sep);
66
+ const dtoIndex = pathParts.findIndex(part => part === "dto");
67
+
68
+ if (dtoIndex === -1) {
69
+ return null;
70
+ }
71
+
72
+ // Konstruiere den Pfad bis zum dto-Verzeichnis
73
+ return pathParts.slice(0, dtoIndex + 1).join(path.sep);
74
+ }
75
+
76
+ // Hilfsmethode um Duplikate zu finden
77
+ function findDuplicateFiles(dtoDirectory, className, currentFile) {
78
+ const duplicateFiles = [];
79
+
80
+ try {
81
+ // Rekursiv durchsuche das dto-Verzeichnis
82
+ const searchDirectory = (dir) => {
83
+ if (!fs.existsSync(dir)) {
84
+ return;
85
+ }
86
+
87
+ const items = fs.readdirSync(dir);
88
+
89
+ for (const item of items) {
90
+ const fullPath = path.join(dir, item);
91
+ const stat = fs.statSync(fullPath);
92
+
93
+ if (stat.isDirectory()) {
94
+ // Rekursiv in Unterverzeichnisse suchen
95
+ searchDirectory(fullPath);
96
+ } else if (stat.isFile() && item.endsWith('.ts')) {
97
+ // Überspringe die aktuelle Datei
98
+ if (fullPath === currentFile) {
99
+ continue;
100
+ }
101
+
102
+ try {
103
+ const content = fs.readFileSync(fullPath, "utf8");
104
+
105
+ // Suche nach der Klasse mit dem gleichen Namen
106
+ const classRegex = new RegExp(`export\\s+(default\\s+)?class\\s+${className}\\b`);
107
+ if (classRegex.test(content)) {
108
+ duplicateFiles.push(fullPath);
109
+ }
110
+ } catch (error) {
111
+ // Ignoriere Dateien, die nicht gelesen werden können
112
+ continue;
113
+ }
114
+ }
115
+ }
116
+ };
117
+
118
+ searchDirectory(dtoDirectory);
119
+ } catch (error) {
120
+ // Ignoriere Fehler beim Durchsuchen der Dateien
121
+ return [];
122
+ }
123
+
124
+ return duplicateFiles;
125
+ }
126
+
127
+ export default noDtoDuplicatesRule;
@@ -0,0 +1,99 @@
1
+
2
+ /**
3
+ * ESLint-Regel: Verhindert die Verwendung von DTOs in Entity-Dateien
4
+ * Für JSONB-Felder müssen eigene Typen definiert werden
5
+ */
6
+ export default {
7
+ meta: {
8
+ type: "problem",
9
+ docs: {
10
+ description: "Verhindert die Verwendung von DTOs in Entity-Dateien. Für JSONB-Felder müssen eigene Typen definiert werden.",
11
+ category: "Architecture",
12
+ recommended: true,
13
+ },
14
+ hasSuggestions: true,
15
+ schema: [],
16
+ messages: {
17
+ dtoInEntity: "Entity '{{entityName}}' verwendet DTO '{{dtoName}}' in Property '{{propertyName}}'. Für JSONB-Felder müssen eigene Typen definiert werden.",
18
+ suggestType: "Definiere einen eigenen Typ für '{{propertyName}}' anstatt '{{dtoName}}' zu verwenden.",
19
+ },
20
+ },
21
+
22
+ create(context) {
23
+ const filename = context.getFilename();
24
+
25
+ // Regel nur auf Entity-Dateien im backend anwenden
26
+ if (!filename.includes("/backend/src/entity/") || !filename.endsWith(".ts")) {
27
+ return {};
28
+ }
29
+
30
+ // Prüfe, ob es sich um eine Entity-Datei handelt
31
+ const isEntityFile = filename.includes("/entity/") &&
32
+ !filename.includes("/dto/") &&
33
+ !filename.includes("/service/") &&
34
+ !filename.includes("/controller/");
35
+
36
+ if (!isEntityFile) {
37
+ return {};
38
+ }
39
+
40
+ return {
41
+ ClassDeclaration(node) {
42
+ if (!node.id?.name) return;
43
+
44
+ const className = node.id.name;
45
+
46
+ // Prüfe alle Properties der Klasse
47
+ node.body.body.forEach(member => {
48
+ if (member.type === "PropertyDefinition" && member.key?.name) {
49
+ const propertyName = member.key.name;
50
+
51
+ // Prüfe Type-Annotation
52
+ if (member.typeAnnotation?.typeAnnotation) {
53
+ const typeAnnotation = member.typeAnnotation.typeAnnotation;
54
+
55
+ // Prüfe auf DTO-Verwendung
56
+ if (typeAnnotation.type === "TSTypeReference") {
57
+ const typeName = typeAnnotation.typeName?.name;
58
+
59
+ // Prüfe, ob der Typ ein DTO ist (endet mit "Dto" oder "EntityDto")
60
+ if (typeName && (typeName.endsWith("Dto") || typeName.endsWith("EntityDto"))) {
61
+ context.report({
62
+ node: member,
63
+ messageId: "dtoInEntity",
64
+ data: {
65
+ entityName: className,
66
+ dtoName: typeName,
67
+ propertyName: propertyName,
68
+ },
69
+ });
70
+ }
71
+ }
72
+
73
+ // Prüfe Union-Types
74
+ if (typeAnnotation.type === "TSUnionType") {
75
+ typeAnnotation.types.forEach(unionType => {
76
+ if (unionType.type === "TSTypeReference") {
77
+ const typeName = unionType.typeName?.name;
78
+
79
+ if (typeName && (typeName.endsWith("Dto") || typeName.endsWith("EntityDto"))) {
80
+ context.report({
81
+ node: member,
82
+ messageId: "dtoInEntity",
83
+ data: {
84
+ entityName: className,
85
+ dtoName: typeName,
86
+ propertyName: propertyName,
87
+ },
88
+ });
89
+ }
90
+ }
91
+ });
92
+ }
93
+ }
94
+ }
95
+ });
96
+ },
97
+ };
98
+ },
99
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @fileoverview Verbietet import("...").Type Syntax in Typ-Annotationen
3
+ * @author Echoes of Order Team
4
+ */
5
+
6
+ export default {
7
+ meta: {
8
+ type: "problem",
9
+ docs: {
10
+ description: "Verbietet import(\"...\").Type Syntax in Typ-Annotationen. Verwende stattdessen explizite Imports.",
11
+ category: "Best Practices",
12
+ recommended: true,
13
+ },
14
+ messages: {
15
+ noDynamicImportInType: "import(\"{{path}}\").{{type}} ist verboten. Importiere '{{type}}' stattdessen direkt: import {{type}} from \"{{path}}\";",
16
+ },
17
+ schema: [],
18
+ },
19
+
20
+ create (context) {
21
+ function checkForDynamicImport (node) {
22
+ if (
23
+ node.type === "TSImportType" &&
24
+ node.argument &&
25
+ node.argument.type === "Literal" &&
26
+ typeof node.argument.value === "string"
27
+ ) {
28
+ const importPath = node.argument.value;
29
+ const typeName = node.qualifier?.name || node.qualifier?.right?.name || "Type";
30
+
31
+ context.report({
32
+ messageId: "noDynamicImportInType",
33
+ node,
34
+ data: {
35
+ path: importPath,
36
+ type: typeName,
37
+ },
38
+ });
39
+ }
40
+ }
41
+
42
+ return {
43
+ TSImportType (node) {
44
+ checkForDynamicImport(node);
45
+ },
46
+ TSTypeReference (node) {
47
+ if (node.typeName && node.typeName.type === "TSQualifiedName") {
48
+ const current = node.typeName;
49
+ if (
50
+ current.left.type === "TSImportType" &&
51
+ current.left.argument &&
52
+ current.left.argument.type === "Literal"
53
+ ) {
54
+ const importPath = current.left.argument.value;
55
+ const typeName = current.right.name;
56
+
57
+ context.report({
58
+ messageId: "noDynamicImportInType",
59
+ node,
60
+ data: {
61
+ path: importPath,
62
+ type: typeName,
63
+ },
64
+ });
65
+ }
66
+ }
67
+ },
68
+ };
69
+ },
70
+ };
71
+
@@ -0,0 +1,95 @@
1
+ /**
2
+ * ESLint-Regel: no-dynamic-imports-in-controllers
3
+ * Verbotet dynamische Imports in Controller-Dateien
4
+ */
5
+
6
+ /** @type {import('eslint').Rule.RuleModule} */
7
+ const noDynamicImportsInControllersRule = {
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "Verbotet dynamische Imports in Controller-Dateien",
12
+ category: "Best Practices",
13
+ recommended: true,
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ noDynamicImport: "Dynamic imports inside classes are not allowed. Import the class at top level and instantiate it with 'new' instead.",
18
+ },
19
+ },
20
+ create(context) {
21
+ let classDepth = 0;
22
+ const reported = new WeakSet();
23
+
24
+ const isImportCall = (node) => {
25
+ return (
26
+ node &&
27
+ node.type === "CallExpression" &&
28
+ ((node.callee && node.callee.type === "Identifier" && node.callee.name === "import") ||
29
+ (node.callee && node.callee.type === "Import"))
30
+ );
31
+ };
32
+
33
+ const isImportExpression = (node) => {
34
+ return node && (node.type === "ImportExpression" || isImportCall(node));
35
+ };
36
+
37
+ const findInnerImport = (node) => {
38
+ if (!node) return null;
39
+ if (node.type === "ImportExpression") return node;
40
+ if (isImportCall(node)) return node;
41
+ if (node.type === "AwaitExpression") return findInnerImport(node.argument);
42
+ if (node.type === "ParenthesizedExpression") return findInnerImport(node.expression);
43
+ if (node.type === "MemberExpression") return findInnerImport(node.object);
44
+ return null;
45
+ };
46
+
47
+ const reportOnceIfInsideClass = (importNode) => {
48
+ if (!importNode) return;
49
+ if (classDepth <= 0) return;
50
+ if (reported.has(importNode)) return;
51
+ reported.add(importNode);
52
+ context.report({ node: importNode, messageId: "noDynamicImport" });
53
+ };
54
+
55
+ return {
56
+ ClassBody () {
57
+ classDepth += 1;
58
+ },
59
+ "ClassBody:exit" () {
60
+ classDepth -= 1;
61
+ },
62
+
63
+ ImportExpression (node) {
64
+ reportOnceIfInsideClass(node);
65
+ },
66
+
67
+ CallExpression (node) {
68
+ if (isImportCall(node)) {
69
+ reportOnceIfInsideClass(node);
70
+ }
71
+ },
72
+
73
+ AwaitExpression (node) {
74
+ const inner = findInnerImport(node);
75
+ reportOnceIfInsideClass(inner);
76
+ },
77
+
78
+ ParenthesizedExpression (node) {
79
+ const inner = findInnerImport(node);
80
+ reportOnceIfInsideClass(inner);
81
+ },
82
+
83
+ MemberExpression (node) {
84
+ const inner = findInnerImport(node);
85
+ reportOnceIfInsideClass(inner);
86
+ },
87
+ };
88
+ },
89
+ };
90
+
91
+ export default {
92
+ rules: {
93
+ "no-dynamic-imports-in-controllers": noDynamicImportsInControllersRule,
94
+ },
95
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * ESLint-Regel: Verbietet den Import von Entities in Controllern
3
+ * Controller sollten nur DTOs verwenden, nie direkt mit Entities arbeiten
4
+ */
5
+
6
+ /** @type {import('eslint').Rule.RuleModule} */
7
+ const noEntityImportsInControllersRule = {
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "Controller dürfen keine Entities importieren, nur DTOs verwenden",
12
+ category: "Architecture",
13
+ recommended: true,
14
+ },
15
+ hasSuggestions: true,
16
+ schema: [],
17
+ messages: {
18
+ noEntityImport: "Controller dürfen keine Entities importieren. Verwende stattdessen DTOs: '{{entityPath}}'",
19
+ useDto: "Ersetze den Entity-Import durch den entsprechenden DTO aus @/dto/",
20
+ },
21
+ },
22
+ create(context) {
23
+ const controllerFiles = /Controller\.ts$/;
24
+ const filename = context.getFilename();
25
+ const isControllerFile = controllerFiles.test(filename) &&
26
+ !/BaseController\.ts$/.test(filename) &&
27
+ filename.includes("/controller/");
28
+
29
+ if (!isControllerFile) return {};
30
+
31
+ return {
32
+ ImportDeclaration(node) {
33
+ const importPath = node.source.value;
34
+
35
+ // Prüfe auf Imports aus @/entity
36
+ if (typeof importPath === 'string' && importPath.startsWith('@/entity')) {
37
+ context.report({
38
+ node,
39
+ messageId: "noEntityImport",
40
+ data: {
41
+ entityPath: importPath,
42
+ },
43
+ suggest: [
44
+ {
45
+ desc: "Verwende stattdessen einen DTO aus @/dto/",
46
+ fix(fixer) {
47
+ // Extrahiere den Entity-Namen und schlage DTO-Import vor
48
+ const entityName = importPath.split('/').pop();
49
+ const dtoPath = importPath.replace('@/entity', '@/dto');
50
+ const suggestedDtoPath = dtoPath.replace(/Entity$/, 'Dto');
51
+
52
+ return fixer.replaceText(
53
+ node.source,
54
+ `"${suggestedDtoPath}"`
55
+ );
56
+ },
57
+ },
58
+ ],
59
+ });
60
+ return; // Verhindert doppelte Meldungen
61
+ }
62
+
63
+ // Prüfe auch auf relative Imports zu Entities (nur wenn nicht bereits @/entity gefunden)
64
+ if (typeof importPath === 'string' &&
65
+ (importPath.includes('../entity/') ||
66
+ importPath.includes('./entity/'))) {
67
+ context.report({
68
+ node,
69
+ messageId: "noEntityImport",
70
+ data: {
71
+ entityPath: importPath,
72
+ },
73
+ });
74
+ }
75
+ },
76
+
77
+ // Prüfe auch auf dynamische Imports
78
+ ImportExpression(node) {
79
+ if (node.source.type === 'Literal') {
80
+ const importPath = node.source.value;
81
+
82
+ if (typeof importPath === 'string' && importPath.startsWith('@/entity')) {
83
+ context.report({
84
+ node,
85
+ messageId: "noEntityImport",
86
+ data: {
87
+ entityPath: importPath,
88
+ },
89
+ });
90
+ }
91
+ }
92
+ },
93
+ };
94
+ },
95
+ };
96
+
97
+ export default {
98
+ rules: {
99
+ "no-entity-imports-in-controllers": noEntityImportsInControllersRule,
100
+ },
101
+ };
@@ -0,0 +1,139 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Prevent usage of Entity references in Swagger documentation",
6
+ category: "Best Practices",
7
+ recommended: true,
8
+ },
9
+ fixable: null,
10
+ schema: [],
11
+ messages: {
12
+ entityInSwaggerSchema: "Swagger schema reference '{{reference}}' should use DTO instead of Entity. Replace '{{entityName}}' with '{{dtoName}}'",
13
+ entityInApiProperty: "ApiProperty type '{{type}}' should use DTO instead of Entity. Replace '{{entityName}}' with '{{dtoName}}'",
14
+ entityInApiBody: "ApiBody type '{{type}}' should use DTO instead of Entity. Replace '{{entityName}}' with '{{dtoName}}'",
15
+ },
16
+ },
17
+ create(context) {
18
+ const entityPattern = /Entity$/;
19
+ const dtoPattern = /Dto$/;
20
+
21
+ function getEntityName(reference) {
22
+ const match = reference.match(/#\/components\/schemas\/(.+)/);
23
+ return match ? match[1] : reference;
24
+ }
25
+
26
+ function getDtoName(entityName) {
27
+ return entityName.replace(/Entity$/, "Dto");
28
+ }
29
+
30
+ function checkStringLiteral(node, messageId) {
31
+ if (node.type === "Literal" && typeof node.value === "string") {
32
+ const value = node.value;
33
+
34
+ // Check for schema references like "#/components/schemas/AbilityEntity"
35
+ if (value.includes("#/components/schemas/") && value.includes("Entity")) {
36
+ const entityName = getEntityName(value);
37
+ if (entityPattern.test(entityName)) {
38
+ const dtoName = getDtoName(entityName);
39
+ context.report({
40
+ node,
41
+ messageId,
42
+ data: {
43
+ reference: value,
44
+ entityName,
45
+ dtoName,
46
+ },
47
+ });
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ function checkTypeReference(node, messageId) {
54
+ // Check for type references in ApiProperty, ApiBody decorators
55
+ if (node.type === "Identifier" && entityPattern.test(node.name)) {
56
+ const dtoName = getDtoName(node.name);
57
+ context.report({
58
+ node,
59
+ messageId,
60
+ data: {
61
+ type: node.name,
62
+ entityName: node.name,
63
+ dtoName,
64
+ },
65
+ });
66
+ }
67
+ }
68
+
69
+ function checkObjectExpression(node) {
70
+ if (node.type === "ObjectExpression") {
71
+ node.properties.forEach((prop) => {
72
+ if (prop.type === "Property") {
73
+ // Check schema references in object properties
74
+ if (prop.key && prop.key.name === "$ref") {
75
+ checkStringLiteral(prop.value, "entityInSwaggerSchema");
76
+ }
77
+
78
+ // Check items property in arrays
79
+ if (prop.key && prop.key.name === "items" && prop.value.type === "ObjectExpression") {
80
+ prop.value.properties.forEach((itemProp) => {
81
+ if (itemProp.key && itemProp.key.name === "$ref") {
82
+ checkStringLiteral(itemProp.value, "entityInSwaggerSchema");
83
+ }
84
+ });
85
+ }
86
+ }
87
+ });
88
+ }
89
+ }
90
+
91
+ return {
92
+ Decorator(node) {
93
+ if (node.expression && node.expression.type === "CallExpression") {
94
+ const callee = node.expression.callee;
95
+
96
+ // Check ApiResponse, ApiBody, ApiProperty decorators
97
+ if (callee.type === "Identifier") {
98
+ const decoratorName = callee.name;
99
+
100
+ if (["ApiResponse", "ApiBody", "ApiProperty"].includes(decoratorName)) {
101
+ node.expression.arguments.forEach((arg) => {
102
+ if (arg.type === "ObjectExpression") {
103
+ arg.properties.forEach((prop) => {
104
+ if (prop.type === "Property") {
105
+ // Check type property
106
+ if (prop.key && prop.key.name === "type") {
107
+ checkTypeReference(prop.value, "entityInApiProperty");
108
+ }
109
+
110
+ // Check schema property
111
+ if (prop.key && prop.key.name === "schema") {
112
+ checkObjectExpression(prop.value);
113
+ }
114
+ }
115
+ });
116
+ }
117
+ });
118
+ }
119
+ }
120
+ }
121
+ },
122
+
123
+ // Check for $ref in any string literal
124
+ Literal(node) {
125
+ if (typeof node.value === "string" && node.value.includes("Entity")) {
126
+ const parent = node.parent;
127
+
128
+ // Check if this is in a swagger context
129
+ if (parent && parent.type === "Property" && parent.key) {
130
+ if (parent.key.name === "$ref" ||
131
+ (parent.key.type === "Literal" && parent.key.value === "$ref")) {
132
+ checkStringLiteral(node, "entityInSwaggerSchema");
133
+ }
134
+ }
135
+ }
136
+ },
137
+ };
138
+ },
139
+ };