@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.
- package/CHANGELOG.md +1093 -0
- package/configs/.gitkeep +1 -0
- package/configs/admin.js +203 -0
- package/configs/api-client.js +46 -0
- package/configs/backend.js +895 -0
- package/configs/domains.js +123 -0
- package/configs/frontend.js +30 -0
- package/configs/image-server.js +26 -0
- package/configs/ionos-proxy.js +372 -0
- package/configs/nestjs.js +156 -0
- package/configs/node.js +92 -0
- package/configs/react.js +111 -0
- package/configs/wiki.js +42 -0
- package/index.js +39 -0
- package/package.json +85 -0
- package/rules/.gitkeep +1 -0
- package/rules/__tests__/analyze-relation-usage.test.js.disabled +300 -0
- package/rules/__tests__/complexity.test.js.disabled +300 -0
- package/rules/__tests__/enforce-dto-factory-in-services.integration.test.js +226 -0
- package/rules/__tests__/enforce-dto-factory-in-services.test.js +177 -0
- package/rules/__tests__/enforce-entity-dto-create-no-id.integration.test.js +18 -0
- package/rules/__tests__/enforce-function-argument-count.test.js.disabled +300 -0
- package/rules/__tests__/enforce-repository-token-handling.test.js +58 -0
- package/rules/__tests__/english-only-code-strings.test.js.disabled +300 -0
- package/rules/__tests__/eslint-rules.integration.test.ts +350 -0
- package/rules/__tests__/integration-test-controller-response-dto.js +261 -0
- package/rules/__tests__/integration-test-dto-factory-in-services.js +260 -0
- package/rules/__tests__/integration-test-no-entity-type-casting.js +161 -0
- package/rules/__tests__/integration-test-typeorm-naming-conventions.js +501 -0
- package/rules/__tests__/test-config.js +33 -0
- package/rules/admin-controller-security.js +180 -0
- package/rules/analyze-relation-usage.js +687 -0
- package/rules/api-response-dto.js +174 -0
- package/rules/auth-guard-required.js +142 -0
- package/rules/backend-specific.js +36 -0
- package/rules/best-practices.js +421 -0
- package/rules/complexity.js +20 -0
- package/rules/controller-architecture.js +340 -0
- package/rules/controller-naming-conventions.js +190 -0
- package/rules/controller-readonly-restriction.js +148 -0
- package/rules/controller-swagger-complete.js +312 -0
- package/rules/controller-swagger-docs.js +119 -0
- package/rules/controller-swagger-english.js +320 -0
- package/rules/coordinate-naming.js +132 -0
- package/rules/custom-mui-button.js +135 -0
- package/rules/dead-code-detection-backend.js +50 -0
- package/rules/dead-code-detection-frontend.js +48 -0
- package/rules/dead-code-detection.js +71 -0
- package/rules/debug-controller-response-dto.js +79 -0
- package/rules/deprecate.js +8 -0
- package/rules/dto-annotation-property-consistency.js +111 -0
- package/rules/dto-entity-mapping-completeness.js +688 -0
- package/rules/dto-entity-swagger-separation.js +265 -0
- package/rules/dto-entity-type-consistency.js +352 -0
- package/rules/dto-entity-type-matching.js +519 -0
- package/rules/dto-naming-convention.js +98 -0
- package/rules/dto-visibility-modifiers.js +159 -0
- package/rules/enforce-api-versioning.js +122 -0
- package/rules/enforce-app-module-registration.js +179 -0
- package/rules/enforce-basecontroller.js +152 -0
- package/rules/enforce-body-request-dto.js +141 -0
- package/rules/enforce-controller-response-dto.js +349 -0
- package/rules/enforce-custom-error-classes.js +242 -0
- package/rules/enforce-database-transaction-safety.js +179 -0
- package/rules/enforce-dto-constructor.js +95 -0
- package/rules/enforce-dto-create-parameter-types.js +170 -0
- package/rules/enforce-dto-create-pattern.js +274 -0
- package/rules/enforce-dto-entity-creation.js +164 -0
- package/rules/enforce-dto-factory-in-services.js +188 -0
- package/rules/enforce-dto-from-entity-method.js +47 -0
- package/rules/enforce-dto-from-entity.js +314 -0
- package/rules/enforce-dto-naming-conventions.js +212 -0
- package/rules/enforce-dto-naming.js +176 -0
- package/rules/enforce-dto-usage-simple.js +114 -0
- package/rules/enforce-dto-usage.js +407 -0
- package/rules/enforce-eager-translation-loading.js +178 -0
- package/rules/enforce-entity-creation-pattern.js +137 -0
- package/rules/enforce-entity-dto-convert-method.js +157 -0
- package/rules/enforce-entity-dto-create-no-id.js +117 -0
- package/rules/enforce-entity-dto-extends-base.js +141 -0
- package/rules/enforce-entity-dto-from-request-dto-structure.js +113 -0
- package/rules/enforce-entity-dto-fromentity-complex.js +69 -0
- package/rules/enforce-entity-dto-fromentity-simple.js +69 -0
- package/rules/enforce-entity-dto-fromrequestdto-structure.js +262 -0
- package/rules/enforce-entity-dto-methods-restriction.js +159 -0
- package/rules/enforce-entity-dto-no-request-dto.js +102 -0
- package/rules/enforce-entity-dto-optional-auto-fields.js +101 -0
- package/rules/enforce-entity-dto-required-methods.js +248 -0
- package/rules/enforce-entity-factory-pattern.js +180 -0
- package/rules/enforce-entity-instantiation-in-toentity.js +125 -0
- package/rules/enforce-enum-for-playable-entities.js +95 -0
- package/rules/enforce-error-handling.js +257 -0
- package/rules/enforce-explicit-dto-types.js +118 -0
- package/rules/enforce-from-request-dto-usage.js +62 -0
- package/rules/enforce-generic-entity-dto.js +71 -0
- package/rules/enforce-inject-decorator.js +133 -0
- package/rules/enforce-lazy-type-loading.js +170 -0
- package/rules/enforce-module-existence.js +157 -0
- package/rules/enforce-nonentity-dto-create.js +107 -0
- package/rules/enforce-playable-entity-naming.js +108 -0
- package/rules/enforce-repository-token-handling.js +92 -0
- package/rules/enforce-request-dto-no-entity-dto.js +201 -0
- package/rules/enforce-request-dto-required-fields.js +217 -0
- package/rules/enforce-result-pattern.js +45 -0
- package/rules/enforce-service-relation-loading.js +116 -0
- package/rules/enforce-test-coverage.js +96 -0
- package/rules/enforce-toentity-conditional-assignment.js +132 -0
- package/rules/enforce-translations-required.js +203 -0
- package/rules/enforce-typeorm-naming-conventions.js +366 -0
- package/rules/enforce-vite-health-metrics.js +240 -0
- package/rules/entity-required-properties.js +321 -0
- package/rules/entity-to-dto-test.js +73 -0
- package/rules/enum-database-validation.js +149 -0
- package/rules/errors.js +190 -0
- package/rules/es6.js +204 -0
- package/rules/eslint-plugin-no-comments.js +44 -0
- package/rules/filename-class-name-match.js +62 -0
- package/rules/forbid-fromentity-outside-entity-folder.js +237 -0
- package/rules/function-params-newline.js +111 -0
- package/rules/imports.js +264 -0
- package/rules/jest.js +13 -0
- package/rules/jsx.js +16 -0
- package/rules/max-classes-per-file.js +49 -0
- package/rules/multiline-formatting.js +146 -0
- package/rules/no-blank-lines-between-decorators-and-properties.js +95 -0
- package/rules/no-comments.js +62 -0
- package/rules/no-dto-constructors.js +126 -0
- package/rules/no-dto-default-values.js +220 -0
- package/rules/no-dto-duplicates.js +127 -0
- package/rules/no-dto-in-entity.js +99 -0
- package/rules/no-dynamic-import-in-types.js +71 -0
- package/rules/no-dynamic-imports-in-controllers.js +95 -0
- package/rules/no-entity-imports-in-controllers.js +101 -0
- package/rules/no-entity-in-swagger-docs.js +139 -0
- package/rules/no-entity-type-casting.js +104 -0
- package/rules/no-fetch.js +77 -0
- package/rules/no-import-meta-env.js +151 -0
- package/rules/no-inline-styles.js +5 -0
- package/rules/no-magic-values.js +85 -0
- package/rules/no-partial-type.js +168 -0
- package/rules/no-relative-imports.js +31 -0
- package/rules/no-tsyringe.js +181 -0
- package/rules/no-type-assertion.js +175 -0
- package/rules/no-undefined-entity-properties.js +121 -0
- package/rules/node.js +44 -0
- package/rules/perfectionist.js +50 -0
- package/rules/performance-minimal.js +155 -0
- package/rules/performance.js +44 -0
- package/rules/pino-logger-format.js +200 -0
- package/rules/prefer-dto-classes.js +112 -0
- package/rules/prefer-dto-create-method.js +225 -0
- package/rules/promises.js +17 -0
- package/rules/react-hooks.js +15 -0
- package/rules/react.js +28 -0
- package/rules/regexp.js +70 -0
- package/rules/require-dto-response.js +81 -0
- package/rules/require-valid-relations.js +388 -0
- package/rules/result-pattern.js +162 -0
- package/rules/security.js +37 -0
- package/rules/service-architecture.js +148 -0
- package/rules/sonarjs.js +26 -0
- package/rules/strict.js +7 -0
- package/rules/style.js +611 -0
- package/rules/stylistic.js +93 -0
- package/rules/typeorm-column-type-validation.js +224 -0
- package/rules/typescript-advanced.js +113 -0
- package/rules/typescript-core.js +111 -0
- package/rules/typescript.js +146 -0
- package/rules/unicorn.js +168 -0
- package/rules/variables.js +51 -0
- 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
|
+
|