@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,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
|
+
};
|