@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,118 @@
1
+ export default {
2
+ meta: {
3
+ type: "suggestion",
4
+ docs: {
5
+ description: "Enforce explicit type annotations in DTO classes",
6
+ category: "Best Practices",
7
+ recommended: true,
8
+ },
9
+ fixable: "code",
10
+ schema: [],
11
+ messages: {
12
+ missingExplicitType: "DTO property '{{propertyName}}' must have explicit type annotation",
13
+ missingTypeAnnotation: "Property '{{propertyName}}' in DTO class must have explicit type annotation",
14
+ },
15
+ },
16
+
17
+ create(context) {
18
+ return {
19
+ ClassDeclaration(node) {
20
+ // Prüfe nur DTO-Klassen (endet mit 'Dto' oder 'EntityDto')
21
+ if (!node.id || !node.id.name.endsWith('Dto')) {
22
+ return;
23
+ }
24
+
25
+ node.body.body.forEach(member => {
26
+ if (member.type === "PropertyDefinition" && member.key?.name) {
27
+ const propertyName = member.key.name;
28
+
29
+ // Überspringe private Properties (beginnen mit _)
30
+ if (propertyName.startsWith('_')) {
31
+ return;
32
+ }
33
+
34
+ // Überspringe statische Properties
35
+ if (member.static) {
36
+ return;
37
+ }
38
+
39
+ // Prüfe ob explizite Typ-Annotation vorhanden ist
40
+ if (!member.typeAnnotation) {
41
+ context.report({
42
+ node: member,
43
+ messageId: "missingExplicitType",
44
+ data: { propertyName },
45
+ fix(fixer) {
46
+ // Versuche den Typ aus dem Initialwert abzuleiten
47
+ const inferredType = inferTypeFromValue(member.value);
48
+ if (inferredType !== "unknown") {
49
+ return fixer.insertTextAfter(member.key, `: ${inferredType}`);
50
+ }
51
+ return null;
52
+ },
53
+ });
54
+ }
55
+ }
56
+ });
57
+ },
58
+
59
+ ClassExpression(node) {
60
+ // Behandle auch anonyme Klassen
61
+ if (node.id && node.id.name && node.id.name.endsWith('Dto')) {
62
+ node.body.body.forEach(member => {
63
+ if (member.type === "PropertyDefinition" && member.key?.name) {
64
+ const propertyName = member.key.name;
65
+
66
+ if (propertyName.startsWith('_') || member.static) {
67
+ return;
68
+ }
69
+
70
+ if (!member.typeAnnotation) {
71
+ context.report({
72
+ node: member,
73
+ messageId: "missingExplicitType",
74
+ data: { propertyName },
75
+ fix(fixer) {
76
+ const inferredType = inferTypeFromValue(member.value);
77
+ if (inferredType !== "unknown") {
78
+ return fixer.insertTextAfter(member.key, `: ${inferredType}`);
79
+ }
80
+ return null;
81
+ },
82
+ });
83
+ }
84
+ }
85
+ });
86
+ }
87
+ },
88
+ };
89
+ },
90
+ };
91
+
92
+ // Hilfsfunktion um Typ aus Initialwert abzuleiten
93
+ function inferTypeFromValue(value) {
94
+ if (!value) return "unknown";
95
+
96
+ switch (value.type) {
97
+ case "StringLiteral":
98
+ return "string";
99
+ case "NumericLiteral":
100
+ return "number";
101
+ case "BooleanLiteral":
102
+ return "boolean";
103
+ case "ArrayExpression":
104
+ return "array";
105
+ case "ObjectExpression":
106
+ return "object";
107
+ case "NewExpression":
108
+ return "object";
109
+ case "Identifier":
110
+ if (value.name === "true" || value.name === "false") return "boolean";
111
+ if (value.name === "null") return "null";
112
+ if (value.name === "undefined") return "undefined";
113
+ return "unknown";
114
+ default:
115
+ return "unknown";
116
+ }
117
+ }
118
+
@@ -0,0 +1,62 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Enforce EntityDto.fromRequestDto() usage in services and validate method existence",
6
+ category: "Best Practices",
7
+ recommended: true,
8
+ },
9
+ messages: {
10
+ useFromRequestDto: "Services müssen EntityDto.fromRequestDto(requestDto) verwenden statt repository.create(). Verwende das DTO-Pattern für typsichere Entity-Erstellung.",
11
+ noDirectRequestDtoProperties: "RequestDto-Properties dürfen nicht direkt an Entity zugewiesen werden. Verwende EntityDto.fromRequestDto() für typsichere Konvertierung.",
12
+ missingFromRequestDtoMethod: "EntityDto '{{entityDtoName}}' muss eine statische fromRequestDto() Methode haben, wenn sie im Service aufgerufen wird. Füge die Methode hinzu oder verwende ein anderes Pattern.",
13
+ },
14
+ schema: [],
15
+ },
16
+ create (context) {
17
+ const filename = context.getFilename();
18
+
19
+ if (!filename.includes("/service/") && !filename.includes("Service.ts")) {
20
+ return {};
21
+ }
22
+
23
+ function isInMergeOrLoadMethod (node) {
24
+ let current = node;
25
+ while (current && current.parent) {
26
+ if (current.type === "MethodDefinition" &&
27
+ current.key?.name) {
28
+ const methodName = current.key.name;
29
+ if (methodName.startsWith("merge") ||
30
+ (methodName.startsWith("load") && methodName.endsWith("EntityDto"))) {
31
+ return true;
32
+ }
33
+ }
34
+ current = current.parent;
35
+ }
36
+ return false;
37
+ }
38
+
39
+ return {
40
+ CallExpression (node) {
41
+ if (isInMergeOrLoadMethod(node)) {
42
+ return;
43
+ }
44
+
45
+ if (
46
+ node.callee.type === "MemberExpression" &&
47
+ node.callee.property.name === "create" &&
48
+ node.callee.object.name?.toLowerCase().includes("repository")
49
+ ) {
50
+ const args = node.arguments;
51
+ if (args.length > 0 && args[0].type === "ObjectExpression") {
52
+ context.report({
53
+ messageId: "useFromRequestDto",
54
+ node,
55
+ });
56
+ }
57
+ }
58
+ },
59
+ };
60
+ },
61
+ };
62
+
@@ -0,0 +1,71 @@
1
+ export default {
2
+ meta: {
3
+ type: "problem",
4
+ docs: {
5
+ description: "Generic Entity-DTOs benötigen spezielle Behandlung für Conditional Types",
6
+ category: "Architecture",
7
+ recommended: true,
8
+ },
9
+ schema: [],
10
+ messages: {
11
+ genericEntityDtoDetected: "Generic Entity-DTO '{{dtoName}}' erkannt. Type-Matching-Regeln werden für diese DTO umgangen.",
12
+ missingGenericFromEntityMethod: "Generic Entity-DTO '{{dtoName}}' muss eine fromEntity-Methode haben, die mit allen Generic-Typen umgehen kann.",
13
+ },
14
+ },
15
+ create(context) {
16
+ const filename = context.getFilename();
17
+
18
+ // Prüfe, ob es sich um eine Generic-Entity-DTO handelt
19
+ const isGenericEntityDto = filename.includes("BackpackRefItemEntityDto") ||
20
+ filename.includes("GenericEntityDto") ||
21
+ filename.includes("test-fixtures");
22
+
23
+ if (!isGenericEntityDto) {
24
+ return {};
25
+ }
26
+
27
+ let classNode = null;
28
+ let dtoClassName = "";
29
+
30
+ return {
31
+ ClassDeclaration(node) {
32
+ classNode = node;
33
+ dtoClassName = node.id?.name;
34
+ },
35
+ ExportDefaultDeclaration(node) {
36
+ if (node.declaration.type === "ClassDeclaration") {
37
+ classNode = node.declaration;
38
+ dtoClassName = node.declaration.id?.name;
39
+ }
40
+ },
41
+ "Program:exit"(node) {
42
+ if (!classNode || !dtoClassName) return;
43
+
44
+ const methods = classNode.body.body.filter(member =>
45
+ member.type === "MethodDefinition" &&
46
+ member.static === true
47
+ );
48
+
49
+ const hasFromEntityMethod = methods.some(method =>
50
+ method.key?.name === "fromEntity"
51
+ );
52
+
53
+ // Für Generic-Entity-DTOs sind die Standard-Regeln gelockert
54
+ if (isGenericEntityDto) {
55
+ if (!hasFromEntityMethod) {
56
+ context.report({
57
+ node: classNode,
58
+ messageId: "missingGenericFromEntityMethod",
59
+ data: { dtoName: dtoClassName }
60
+ });
61
+ }
62
+ }
63
+ },
64
+ };
65
+ },
66
+ };
67
+
68
+
69
+
70
+
71
+
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+
3
+ export default {
4
+ meta: {
5
+ type: "problem",
6
+ docs: {
7
+ description: "Enforce @Inject() decorator for all service dependencies in constructor parameters",
8
+ category: "Best Practices",
9
+ recommended: true,
10
+ },
11
+ messages: {
12
+ missingInjectDecorator: "Dependency '{{serviceName}}' in constructor parameter must use @Inject({{serviceName}}) decorator for proper NestJS dependency injection.",
13
+ invalidInjectUsage: "@Inject() decorator should only be used on constructor parameters with dependency names (Service, Repository, Factory, dataSource, logger).",
14
+ },
15
+ fixable: "code",
16
+ schema: [],
17
+ },
18
+
19
+ create(context) {
20
+ const filename = context.getFilename();
21
+
22
+ // Nur in Controller-, Service- und Factory-Dateien anwenden
23
+ if ((!filename.includes("/controller/") && !filename.includes("/service/")) ||
24
+ (!filename.includes("Controller") && !filename.includes("Service") && !filename.includes("Factory"))) {
25
+ return {};
26
+ }
27
+
28
+ let hasInjectImport = false;
29
+
30
+ return {
31
+ ImportDeclaration(node) {
32
+ if (node.source.value === "@nestjs/common") {
33
+ const hasInject = node.specifiers.some(
34
+ spec => spec.type === "ImportSpecifier" && spec.imported.name === "Inject"
35
+ );
36
+ if (hasInject) {
37
+ hasInjectImport = true;
38
+ }
39
+ }
40
+ },
41
+
42
+ ClassDeclaration(node) {
43
+ // Nur für Controller-, Service- und Factory-Klassen
44
+ if (!node.id || (!node.id.name.endsWith("Controller") && !node.id.name.endsWith("Service") && !node.id.name.endsWith("Factory"))) {
45
+ return;
46
+ }
47
+
48
+ // Finde den Constructor
49
+ const constructor = node.body.body.find(
50
+ (member) => member.type === "MethodDefinition" && member.key.name === "constructor"
51
+ );
52
+
53
+ if (!constructor || !constructor.value) {
54
+ return;
55
+ }
56
+
57
+ // Prüfe Constructor-Parameter
58
+ constructor.value.params.forEach((param, index) => {
59
+ if (param.type !== "TSParameterProperty") {
60
+ return;
61
+ }
62
+
63
+ // ALLE Dependencies benötigen @Inject() in diesem Projekt
64
+ const paramName = param.parameter.name;
65
+ const isDependency = paramName.endsWith("Service") ||
66
+ paramName.endsWith("Repository") ||
67
+ paramName.endsWith("Factory") ||
68
+ paramName === "dataSource" ||
69
+ paramName === "logger";
70
+
71
+ if (!isDependency) {
72
+ return;
73
+ }
74
+
75
+ // Prüfe, ob @Inject() Decorator vorhanden ist
76
+ const hasInjectDecorator = param.decorators &&
77
+ param.decorators.some(decorator =>
78
+ decorator.expression &&
79
+ decorator.expression.callee &&
80
+ decorator.expression.callee.name === "Inject"
81
+ );
82
+
83
+ if (!hasInjectDecorator) {
84
+ const typeAnnotation = param.parameter.typeAnnotation;
85
+ const typeName = typeAnnotation && typeAnnotation.typeAnnotation && typeAnnotation.typeAnnotation.typeName
86
+ ? typeAnnotation.typeAnnotation.typeName.name
87
+ : paramName.charAt(0).toUpperCase() + paramName.slice(1);
88
+
89
+ context.report({
90
+ node: param,
91
+ messageId: "missingInjectDecorator",
92
+ data: { serviceName: paramName },
93
+ fix(fixer) {
94
+ const sourceCode = context.getSourceCode();
95
+ const paramText = sourceCode.getText(param);
96
+ const injectDecorator = paramName === "dataSource"
97
+ ? "@Inject(DataSource) "
98
+ : `@Inject(${typeName}) `;
99
+
100
+ return fixer.replaceText(param, injectDecorator + paramText);
101
+ },
102
+ });
103
+ }
104
+ });
105
+ },
106
+
107
+ // @Inject() ist optional für normale Services, aber sollte nicht auf anderen Parametern verwendet werden
108
+ Decorator(node) {
109
+ if (node.expression && node.expression.callee && node.expression.callee.name === "Inject") {
110
+ // Finde den Parameter, auf den dieser Decorator angewendet wird
111
+ const parent = node.parent;
112
+
113
+ if (parent && parent.type === "TSParameterProperty") {
114
+ const paramName = parent.parameter.name;
115
+ const isValidInjectTarget = paramName.endsWith("Service") ||
116
+ paramName.endsWith("Repository") ||
117
+ paramName.endsWith("Factory") ||
118
+ paramName === "dataSource" ||
119
+ paramName === "logger";
120
+
121
+ if (!isValidInjectTarget) {
122
+ context.report({
123
+ node: node,
124
+ messageId: "invalidInjectUsage",
125
+ data: { paramName },
126
+ });
127
+ }
128
+ }
129
+ }
130
+ },
131
+ };
132
+ },
133
+ };
@@ -0,0 +1,170 @@
1
+ /**
2
+ * @fileoverview Enforce lazy type loading in @ApiProperty decorators to prevent circular import issues
3
+ * @author Echoes of Order Team
4
+ */
5
+
6
+ export default {
7
+ meta: {
8
+ type: "problem",
9
+ docs: {
10
+ description: "Enforce lazy type loading in @ApiProperty decorators to prevent circular import issues",
11
+ category: "Best Practices",
12
+ recommended: true,
13
+ },
14
+ fixable: "code",
15
+ schema: [],
16
+ messages: {
17
+ lazyTypeRequired: "Use lazy type loading 'type: () => [{{className}}]' instead of 'type: [{{className}}]' to prevent circular import issues",
18
+ lazyTypeRequiredSingle: "Use lazy type loading 'type: () => {{className}}' instead of 'type: {{className}}' to prevent circular import issues",
19
+ },
20
+ },
21
+
22
+ create(context) {
23
+ const sourceCode = context.getSourceCode();
24
+
25
+ /**
26
+ * Check if a node is an @ApiProperty decorator
27
+ */
28
+ function isApiPropertyDecorator(node) {
29
+ return (
30
+ node.type === "CallExpression" &&
31
+ node.callee &&
32
+ node.callee.type === "Identifier" &&
33
+ node.callee.name === "ApiProperty"
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Check if a property is a DTO class reference
39
+ */
40
+ function isDtoClassReference(property) {
41
+ if (!property || property.type !== "Identifier") {
42
+ return false;
43
+ }
44
+
45
+ // Check if it ends with Dto (case insensitive)
46
+ return /dto$/i.test(property.name);
47
+ }
48
+
49
+ /**
50
+ * Check if a property is an array of DTO classes
51
+ */
52
+ function isDtoArrayReference(property) {
53
+ if (!property || property.type !== "ArrayExpression") {
54
+ return false;
55
+ }
56
+
57
+ if (property.elements.length !== 1) {
58
+ return false;
59
+ }
60
+
61
+ return isDtoClassReference(property.elements[0]);
62
+ }
63
+
64
+ /**
65
+ * Get the DTO class name from a property
66
+ */
67
+ function getDtoClassName(property) {
68
+ if (property.type === "Identifier") {
69
+ return property.name;
70
+ }
71
+ if (property.type === "ArrayExpression" && property.elements.length === 1) {
72
+ return property.elements[0].name;
73
+ }
74
+ return null;
75
+ }
76
+
77
+ /**
78
+ * Check if type property already uses lazy loading
79
+ */
80
+ function isLazyTypeLoading(property) {
81
+ return (
82
+ property.type === "ArrowFunctionExpression" &&
83
+ property.params.length === 0 &&
84
+ property.body &&
85
+ (property.body.type === "ArrayExpression" || property.body.type === "Identifier")
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Create fix for lazy type loading
91
+ */
92
+ function createFix(node, property, isArray) {
93
+ const className = getDtoClassName(property);
94
+ if (!className) return null;
95
+
96
+ const newType = isArray
97
+ ? `() => [${className}]`
98
+ : `() => ${className}`;
99
+
100
+ return (fixer) => {
101
+ const propertyText = sourceCode.getText(property);
102
+ const newPropertyText = newType;
103
+
104
+ return fixer.replaceText(property, newPropertyText);
105
+ };
106
+ }
107
+
108
+ return {
109
+ CallExpression(node) {
110
+ if (!isApiPropertyDecorator(node)) {
111
+ return;
112
+ }
113
+
114
+ // Find the type property in the decorator arguments
115
+ const firstArg = node.arguments[0];
116
+ if (!firstArg || firstArg.type !== "ObjectExpression") {
117
+ return;
118
+ }
119
+
120
+ const typeProperty = firstArg.properties.find(
121
+ (prop) =>
122
+ prop.type === "Property" &&
123
+ prop.key &&
124
+ prop.key.type === "Identifier" &&
125
+ prop.key.name === "type"
126
+ );
127
+
128
+ if (!typeProperty || !typeProperty.value) {
129
+ return;
130
+ }
131
+
132
+ const typeValue = typeProperty.value;
133
+
134
+ // Skip if already using lazy loading
135
+ if (isLazyTypeLoading(typeValue)) {
136
+ return;
137
+ }
138
+
139
+ // Check for array of DTOs
140
+ if (isDtoArrayReference(typeValue)) {
141
+ const className = getDtoClassName(typeValue);
142
+ context.report({
143
+ node: typeValue,
144
+ messageId: "lazyTypeRequired",
145
+ data: { className },
146
+ fix: createFix(node, typeValue, true),
147
+ });
148
+ return;
149
+ }
150
+
151
+ // Check for single DTO reference
152
+ if (isDtoClassReference(typeValue)) {
153
+ const className = getDtoClassName(typeValue);
154
+ context.report({
155
+ node: typeValue,
156
+ messageId: "lazyTypeRequiredSingle",
157
+ data: { className },
158
+ fix: createFix(node, typeValue, false),
159
+ });
160
+ return;
161
+ }
162
+ },
163
+ };
164
+ },
165
+ };
166
+
167
+
168
+
169
+
170
+