@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,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce that Entities can only be initialized in DTO methods (fromEntity, fromEntityArray, toEntity, fromRequestDto)
|
|
3
|
+
* @author Echoes of Order Team
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
//------------------------------------------------------------------------------
|
|
7
|
+
// Rule Definition
|
|
8
|
+
//------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
11
|
+
export default {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
docs: {
|
|
15
|
+
description: "Entities dürfen nur in DTO-Methoden manuell initialisiert werden (fromEntity, fromEntityArray, toEntity, fromRequestDto)",
|
|
16
|
+
category: "Architecture",
|
|
17
|
+
recommended: true,
|
|
18
|
+
},
|
|
19
|
+
fixable: null,
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
entityInitializationForbidden: "Manuelle Entity-Initialisierung 'new {{entityName}}()' ist nur in DTO-Methoden erlaubt: fromEntity, fromEntityArray, toEntity, fromRequestDto",
|
|
23
|
+
entityPropertyAssignmentForbidden: "Manuelle Property-Zuweisung an Entity '{{entityName}}' ist nur in DTO-Methoden erlaubt: fromEntity, fromEntityArray, toEntity, fromRequestDto",
|
|
24
|
+
useRepositoryCreate: "Verwende repository.create() oder DTO-Methoden statt manueller Entity-Initialisierung",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
create(context) {
|
|
29
|
+
//--------------------------------------------------------------------------
|
|
30
|
+
// Helpers
|
|
31
|
+
//--------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
const filename = context.getFilename();
|
|
34
|
+
const isServiceFile = (filename.includes("/service/") && filename.endsWith(".ts") && !filename.includes("test")) || filename.includes("test-fixtures");
|
|
35
|
+
const isDtoFile = filename.includes("/dto/") && filename.endsWith(".ts");
|
|
36
|
+
const isEntityFile = filename.includes("/entity/") && filename.endsWith(".ts");
|
|
37
|
+
const isTestFixture = filename.includes("test-fixtures");
|
|
38
|
+
const isInvalidTestFixture = filename.includes("InvalidEntityFactoryPattern");
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if we are in a fromEntity method
|
|
42
|
+
* @param {ASTNode} node - The current node
|
|
43
|
+
* @returns {boolean} True if in fromEntity method
|
|
44
|
+
*/
|
|
45
|
+
function isInFromEntityMethod(node) {
|
|
46
|
+
let current = node;
|
|
47
|
+
while (current && current.parent) {
|
|
48
|
+
if (current.type === "MethodDefinition" &&
|
|
49
|
+
current.static === true &&
|
|
50
|
+
current.key?.name === "fromEntity") {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
current = current.parent;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if we are in a fromEntityArray method
|
|
60
|
+
* @param {ASTNode} node - The current node
|
|
61
|
+
* @returns {boolean} True if in fromEntityArray method
|
|
62
|
+
*/
|
|
63
|
+
function isInFromEntityArrayMethod(node) {
|
|
64
|
+
let current = node;
|
|
65
|
+
while (current && current.parent) {
|
|
66
|
+
if (current.type === "MethodDefinition" &&
|
|
67
|
+
current.static === true &&
|
|
68
|
+
current.key?.name === "fromEntityArray") {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
current = current.parent;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if we are in a toEntity method
|
|
78
|
+
* @param {ASTNode} node - The current node
|
|
79
|
+
* @returns {boolean} True if in toEntity method
|
|
80
|
+
*/
|
|
81
|
+
function isInToEntityMethod(node) {
|
|
82
|
+
let current = node;
|
|
83
|
+
while (current && current.parent) {
|
|
84
|
+
if (current.type === "MethodDefinition" &&
|
|
85
|
+
current.static === true &&
|
|
86
|
+
current.key?.name === "toEntity") {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
current = current.parent;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if we are in a fromRequestDto method
|
|
96
|
+
* @param {ASTNode} node - The current node
|
|
97
|
+
* @returns {boolean} True if in fromRequestDto method
|
|
98
|
+
*/
|
|
99
|
+
function isInFromRequestDtoMethod(node) {
|
|
100
|
+
let current = node;
|
|
101
|
+
while (current && current.parent) {
|
|
102
|
+
if (current.type === "MethodDefinition" &&
|
|
103
|
+
current.static === true &&
|
|
104
|
+
current.key?.name === "fromRequestDto") {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
current = current.parent;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if we are in a DTO fromEntity method
|
|
114
|
+
* @param {ASTNode} node - The current node
|
|
115
|
+
* @returns {boolean} True if in DTO fromEntity method
|
|
116
|
+
*/
|
|
117
|
+
function isInDtoFromEntityMethod(node) {
|
|
118
|
+
return isDtoFile && (isInFromEntityMethod(node) || isInFromEntityArrayMethod(node) || isInToEntityMethod(node) || isInFromRequestDtoMethod(node));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//--------------------------------------------------------------------------
|
|
122
|
+
// Public
|
|
123
|
+
//--------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
// Überwache new Entity() Aufrufe
|
|
127
|
+
NewExpression(node) {
|
|
128
|
+
// Ignoriere Test-Fixtures, außer für InvalidEntityFactoryPattern
|
|
129
|
+
if (isTestFixture && !isInvalidTestFixture) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const entityName = node.callee?.name;
|
|
134
|
+
|
|
135
|
+
// Prüfe ob es eine Entity ist (Name endet mit "Entity")
|
|
136
|
+
if (entityName && entityName.endsWith("Entity")) {
|
|
137
|
+
// Erlaube nur in DTO fromEntity-Methoden
|
|
138
|
+
if (!isInDtoFromEntityMethod(node)) {
|
|
139
|
+
context.report({
|
|
140
|
+
node,
|
|
141
|
+
messageId: "entityInitializationForbidden",
|
|
142
|
+
data: {
|
|
143
|
+
entityName,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// Überwache Property-Zuweisungen an Entities
|
|
151
|
+
AssignmentExpression(node) {
|
|
152
|
+
// Ignoriere Test-Fixtures, außer für InvalidEntityFactoryPattern
|
|
153
|
+
if (isTestFixture && !isInvalidTestFixture) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (isServiceFile || isEntityFile) {
|
|
158
|
+
// Prüfe ob links eine Entity-Variable ist (kann auch camelCase sein)
|
|
159
|
+
if (node.left.type === "MemberExpression" &&
|
|
160
|
+
node.left.object?.name &&
|
|
161
|
+
(node.left.object.name.endsWith("Entity") ||
|
|
162
|
+
node.left.object.name.match(/^[a-z][a-zA-Z]*Entity$/))) {
|
|
163
|
+
|
|
164
|
+
// Erlaube nur in DTO fromEntity-Methoden
|
|
165
|
+
if (!isInDtoFromEntityMethod(node)) {
|
|
166
|
+
context.report({
|
|
167
|
+
node,
|
|
168
|
+
messageId: "entityPropertyAssignmentForbidden",
|
|
169
|
+
data: {
|
|
170
|
+
entityName: node.left.object.name,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce that Entity instantiation (new Entity()) is only allowed within toEntity methods
|
|
3
|
+
* @author Echoes of Order Team
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
//------------------------------------------------------------------------------
|
|
7
|
+
// Rule Definition
|
|
8
|
+
//------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
11
|
+
export default {
|
|
12
|
+
rules: {
|
|
13
|
+
"enforce-entity-instantiation-in-toentity": {
|
|
14
|
+
meta: {
|
|
15
|
+
type: "problem",
|
|
16
|
+
docs: {
|
|
17
|
+
description: "Enforce that new Entity() is only allowed within toEntity methods of Entity DTOs",
|
|
18
|
+
category: "Best Practices",
|
|
19
|
+
recommended: true,
|
|
20
|
+
},
|
|
21
|
+
fixable: null,
|
|
22
|
+
schema: [],
|
|
23
|
+
messages: {
|
|
24
|
+
newEntityOutsideToEntity: "new {{entityName}}() is only allowed within the toEntity method of the corresponding Entity DTO. Use repository.create() or DTO methods instead.",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
create(context) {
|
|
29
|
+
const entityDtos = new Map();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if filename is an Entity DTO file
|
|
33
|
+
*/
|
|
34
|
+
function isEntityDtoFile(filename) {
|
|
35
|
+
return (
|
|
36
|
+
(filename.includes("/dto/Entity/") || filename.includes("/test-fixtures/")) &&
|
|
37
|
+
(filename.endsWith("EntityDto.ts") || filename.includes("EntityDto"))
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get DTO name from Entity name
|
|
43
|
+
*/
|
|
44
|
+
function getDtoNameFromEntity(entityName) {
|
|
45
|
+
return entityName.replace("Entity", "EntityDto");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if node is inside a toEntity method
|
|
50
|
+
*/
|
|
51
|
+
function isInsideToEntityMethod(node) {
|
|
52
|
+
let parent = node.parent;
|
|
53
|
+
while (parent) {
|
|
54
|
+
if (parent.type === "MethodDefinition" || parent.type === "Property") {
|
|
55
|
+
if (parent.key && parent.key.name === "toEntity") {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
parent = parent.parent;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if node is inside a fromRequestDto method
|
|
66
|
+
*/
|
|
67
|
+
function isInsideFromRequestDtoMethod(node) {
|
|
68
|
+
let parent = node.parent;
|
|
69
|
+
while (parent) {
|
|
70
|
+
if (parent.type === "MethodDefinition" || parent.type === "Property") {
|
|
71
|
+
if (parent.key && parent.key.name === "fromRequestDto") {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
parent = parent.parent;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
ClassDeclaration(node) {
|
|
82
|
+
const filename = context.getFilename();
|
|
83
|
+
const className = node.id.name;
|
|
84
|
+
|
|
85
|
+
if (isEntityDtoFile(filename) && className.endsWith("EntityDto")) {
|
|
86
|
+
// Track Entity DTOs for later validation
|
|
87
|
+
entityDtos.set(className, {
|
|
88
|
+
node,
|
|
89
|
+
filename,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
NewExpression(node) {
|
|
95
|
+
const filename = context.getFilename();
|
|
96
|
+
const entityName = node.callee.name;
|
|
97
|
+
|
|
98
|
+
// Only check Entity instantiations (new XxxEntity())
|
|
99
|
+
if (entityName && entityName.endsWith("Entity")) {
|
|
100
|
+
const dtoName = getDtoNameFromEntity(entityName);
|
|
101
|
+
const dtoInfo = entityDtos.get(dtoName);
|
|
102
|
+
|
|
103
|
+
// If we're in an Entity DTO file and instantiating the corresponding Entity
|
|
104
|
+
if (dtoInfo) {
|
|
105
|
+
const isInToEntity = isInsideToEntityMethod(node);
|
|
106
|
+
const isInFromRequestDto = isInsideFromRequestDtoMethod(node);
|
|
107
|
+
|
|
108
|
+
// Allow in toEntity and fromRequestDto methods
|
|
109
|
+
if (!isInToEntity && !isInFromRequestDto) {
|
|
110
|
+
context.report({
|
|
111
|
+
node,
|
|
112
|
+
messageId: "newEntityOutsideToEntity",
|
|
113
|
+
data: { entityName },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "problem",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Enforce enum types (PlayableRaceName, PlayableClassName, PlayableFactionName) for playable entity properties instead of string",
|
|
6
|
+
category: "Best Practices",
|
|
7
|
+
recommended: true,
|
|
8
|
+
},
|
|
9
|
+
messages: {
|
|
10
|
+
useEnumType: "Property '{{propertyName}}' must use enum type '{{enumType}}' instead of 'string'. This ensures type safety and validation at compile time.",
|
|
11
|
+
},
|
|
12
|
+
fixable: "code",
|
|
13
|
+
schema: [],
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
create (context) {
|
|
17
|
+
const enumMapping = {
|
|
18
|
+
playableClass: "PlayableClassName",
|
|
19
|
+
playableFaction: "PlayableFactionName",
|
|
20
|
+
playableRace: "PlayableRaceName",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function shouldSkipFile (filename) {
|
|
24
|
+
return filename.includes("Enum.ts") ||
|
|
25
|
+
filename.includes("enum/") ||
|
|
26
|
+
filename.includes(".test.ts") ||
|
|
27
|
+
filename.includes(".spec.ts");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function hasEnumImport (context, enumName) {
|
|
31
|
+
const sourceCode = context.getSourceCode();
|
|
32
|
+
const text = sourceCode.getText();
|
|
33
|
+
|
|
34
|
+
return text.includes(`import ${enumName}`) ||
|
|
35
|
+
text.includes(`import { ${enumName} }`) ||
|
|
36
|
+
text.includes(`import type { ${enumName} }`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createEnumImport (enumName) {
|
|
40
|
+
return `import ${enumName} from "@/enum/${enumName}";\n`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
Identifier (node) {
|
|
45
|
+
const name = node.name;
|
|
46
|
+
|
|
47
|
+
if (!(name in enumMapping)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const filename = context.getFilename();
|
|
52
|
+
if (shouldSkipFile(filename)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!node.typeAnnotation || !node.typeAnnotation.typeAnnotation) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const typeAnnotation = node.typeAnnotation.typeAnnotation;
|
|
61
|
+
|
|
62
|
+
if (typeAnnotation.type !== "TSStringKeyword") {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const enumType = enumMapping[name];
|
|
67
|
+
|
|
68
|
+
context.report({
|
|
69
|
+
messageId: "useEnumType",
|
|
70
|
+
node: node.typeAnnotation,
|
|
71
|
+
data: {
|
|
72
|
+
enumType,
|
|
73
|
+
propertyName: name,
|
|
74
|
+
},
|
|
75
|
+
fix (fixer) {
|
|
76
|
+
const fixes = [];
|
|
77
|
+
|
|
78
|
+
fixes.push(fixer.replaceText(typeAnnotation, enumType));
|
|
79
|
+
|
|
80
|
+
if (!hasEnumImport(context, enumType)) {
|
|
81
|
+
const sourceCode = context.getSourceCode();
|
|
82
|
+
const firstImport = sourceCode.ast.body.find((n) => n.type === "ImportDeclaration");
|
|
83
|
+
|
|
84
|
+
if (firstImport) {
|
|
85
|
+
fixes.push(fixer.insertTextBefore(firstImport, createEnumImport(enumType)));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return fixes;
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint-Regel: enforce-error-handling
|
|
3
|
+
* Erzwingt korrekte Error-Handling-Patterns in Services und Controllern
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
7
|
+
const enforceErrorHandlingRule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Erzwingt korrekte Error-Handling-Patterns und englische Error-Messages",
|
|
12
|
+
category: "Error Handling",
|
|
13
|
+
recommended: true,
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
missingTryCatch: "Async-Methode '{{methodName}}' muss try-catch für Error-Handling verwenden.",
|
|
18
|
+
genericErrorThrow: "Verwende spezifische Error-Klassen statt generischem 'throw new Error()'.",
|
|
19
|
+
missingErrorLogging: "Error muss geloggt werden bevor Result.failure() zurückgegeben wird.",
|
|
20
|
+
germanErrorMessage: "Error-Messages müssen auf Englisch sein. Deutsche Texte sind nicht erlaubt.",
|
|
21
|
+
unhandledPromise: "Promise muss mit .catch() oder try-catch behandelt werden.",
|
|
22
|
+
emptyCatchBlock: "Catch-Block muss einen Error-Parameter haben und den Fehler loggen.",
|
|
23
|
+
missingErrorLoggingInCatch: "Error muss im catch-Block geloggt werden.",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
create(context) {
|
|
27
|
+
const filename = context.getFilename();
|
|
28
|
+
const isServiceFile = filename.includes("/service/") && filename.endsWith(".ts");
|
|
29
|
+
const isControllerFile = filename.includes("/controller/") && filename.endsWith("Controller.ts");
|
|
30
|
+
const isTestFile = filename.includes(".test.") || filename.includes(".spec.");
|
|
31
|
+
|
|
32
|
+
if (!isServiceFile && !isControllerFile || isTestFile) return {};
|
|
33
|
+
|
|
34
|
+
// Deutsche Indikatoren für Error-Messages
|
|
35
|
+
const germanIndicators = [
|
|
36
|
+
"fehler", "nicht", "beim", "für", "der", "die", "das",
|
|
37
|
+
"ist", "sind", "wurde", "werden", "haben", "hat",
|
|
38
|
+
"ungültig", "unbekannt", "gefunden", "existiert",
|
|
39
|
+
"berechtigung", "zugriff", "verweigert", "fehlgeschlagen"
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function containsGermanText(text) {
|
|
43
|
+
if (!text || typeof text !== "string") return false;
|
|
44
|
+
const lowerText = text.toLowerCase();
|
|
45
|
+
return germanIndicators.some(indicator => lowerText.includes(indicator));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isAsyncMethod(node) {
|
|
49
|
+
return node.type === "MethodDefinition" && node.value?.async === true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function hasTryCatchBlock(methodNode) {
|
|
53
|
+
const body = methodNode.value?.body;
|
|
54
|
+
if (!body || body.type !== "BlockStatement") return false;
|
|
55
|
+
|
|
56
|
+
return body.body.some(statement => statement.type === "TryStatement");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function hasValidErrorLoggingInCatch(tryStatement) {
|
|
60
|
+
if (!tryStatement || tryStatement.type !== "TryStatement") return false;
|
|
61
|
+
if (!tryStatement.handler) return false;
|
|
62
|
+
|
|
63
|
+
const catchBlock = tryStatement.handler;
|
|
64
|
+
const catchBody = catchBlock.body;
|
|
65
|
+
|
|
66
|
+
// Prüfe, ob der catch Block einen Parameter hat
|
|
67
|
+
if (!catchBlock.param) {
|
|
68
|
+
return false; // Leere catch Blöcke sind nicht erlaubt
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Prüfe, ob im catch Block ein logger.error() Aufruf vorhanden ist
|
|
72
|
+
if (catchBody.type !== "BlockStatement") return false;
|
|
73
|
+
|
|
74
|
+
return catchBody.body.some(statement => {
|
|
75
|
+
if (statement.type === "ExpressionStatement" &&
|
|
76
|
+
statement.expression?.type === "CallExpression" &&
|
|
77
|
+
statement.expression.callee?.type === "MemberExpression" &&
|
|
78
|
+
statement.expression.callee.property?.name === "error") {
|
|
79
|
+
|
|
80
|
+
// Prüfe auf logger.error oder this.logger.error
|
|
81
|
+
const calleeObject = statement.expression.callee.object;
|
|
82
|
+
if ((calleeObject?.name === "logger") ||
|
|
83
|
+
(calleeObject?.type === "MemberExpression" &&
|
|
84
|
+
calleeObject.object?.type === "ThisExpression" &&
|
|
85
|
+
calleeObject.property?.name === "logger")) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function hasErrorLogging(node) {
|
|
94
|
+
// Prüfe, ob vor Return-Statement ein logger.error() Aufruf steht
|
|
95
|
+
const parent = node.parent;
|
|
96
|
+
if (parent?.type !== "BlockStatement") return false;
|
|
97
|
+
|
|
98
|
+
const statements = parent.body;
|
|
99
|
+
const returnIndex = statements.indexOf(node);
|
|
100
|
+
|
|
101
|
+
// Prüfe die vorangehenden Statements auf logger.error oder this.logger.error
|
|
102
|
+
for (let i = returnIndex - 1; i >= 0; i--) {
|
|
103
|
+
const stmt = statements[i];
|
|
104
|
+
if (stmt.type === "ExpressionStatement" &&
|
|
105
|
+
stmt.expression?.type === "CallExpression" &&
|
|
106
|
+
stmt.expression.callee?.type === "MemberExpression" &&
|
|
107
|
+
stmt.expression.callee.property?.name === "error") {
|
|
108
|
+
|
|
109
|
+
// Prüfe auf logger.error oder this.logger.error
|
|
110
|
+
const calleeObject = stmt.expression.callee.object;
|
|
111
|
+
if ((calleeObject?.name === "logger") ||
|
|
112
|
+
(calleeObject?.type === "MemberExpression" &&
|
|
113
|
+
calleeObject.object?.type === "ThisExpression" &&
|
|
114
|
+
calleeObject.property?.name === "logger")) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
// Prüfe async Methoden auf try-catch
|
|
124
|
+
MethodDefinition(node) {
|
|
125
|
+
if (isAsyncMethod(node) && !hasTryCatchBlock(node)) {
|
|
126
|
+
const methodName = node.key?.name || "unknown";
|
|
127
|
+
context.report({
|
|
128
|
+
node,
|
|
129
|
+
messageId: "missingTryCatch",
|
|
130
|
+
data: { methodName },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// Prüfe throw new Error() auf spezifische Error-Klassen
|
|
136
|
+
ThrowStatement(node) {
|
|
137
|
+
if (node.argument?.type === "NewExpression" &&
|
|
138
|
+
node.argument.callee?.name === "Error") {
|
|
139
|
+
context.report({
|
|
140
|
+
node,
|
|
141
|
+
messageId: "genericErrorThrow",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Prüfe Error-Messages auf deutsche Texte
|
|
146
|
+
if (node.argument?.arguments?.[0]?.type === "Literal") {
|
|
147
|
+
const message = node.argument.arguments[0].value;
|
|
148
|
+
if (containsGermanText(message)) {
|
|
149
|
+
context.report({
|
|
150
|
+
node: node.argument.arguments[0],
|
|
151
|
+
messageId: "germanErrorMessage",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
// Prüfe Result.failure() auf Error-Logging
|
|
158
|
+
ReturnStatement(node) {
|
|
159
|
+
if (node.argument?.type === "CallExpression" &&
|
|
160
|
+
node.argument.callee?.type === "MemberExpression" &&
|
|
161
|
+
node.argument.callee.object?.name === "Result" &&
|
|
162
|
+
node.argument.callee.property?.name === "failure") {
|
|
163
|
+
|
|
164
|
+
if (!hasErrorLogging(node)) {
|
|
165
|
+
context.report({
|
|
166
|
+
node,
|
|
167
|
+
messageId: "missingErrorLogging",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
// Prüfe Promise-Aufrufe auf Fehlerbehandlung
|
|
174
|
+
CallExpression(node) {
|
|
175
|
+
// Prüfe auf unbehandelte Promise-Aufrufe
|
|
176
|
+
if (node.callee?.type === "MemberExpression") {
|
|
177
|
+
const method = node.callee.property?.name;
|
|
178
|
+
const isPromiseMethod = ["then", "catch", "finally"].includes(method);
|
|
179
|
+
|
|
180
|
+
if (isPromiseMethod && method !== "catch") {
|
|
181
|
+
// Prüfe, ob .catch() in der Chain vorhanden ist
|
|
182
|
+
let currentNode = node.parent;
|
|
183
|
+
let hasCatch = false;
|
|
184
|
+
|
|
185
|
+
while (currentNode?.type === "CallExpression" &&
|
|
186
|
+
currentNode.callee?.type === "MemberExpression") {
|
|
187
|
+
if (currentNode.callee.property?.name === "catch") {
|
|
188
|
+
hasCatch = true;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
currentNode = currentNode.parent;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!hasCatch && method === "then") {
|
|
195
|
+
context.report({
|
|
196
|
+
node,
|
|
197
|
+
messageId: "unhandledPromise",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// Prüfe Error-Konstruktor-Aufrufe auf deutsche Messages
|
|
205
|
+
NewExpression(node) {
|
|
206
|
+
const errorTypes = ["Error", "TypeError", "ReferenceError", "SyntaxError"];
|
|
207
|
+
if (errorTypes.includes(node.callee?.name) &&
|
|
208
|
+
node.arguments?.[0]?.type === "Literal") {
|
|
209
|
+
|
|
210
|
+
const message = node.arguments[0].value;
|
|
211
|
+
if (containsGermanText(message)) {
|
|
212
|
+
context.report({
|
|
213
|
+
node: node.arguments[0],
|
|
214
|
+
messageId: "germanErrorMessage",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
// Prüfe TryStatement auf korrekte Error-Behandlung
|
|
221
|
+
TryStatement(node) {
|
|
222
|
+
if (!node.handler) {
|
|
223
|
+
context.report({
|
|
224
|
+
node,
|
|
225
|
+
messageId: "emptyCatchBlock",
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const catchBlock = node.handler;
|
|
231
|
+
|
|
232
|
+
// Prüfe, ob der catch Block einen Parameter hat
|
|
233
|
+
if (!catchBlock.param) {
|
|
234
|
+
context.report({
|
|
235
|
+
node: catchBlock,
|
|
236
|
+
messageId: "emptyCatchBlock",
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Prüfe, ob der Fehler geloggt wird
|
|
242
|
+
if (!hasValidErrorLoggingInCatch(node)) {
|
|
243
|
+
context.report({
|
|
244
|
+
node: catchBlock,
|
|
245
|
+
messageId: "missingErrorLoggingInCatch",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export default {
|
|
254
|
+
rules: {
|
|
255
|
+
"enforce-error-handling": enforceErrorHandlingRule,
|
|
256
|
+
},
|
|
257
|
+
};
|