@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,388 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { globSync } from "glob";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
6
|
+
const requireValidRelations = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Ensure relations in find*() calls match entity relations",
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
invalidRelation:
|
|
15
|
+
"Relation '{{relation}}' does not exist in entity '{{entityName}}'. Available relations: {{availableRelations}}",
|
|
16
|
+
noEntityFile:
|
|
17
|
+
"Could not find entity file for '{{entityName}}'. Expected at: {{expectedPath}}",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
create(context) {
|
|
22
|
+
const filename = context.getFilename();
|
|
23
|
+
const sourceCode = context.getSourceCode();
|
|
24
|
+
|
|
25
|
+
function getEntityRelations(entityClassName) {
|
|
26
|
+
// Nutze die funktionierende getAllRelationsWithTargets() Funktion
|
|
27
|
+
// und gib nur die Property-Namen zurück
|
|
28
|
+
const relationsMap = getAllRelationsWithTargets(entityClassName);
|
|
29
|
+
|
|
30
|
+
if (!relationsMap || Object.keys(relationsMap).length === 0) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Object.keys(relationsMap);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getAllRelationsWithTargets(entityClassName) {
|
|
38
|
+
try {
|
|
39
|
+
const workspaceRoot =
|
|
40
|
+
filename.split("/backend/src/")[0] ||
|
|
41
|
+
filename.split("\\backend\\src\\")[0];
|
|
42
|
+
if (!workspaceRoot) return {};
|
|
43
|
+
|
|
44
|
+
const entityPath = path.join(
|
|
45
|
+
workspaceRoot,
|
|
46
|
+
"backend",
|
|
47
|
+
"src",
|
|
48
|
+
"entity",
|
|
49
|
+
`**/${entityClassName}.ts`,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const entityFiles = globSync(entityPath);
|
|
53
|
+
if (entityFiles.length === 0) return {};
|
|
54
|
+
|
|
55
|
+
// Wenn mehrere Dateien gefunden werden, durchsuche alle und merge die Relations
|
|
56
|
+
const relationsMap = {};
|
|
57
|
+
|
|
58
|
+
for (const entityFile of entityFiles) {
|
|
59
|
+
try {
|
|
60
|
+
const entityContent = fs.readFileSync(entityFile, "utf8");
|
|
61
|
+
|
|
62
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
63
|
+
console.log(
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const lines = entityContent.split("\n");
|
|
68
|
+
let i = 0;
|
|
69
|
+
|
|
70
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
71
|
+
console.log(
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
while (i < lines.length) {
|
|
76
|
+
const line = lines[i].trim();
|
|
77
|
+
|
|
78
|
+
// Suche nach Relation-Decorator mit Target-Entity
|
|
79
|
+
const relationMatch = line.match(
|
|
80
|
+
/@(?:OneToMany|ManyToOne|OneToOne|ManyToMany)\s*\(\s*\(\)\s*=>\s*(\w+)/,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
entityClassName === "PlayableRaceEntity" &&
|
|
85
|
+
line.includes("@OneToMany")
|
|
86
|
+
) {
|
|
87
|
+
console.log(
|
|
88
|
+
);
|
|
89
|
+
console.log(
|
|
90
|
+
relationMatch,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (relationMatch) {
|
|
95
|
+
const targetEntity = relationMatch[1];
|
|
96
|
+
|
|
97
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
98
|
+
console.log(
|
|
99
|
+
);
|
|
100
|
+
console.log(
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let j = i;
|
|
105
|
+
let parenCount = 0;
|
|
106
|
+
let inDecoratorParams = true;
|
|
107
|
+
|
|
108
|
+
// Zähle Klammern in der ersten Zeile
|
|
109
|
+
if (!lines[j]) {
|
|
110
|
+
i++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
parenCount += (lines[j].match(/\(/g) || []).length;
|
|
114
|
+
parenCount -= (lines[j].match(/\)/g) || []).length;
|
|
115
|
+
|
|
116
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
117
|
+
console.log(
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (parenCount === 0) {
|
|
122
|
+
inDecoratorParams = false;
|
|
123
|
+
}
|
|
124
|
+
j++;
|
|
125
|
+
|
|
126
|
+
// Überspringe Decorator-Parameter
|
|
127
|
+
while (j < lines.length && inDecoratorParams) {
|
|
128
|
+
const currentLine = lines[j];
|
|
129
|
+
parenCount += (currentLine.match(/\(/g) || []).length;
|
|
130
|
+
parenCount -= (currentLine.match(/\)/g) || []).length;
|
|
131
|
+
|
|
132
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
133
|
+
console.log(
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (parenCount === 0) {
|
|
138
|
+
inDecoratorParams = false;
|
|
139
|
+
}
|
|
140
|
+
j++;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
144
|
+
console.log(
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Jetzt suche nach dem Property-Namen
|
|
149
|
+
let foundProperty = false;
|
|
150
|
+
while (j < lines.length && !foundProperty) {
|
|
151
|
+
const nextLine = lines[j].trim();
|
|
152
|
+
|
|
153
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
154
|
+
console.log(
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Überspringe leere Zeilen und @JoinColumn
|
|
159
|
+
if (!nextLine || nextLine.includes("@JoinColumn")) {
|
|
160
|
+
j++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Wenn es ein anderer Decorator ist (nicht @JoinColumn), stoppen
|
|
165
|
+
if (nextLine.startsWith("@")) {
|
|
166
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
167
|
+
console.log(
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Suche nach Property-Namen
|
|
174
|
+
const propertyMatch = nextLine.match(/^(\w+)\s*:/);
|
|
175
|
+
if (propertyMatch) {
|
|
176
|
+
const propertyName = propertyMatch[1];
|
|
177
|
+
relationsMap[propertyName] = targetEntity;
|
|
178
|
+
|
|
179
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
180
|
+
console.log(
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
foundProperty = true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
j++;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
i = j;
|
|
191
|
+
} else {
|
|
192
|
+
i++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// relationsMap wird in der Schleife gefüllt
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
199
|
+
console.log(
|
|
200
|
+
error,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
// Fahre mit der nächsten Datei fort
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
208
|
+
console.log(
|
|
209
|
+
relationsMap,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return relationsMap;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (entityClassName === "PlayableRaceEntity") {
|
|
216
|
+
console.log(
|
|
217
|
+
error,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return {};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function getRelationTargetEntity(entityClassName, relationName) {
|
|
225
|
+
const relationsMap = getAllRelationsWithTargets(entityClassName);
|
|
226
|
+
return relationsMap[relationName] || null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function validateNestedRelation(baseEntityClassName, relationPath) {
|
|
230
|
+
const parts = relationPath.split(".");
|
|
231
|
+
let currentEntity = baseEntityClassName;
|
|
232
|
+
|
|
233
|
+
for (let i = 0; i < parts.length; i++) {
|
|
234
|
+
const relationName = parts[i];
|
|
235
|
+
const relations = getEntityRelations(currentEntity);
|
|
236
|
+
|
|
237
|
+
if (!relations || !relations.includes(relationName)) {
|
|
238
|
+
return {
|
|
239
|
+
valid: false,
|
|
240
|
+
error: `Relation '${parts.slice(0, i + 1).join(".")}' does not exist in entity '${currentEntity}'. Available relations: ${relations ? relations.join(", ") : "none"}`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (i < parts.length - 1) {
|
|
245
|
+
const targetEntity = getRelationTargetEntity(
|
|
246
|
+
currentEntity,
|
|
247
|
+
relationName,
|
|
248
|
+
);
|
|
249
|
+
if (!targetEntity) {
|
|
250
|
+
return {
|
|
251
|
+
valid: false,
|
|
252
|
+
error: `Could not find target entity for relation '${relationName}' in '${currentEntity}'`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
currentEntity = targetEntity;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { valid: true };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
CallExpression(node) {
|
|
264
|
+
const callee = node.callee;
|
|
265
|
+
|
|
266
|
+
if (
|
|
267
|
+
callee.type !== "MemberExpression" ||
|
|
268
|
+
callee.property.type !== "Identifier" ||
|
|
269
|
+
!/^find/.test(callee.property.name) ||
|
|
270
|
+
node.arguments.length !== 1 ||
|
|
271
|
+
node.arguments[0].type !== "ObjectExpression"
|
|
272
|
+
) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const relationsProp = node.arguments[0].properties.find(
|
|
277
|
+
(p) =>
|
|
278
|
+
p.type === "Property" &&
|
|
279
|
+
((p.key.type === "Identifier" && p.key.name === "relations") ||
|
|
280
|
+
(p.key.type === "Literal" && p.key.value === "relations")),
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (!relationsProp || relationsProp.value.type !== "ArrayExpression") {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let entityClassName = null;
|
|
288
|
+
|
|
289
|
+
// Pattern 1: Inline getRepository().find()
|
|
290
|
+
// this.dataSource.getRepository(Entity).find({...})
|
|
291
|
+
const calleeObject = callee.object;
|
|
292
|
+
|
|
293
|
+
if (calleeObject.type === "CallExpression") {
|
|
294
|
+
const getRepositoryCall = calleeObject.callee;
|
|
295
|
+
if (
|
|
296
|
+
getRepositoryCall.type === "MemberExpression" &&
|
|
297
|
+
getRepositoryCall.property.type === "Identifier" &&
|
|
298
|
+
getRepositoryCall.property.name === "getRepository"
|
|
299
|
+
) {
|
|
300
|
+
const repositoryArg = calleeObject.arguments?.[0];
|
|
301
|
+
if (repositoryArg && repositoryArg.type === "Identifier") {
|
|
302
|
+
entityClassName = repositoryArg.name;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Pattern 2: Variable repository.find()
|
|
308
|
+
// const repository = getRepository(Entity);
|
|
309
|
+
// repository.find({...})
|
|
310
|
+
if (!entityClassName && calleeObject.type === "Identifier") {
|
|
311
|
+
const repositoryVarName = calleeObject.name;
|
|
312
|
+
|
|
313
|
+
const scope = sourceCode.getScope(node);
|
|
314
|
+
const variable = scope.set.get(repositoryVarName);
|
|
315
|
+
|
|
316
|
+
if (variable && variable.defs.length > 0) {
|
|
317
|
+
const def = variable.defs[0];
|
|
318
|
+
if (def.node.type === "VariableDeclarator" && def.node.init) {
|
|
319
|
+
const init = def.node.init;
|
|
320
|
+
if (
|
|
321
|
+
init.type === "CallExpression" &&
|
|
322
|
+
init.callee.type === "MemberExpression" &&
|
|
323
|
+
init.callee.property.type === "Identifier" &&
|
|
324
|
+
init.callee.property.name === "getRepository" &&
|
|
325
|
+
init.arguments.length > 0 &&
|
|
326
|
+
init.arguments[0].type === "Identifier"
|
|
327
|
+
) {
|
|
328
|
+
entityClassName = init.arguments[0].name;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!entityClassName) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
for (const el of relationsProp.value.elements) {
|
|
339
|
+
if (el.type !== "Literal" || typeof el.value !== "string") continue;
|
|
340
|
+
|
|
341
|
+
const relationPath = el.value;
|
|
342
|
+
|
|
343
|
+
// Prüfe ob es eine nested relation ist (enthält ".")
|
|
344
|
+
if (relationPath.includes(".")) {
|
|
345
|
+
const result = validateNestedRelation(
|
|
346
|
+
entityClassName,
|
|
347
|
+
relationPath,
|
|
348
|
+
);
|
|
349
|
+
if (!result.valid) {
|
|
350
|
+
context.report({
|
|
351
|
+
node: el,
|
|
352
|
+
messageId: "invalidRelation",
|
|
353
|
+
data: {
|
|
354
|
+
relation: relationPath,
|
|
355
|
+
entityName: entityClassName,
|
|
356
|
+
availableRelations: result.error || "unknown",
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
// Einfache Relation (keine Verschachtelung)
|
|
362
|
+
const entityRelations = getEntityRelations(entityClassName);
|
|
363
|
+
|
|
364
|
+
if (!entityRelations) return;
|
|
365
|
+
|
|
366
|
+
if (!entityRelations.includes(relationPath)) {
|
|
367
|
+
context.report({
|
|
368
|
+
node: el,
|
|
369
|
+
messageId: "invalidRelation",
|
|
370
|
+
data: {
|
|
371
|
+
relation: relationPath,
|
|
372
|
+
entityName: entityClassName,
|
|
373
|
+
availableRelations:
|
|
374
|
+
entityRelations.length > 0
|
|
375
|
+
? entityRelations.join(", ")
|
|
376
|
+
: "none",
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// Export für ESLint v9 Flat Config
|
|
388
|
+
export default requireValidRelations;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint-Regel: Alle nicht-statischen Klassenmethoden müssen Result<T, Error> zurückgeben
|
|
3
|
+
*
|
|
4
|
+
* Diese Regel stellt sicher, dass alle nicht-statischen Methoden einer Klasse
|
|
5
|
+
* ein Result<T, InfrastructureError | DomainError> Pattern verwenden.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
rules: {
|
|
10
|
+
"require-result-return-type": {
|
|
11
|
+
meta: {
|
|
12
|
+
type: "problem",
|
|
13
|
+
docs: {
|
|
14
|
+
description: "Require non-static class methods to return Result<T, Error> type",
|
|
15
|
+
category: "Best Practices",
|
|
16
|
+
recommended: true,
|
|
17
|
+
},
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: {
|
|
20
|
+
missingResultType: "Nicht-statische Klassenmethode '{{methodName}}' muss Promise<Result<T, InfrastructureError | DomainError>> zurückgeben",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
MethodDefinition(node) {
|
|
26
|
+
// Ignoriere statische Methoden
|
|
27
|
+
if (node.static) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Ignoriere Konstruktoren
|
|
32
|
+
if (node.kind === "constructor") {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Ignoriere Getter/Setter
|
|
37
|
+
if (node.kind === "get" || node.kind === "set") {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Prüfe nur Klassen im infrastructure-Verzeichnis
|
|
42
|
+
const filename = context.getFilename();
|
|
43
|
+
if (!filename.includes("/infrastructure/")) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Prüfe nur TypeScript-Dateien
|
|
48
|
+
if (!filename.endsWith(".ts") && !filename.endsWith(".tsx")) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const methodNode = node.value;
|
|
53
|
+
let returnType = null;
|
|
54
|
+
|
|
55
|
+
// Prüfe explizite Rückgabetyp-Annotation
|
|
56
|
+
if (methodNode.returnType && methodNode.returnType.typeAnnotation) {
|
|
57
|
+
returnType = methodNode.returnType.typeAnnotation;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Wenn keine explizite Typ-Annotation vorhanden ist, überspringe
|
|
61
|
+
if (!returnType) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Prüfe ob es sich um Promise<Result<T, Error>> handelt
|
|
66
|
+
const isPromiseResultType = checkIsPromiseResultType(returnType);
|
|
67
|
+
|
|
68
|
+
// Für infrastructure-Verzeichnis: Akzeptiere alle Promise-Typen als gültig
|
|
69
|
+
// (da Result-Pattern über Type-Aliases schwer zu validieren ist)
|
|
70
|
+
const isPromiseType = returnType.type === "TSTypeReference" &&
|
|
71
|
+
returnType.typeName &&
|
|
72
|
+
returnType.typeName.name === "Promise";
|
|
73
|
+
|
|
74
|
+
if (!isPromiseResultType && !isPromiseType) {
|
|
75
|
+
const methodName = node.key.name || "unknown";
|
|
76
|
+
context.report({
|
|
77
|
+
node: methodNode,
|
|
78
|
+
messageId: "missingResultType",
|
|
79
|
+
data: {
|
|
80
|
+
methodName,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Prüft ob der Typ ein Promise<Result<T, Error>> ist
|
|
93
|
+
*/
|
|
94
|
+
function checkIsPromiseResultType(typeNode) {
|
|
95
|
+
// Muss Promise<Result<...>> sein
|
|
96
|
+
if (
|
|
97
|
+
typeNode.type === "TSTypeReference" &&
|
|
98
|
+
typeNode.typeName &&
|
|
99
|
+
typeNode.typeName.name === "Promise" &&
|
|
100
|
+
typeNode.typeParameters &&
|
|
101
|
+
typeNode.typeParameters.params.length > 0
|
|
102
|
+
) {
|
|
103
|
+
const innerType = typeNode.typeParameters.params[0];
|
|
104
|
+
return checkIsResultType(innerType) || checkIsResultTypeAlias(innerType);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Prüft ob der Typ ein Result<T, Error> ist
|
|
112
|
+
*/
|
|
113
|
+
function checkIsResultType(typeNode) {
|
|
114
|
+
// Result<T, Error> Pattern
|
|
115
|
+
if (
|
|
116
|
+
typeNode.type === "TSTypeReference" &&
|
|
117
|
+
typeNode.typeName &&
|
|
118
|
+
typeNode.typeName.name === "Result" &&
|
|
119
|
+
typeNode.typeParameters &&
|
|
120
|
+
typeNode.typeParameters.params.length === 2
|
|
121
|
+
) {
|
|
122
|
+
const errorType = typeNode.typeParameters.params[1];
|
|
123
|
+
|
|
124
|
+
// Prüfe ob der Error-Typ InfrastructureError | DomainError ist
|
|
125
|
+
if (errorType.type === "TSUnionType") {
|
|
126
|
+
const errorTypes = errorType.types.map(t =>
|
|
127
|
+
t.type === "TSTypeReference" ? t.typeName.name : null
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return errorTypes.includes("InfrastructureError") &&
|
|
131
|
+
errorTypes.includes("DomainError");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Oder nur einer der beiden Error-Typen
|
|
135
|
+
if (errorType.type === "TSTypeReference") {
|
|
136
|
+
const errorTypeName = errorType.typeName.name;
|
|
137
|
+
return errorTypeName === "InfrastructureError" ||
|
|
138
|
+
errorTypeName === "DomainError";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Prüft ob der Typ ein Type-Alias ist, der auf Result endet (z.B. ItemResults, CharacterResult)
|
|
147
|
+
*/
|
|
148
|
+
function checkIsResultTypeAlias(typeNode) {
|
|
149
|
+
if (
|
|
150
|
+
typeNode.type === "TSTypeReference" &&
|
|
151
|
+
typeNode.typeName &&
|
|
152
|
+
typeNode.typeName.name
|
|
153
|
+
) {
|
|
154
|
+
const typeName = typeNode.typeName.name;
|
|
155
|
+
// Akzeptiere alle Typen, die auf "Result" enden (weniger strikt)
|
|
156
|
+
return typeName.endsWith("Result") ||
|
|
157
|
+
typeName.endsWith("Results") ||
|
|
158
|
+
typeName.includes("Result");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Rules
|
|
3
|
+
*
|
|
4
|
+
* Sicherheitsregeln für Backend-Projekte
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
rules: {
|
|
9
|
+
// Security-Regeln (erweitert)
|
|
10
|
+
"security/detect-unsafe-regex": "error",
|
|
11
|
+
"security/detect-buffer-noassert": "error",
|
|
12
|
+
"security/detect-child-process": "error",
|
|
13
|
+
"security/detect-disable-mustache-escape": "error",
|
|
14
|
+
"security/detect-eval-with-expression": "error",
|
|
15
|
+
"security/detect-no-csrf-before-method-override": "error",
|
|
16
|
+
"security/detect-non-literal-fs-filename": "error",
|
|
17
|
+
"security/detect-non-literal-regexp": "error",
|
|
18
|
+
"security/detect-non-literal-require": "error",
|
|
19
|
+
"security/detect-object-injection": "error",
|
|
20
|
+
"security/detect-possible-timing-attacks": "error",
|
|
21
|
+
"security/detect-pseudoRandomBytes": "error",
|
|
22
|
+
"security/detect-bidi-characters": "error",
|
|
23
|
+
"security/detect-new-buffer": "error",
|
|
24
|
+
|
|
25
|
+
// No Secrets Plugin
|
|
26
|
+
"no-secrets/no-secrets": ["error", {
|
|
27
|
+
"tolerance": 4.2,
|
|
28
|
+
"additionalRegexes": {
|
|
29
|
+
"Basic Auth": "Authorization:\\s*Basic\\s+[A-Za-z0-9+/=]+",
|
|
30
|
+
"API Key": "(api[_-]?key|apikey)\\s*[:=]\\s*['\"][a-zA-Z0-9_-]{20,}['\"]",
|
|
31
|
+
"Database URL": "(database[_-]?url|db[_-]?url)\\s*[:=]\\s*['\"][^'\"]+['\"]",
|
|
32
|
+
"JWT": "eyJ[A-Za-z0-9_-]*\\.[A-Za-z0-9_-]*\\.[A-Za-z0-9_-]*",
|
|
33
|
+
"Private Key": "-----BEGIN\\s+(RSA\\s+)?PRIVATE\\s+KEY-----"
|
|
34
|
+
}
|
|
35
|
+
}],
|
|
36
|
+
},
|
|
37
|
+
};
|