@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,49 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "problem",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Enforce only one class per file",
|
|
6
|
+
category: "Best Practices",
|
|
7
|
+
},
|
|
8
|
+
fixable: null,
|
|
9
|
+
schema: [],
|
|
10
|
+
messages: {
|
|
11
|
+
tooManyClasses: "Only one class per file is allowed. Found {{count}} classes: {{names}}. Please split into separate files.",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
create(context) {
|
|
16
|
+
const classes = [];
|
|
17
|
+
|
|
18
|
+
function checkClasses() {
|
|
19
|
+
if (classes.length > 1) {
|
|
20
|
+
const classNames = classes.map(d => d.name);
|
|
21
|
+
|
|
22
|
+
// Report on the first class found
|
|
23
|
+
const firstClass = classes[0];
|
|
24
|
+
|
|
25
|
+
context.report({
|
|
26
|
+
node: firstClass.node,
|
|
27
|
+
messageId: "tooManyClasses",
|
|
28
|
+
data: {
|
|
29
|
+
count: classes.length,
|
|
30
|
+
names: classNames.join(", "),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
ClassDeclaration(node) {
|
|
38
|
+
classes.push({
|
|
39
|
+
node,
|
|
40
|
+
name: node.id ? node.id.name : "Anonymous",
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
"Program:exit"() {
|
|
45
|
+
checkClasses();
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint Custom Rule: multiline-formatting
|
|
3
|
+
*
|
|
4
|
+
* Erzwingt und fixt automatisch Zeilenumbrüche bei:
|
|
5
|
+
* 1. Langen Method-Chains (.filter().map())
|
|
6
|
+
* 2. Langen Property-Zuweisungen
|
|
7
|
+
* 3. Objekten mit mehreren Properties
|
|
8
|
+
*
|
|
9
|
+
* ❌ Falsch:
|
|
10
|
+
* items: result.items.filter((item): item is ItemEntity => item !== null).map((item) => ItemEntityDto.fromEntity(item)),
|
|
11
|
+
*
|
|
12
|
+
* ✅ Richtig:
|
|
13
|
+
* items: result.items
|
|
14
|
+
* .filter((item): item is ItemEntity => item !== null)
|
|
15
|
+
* .map((item) => ItemEntityDto.fromEntity(item)),
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
meta: {
|
|
20
|
+
type: "layout",
|
|
21
|
+
docs: {
|
|
22
|
+
description: "Automatische Zeilenumbrüche bei langen Ausdrücken",
|
|
23
|
+
category: "Stylistic Issues",
|
|
24
|
+
recommended: true,
|
|
25
|
+
},
|
|
26
|
+
fixable: "whitespace",
|
|
27
|
+
messages: {
|
|
28
|
+
tooLongLine: "Diese Zeile ist zu lang ({{length}} Zeichen, max {{max}}). Breche sie um.",
|
|
29
|
+
methodChainShouldBreak: "Method-Chains sollten auf separate Zeilen umgebrochen werden.",
|
|
30
|
+
},
|
|
31
|
+
schema: [{
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
maxLength: {
|
|
35
|
+
type: "number",
|
|
36
|
+
default: 120,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
}],
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
create (context) {
|
|
44
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
45
|
+
const maxLength = context.options[0]?.maxLength ?? 120;
|
|
46
|
+
|
|
47
|
+
function getLineLength (node) {
|
|
48
|
+
const start = node.loc.start;
|
|
49
|
+
const end = node.loc.end;
|
|
50
|
+
|
|
51
|
+
if (start.line === end.line) {
|
|
52
|
+
return sourceCode.lines[start.line - 1]?.length ?? 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function fixMethodChain (fixer, node) {
|
|
59
|
+
const text = sourceCode.getText(node);
|
|
60
|
+
const fixes = [];
|
|
61
|
+
|
|
62
|
+
if (node.type === "CallExpression" && node.callee.type === "MemberExpression") {
|
|
63
|
+
const chain = [];
|
|
64
|
+
let current = node;
|
|
65
|
+
|
|
66
|
+
while (current.type === "CallExpression" && current.callee.type === "MemberExpression") {
|
|
67
|
+
chain.unshift(current);
|
|
68
|
+
current = current.callee.object;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (chain.length > 1) {
|
|
72
|
+
const baseObject = sourceCode.getText(current);
|
|
73
|
+
const indent = " ".repeat(node.loc.start.column);
|
|
74
|
+
|
|
75
|
+
let newCode = baseObject;
|
|
76
|
+
|
|
77
|
+
for (const call of chain) {
|
|
78
|
+
const methodName = call.callee.property.name;
|
|
79
|
+
const args = call.arguments.map((arg) => sourceCode.getText(arg)).join(", ");
|
|
80
|
+
newCode += `\n${indent} .${methodName}(${args})`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return fixer.replaceText(node, newCode);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
CallExpression (node) {
|
|
92
|
+
const lineLength = getLineLength(node);
|
|
93
|
+
|
|
94
|
+
if (lineLength > maxLength && node.callee.type === "MemberExpression") {
|
|
95
|
+
let current = node;
|
|
96
|
+
let chainLength = 0;
|
|
97
|
+
|
|
98
|
+
while (current.type === "CallExpression" && current.callee.type === "MemberExpression") {
|
|
99
|
+
chainLength++;
|
|
100
|
+
current = current.callee.object;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (chainLength > 1) {
|
|
104
|
+
context.report({
|
|
105
|
+
node,
|
|
106
|
+
messageId: "methodChainShouldBreak",
|
|
107
|
+
fix (fixer) {
|
|
108
|
+
return fixMethodChain(fixer, node);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
Property (node) {
|
|
116
|
+
if (node.value && node.value.type === "CallExpression") {
|
|
117
|
+
const lineLength = getLineLength(node);
|
|
118
|
+
|
|
119
|
+
if (lineLength > maxLength) {
|
|
120
|
+
const valueNode = node.value;
|
|
121
|
+
|
|
122
|
+
if (valueNode.callee.type === "MemberExpression") {
|
|
123
|
+
let current = valueNode;
|
|
124
|
+
let chainLength = 0;
|
|
125
|
+
|
|
126
|
+
while (current.type === "CallExpression" && current.callee.type === "MemberExpression") {
|
|
127
|
+
chainLength++;
|
|
128
|
+
current = current.callee.object;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (chainLength > 1) {
|
|
132
|
+
context.report({
|
|
133
|
+
node: valueNode,
|
|
134
|
+
messageId: "methodChainShouldBreak",
|
|
135
|
+
fix (fixer) {
|
|
136
|
+
return fixMethodChain(fixer, valueNode);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Verbot von Leerzeilen zwischen Dekoratoren und Properties
|
|
3
|
+
* @author Echoes of Order Team
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "layout",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Verbot von Leerzeilen zwischen Dekoratoren und Properties",
|
|
11
|
+
category: "Stylistic Issues",
|
|
12
|
+
recommended: false,
|
|
13
|
+
},
|
|
14
|
+
fixable: "whitespace",
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
noBlankLinesBetweenDecoratorsAndProperties:
|
|
18
|
+
"Leerzeilen zwischen Dekoratoren und Properties sind nicht erlaubt. Entferne die Leerzeile(n)."
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
create(context) {
|
|
23
|
+
const sourceCode = context.getSourceCode();
|
|
24
|
+
|
|
25
|
+
function checkForBlankLinesBetweenDecoratorsAndProperties(node) {
|
|
26
|
+
// Nur für Property-Definitionen prüfen
|
|
27
|
+
if (node.type !== "PropertyDefinition" && node.type !== "MethodDefinition") {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Prüfen ob das Property/Method Dekoratoren hat
|
|
32
|
+
if (!node.decorators || node.decorators.length === 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const lastDecorator = node.decorators[node.decorators.length - 1];
|
|
37
|
+
const propertyStart = node.key ? node.key.loc.start : node.loc.start;
|
|
38
|
+
|
|
39
|
+
// Alle Tokens zwischen dem letzten Dekorator und dem Property finden
|
|
40
|
+
const tokens = sourceCode.getTokensBetween(lastDecorator, node);
|
|
41
|
+
|
|
42
|
+
// Nach Leerzeilen suchen
|
|
43
|
+
let hasBlankLines = false;
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
46
|
+
const token = tokens[i];
|
|
47
|
+
|
|
48
|
+
if (token.type === "Punctuator" && token.value === "@") {
|
|
49
|
+
// Wenn wir einen weiteren Dekorator finden, stoppen
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Nach Leerzeilen suchen
|
|
54
|
+
if (token.type === "Whitespace" || token.type === "Line") {
|
|
55
|
+
const lines = token.value.split("\n");
|
|
56
|
+
if (lines.length > 2) { // Mehr als eine Leerzeile
|
|
57
|
+
hasBlankLines = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Alternative Prüfung über Zeilen
|
|
63
|
+
const lastDecoratorLine = lastDecorator.loc.end.line;
|
|
64
|
+
const propertyLine = propertyStart.line;
|
|
65
|
+
|
|
66
|
+
if (propertyLine - lastDecoratorLine > 1) {
|
|
67
|
+
hasBlankLines = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (hasBlankLines) {
|
|
71
|
+
context.report({
|
|
72
|
+
node,
|
|
73
|
+
messageId: "noBlankLinesBetweenDecoratorsAndProperties",
|
|
74
|
+
fix(fixer) {
|
|
75
|
+
// Alle Leerzeilen zwischen Dekorator und Property entfernen
|
|
76
|
+
const range = [
|
|
77
|
+
lastDecorator.range[1],
|
|
78
|
+
node.range[0]
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const text = sourceCode.text.slice(range[0], range[1]);
|
|
82
|
+
const cleanedText = text.replace(/\n\s*\n/g, "\n");
|
|
83
|
+
|
|
84
|
+
return fixer.replaceTextRange(range, cleanedText);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
PropertyDefinition: checkForBlankLinesBetweenDecoratorsAndProperties,
|
|
92
|
+
MethodDefinition: checkForBlankLinesBetweenDecoratorsAndProperties
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No Comments Rules
|
|
3
|
+
*
|
|
4
|
+
* Verbot von Kommentaren im Code
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
rules: {
|
|
9
|
+
// No-Comments-Regel
|
|
10
|
+
"no-comments/no-comments": {
|
|
11
|
+
meta: {
|
|
12
|
+
type: "problem",
|
|
13
|
+
docs: {
|
|
14
|
+
description: "Disallow comments in code",
|
|
15
|
+
category: "Stylistic Issues",
|
|
16
|
+
recommended: false,
|
|
17
|
+
},
|
|
18
|
+
fixable: null,
|
|
19
|
+
schema: [],
|
|
20
|
+
},
|
|
21
|
+
create (context) {
|
|
22
|
+
const sourceCode = context.getSourceCode();
|
|
23
|
+
return {
|
|
24
|
+
Program () {
|
|
25
|
+
const comments = sourceCode.getAllComments();
|
|
26
|
+
comments.forEach((comment) => {
|
|
27
|
+
const value = comment.value.trim();
|
|
28
|
+
if (value.startsWith("eslint-disable")
|
|
29
|
+
|| value.startsWith("eslint-enable")
|
|
30
|
+
|| value.startsWith("@ts-ignore")
|
|
31
|
+
|| value.startsWith("@ts-expect-error")
|
|
32
|
+
|| value.startsWith("@ts-nocheck")
|
|
33
|
+
|| value.startsWith("@ts-check")
|
|
34
|
+
|| value.includes("prettier-ignore")
|
|
35
|
+
|| value.includes("istanbul ignore")
|
|
36
|
+
|| value.includes("c8 ignore")
|
|
37
|
+
|| value.includes("coverage ignore")
|
|
38
|
+
|| value.includes("codecov ignore")
|
|
39
|
+
|| value.startsWith("SPDX-License-Identifier")
|
|
40
|
+
|| value.startsWith("Copyright")
|
|
41
|
+
|| value.startsWith("(c)")
|
|
42
|
+
|| value.startsWith("MIT License")
|
|
43
|
+
|| value.includes("Apache License")
|
|
44
|
+
|| value.includes("GPL")
|
|
45
|
+
|| value.includes("BSD")
|
|
46
|
+
|| value.includes("ISC License")
|
|
47
|
+
) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
context.report({
|
|
51
|
+
node: comment,
|
|
52
|
+
message: "Comments are not allowed in code. Code should be self-explanatory.",
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Verbietet Konstruktoren in DTO-Klassen
|
|
3
|
+
* DTOs sind reine Datencontainer und sollten keine Konstruktoren haben.
|
|
4
|
+
* Sie werden automatisch von class-transformer aus JSON erstellt.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "error",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Forbid constructors in DTO classes - DTOs should be pure data containers",
|
|
12
|
+
category: "Best Practices",
|
|
13
|
+
recommended: true,
|
|
14
|
+
},
|
|
15
|
+
fixable: "code",
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: {
|
|
18
|
+
noDtoConstructor: "DTO class '{{className}}' must not have a constructor. DTOs are pure data containers that are automatically instantiated by class-transformer from JSON. Remove the constructor and let the framework handle object creation.",
|
|
19
|
+
constructorRemoved: "Constructor removed from DTO class '{{className}}'",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
create(context) {
|
|
24
|
+
const filename = context.getFilename();
|
|
25
|
+
|
|
26
|
+
// Ignoriere Test-Dateien und Non-DTO-Dateien
|
|
27
|
+
if (!filename.includes("/dto/") ||
|
|
28
|
+
filename.includes("test") ||
|
|
29
|
+
filename.includes("spec") ||
|
|
30
|
+
filename.includes("__tests__") ||
|
|
31
|
+
filename.endsWith(".test.ts") ||
|
|
32
|
+
filename.endsWith(".spec.ts")) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isDtoClass(className) {
|
|
37
|
+
return (
|
|
38
|
+
className.endsWith("Dto") ||
|
|
39
|
+
className.endsWith("DTO") ||
|
|
40
|
+
className.includes("Request") ||
|
|
41
|
+
className.includes("Response") ||
|
|
42
|
+
className.includes("Create") ||
|
|
43
|
+
className.includes("Update") ||
|
|
44
|
+
className.includes("Delete")
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isInDtoClass(node) {
|
|
49
|
+
let parent = node.parent;
|
|
50
|
+
|
|
51
|
+
// Traverse up to find the class declaration
|
|
52
|
+
while (parent) {
|
|
53
|
+
if (parent.type === "ClassDeclaration" ||
|
|
54
|
+
(parent.type === "ExportDefaultDeclaration" && parent.declaration?.type === "ClassDeclaration")) {
|
|
55
|
+
|
|
56
|
+
const classNode = parent.type === "ClassDeclaration" ? parent : parent.declaration;
|
|
57
|
+
const className = classNode.id?.name;
|
|
58
|
+
|
|
59
|
+
if (className && isDtoClass(className)) {
|
|
60
|
+
return { isDto: true, className, classNode };
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
parent = parent.parent;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { isDto: false, className: null, classNode: null };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
MethodDefinition(node) {
|
|
72
|
+
// Prüfe nur Konstruktoren
|
|
73
|
+
if (node.kind !== "constructor") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const { isDto, className } = isInDtoClass(node);
|
|
78
|
+
|
|
79
|
+
if (isDto) {
|
|
80
|
+
context.report({
|
|
81
|
+
node,
|
|
82
|
+
messageId: "noDtoConstructor",
|
|
83
|
+
data: {
|
|
84
|
+
className: className || "unknown"
|
|
85
|
+
},
|
|
86
|
+
fix(fixer) {
|
|
87
|
+
const sourceCode = context.getSourceCode();
|
|
88
|
+
|
|
89
|
+
// Finde den Konstruktor-Block
|
|
90
|
+
const constructorStart = node.range[0];
|
|
91
|
+
const constructorEnd = node.range[1];
|
|
92
|
+
|
|
93
|
+
// Prüfe, ob es Leerzeilen vor/nach dem Konstruktor gibt
|
|
94
|
+
const beforeConstructor = sourceCode.getText().substring(0, constructorStart);
|
|
95
|
+
const afterConstructor = sourceCode.getText().substring(constructorEnd);
|
|
96
|
+
|
|
97
|
+
// Entferne auch überflüssige Leerzeilen
|
|
98
|
+
let startPos = constructorStart;
|
|
99
|
+
let endPos = constructorEnd;
|
|
100
|
+
|
|
101
|
+
// Prüfe auf Leerzeilen vor dem Konstruktor
|
|
102
|
+
const lines = beforeConstructor.split('\n');
|
|
103
|
+
if (lines.length > 1 && lines[lines.length - 1].trim() === '') {
|
|
104
|
+
// Finde die Position der letzten nicht-leeren Zeile vor dem Konstruktor
|
|
105
|
+
for (let i = lines.length - 2; i >= 0; i--) {
|
|
106
|
+
if (lines[i].trim() !== '') {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
startPos -= (lines[lines.length - 1 - (lines.length - 2 - i)].length + 1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Prüfe auf Leerzeilen nach dem Konstruktor
|
|
114
|
+
const afterLines = afterConstructor.split('\n');
|
|
115
|
+
if (afterLines.length > 1 && afterLines[0].trim() === '') {
|
|
116
|
+
endPos += afterLines[0].length + 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return fixer.removeRange([startPos, endPos]);
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint-Regel: no-dto-default-values
|
|
3
|
+
* Verbietet Default Values in DTO-Klassen
|
|
4
|
+
* Verbesserte Version für TypeScript
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
8
|
+
const noDtoDefaultValuesRule = {
|
|
9
|
+
meta: {
|
|
10
|
+
type: "problem",
|
|
11
|
+
docs: {
|
|
12
|
+
description: "DTO-Klassen dürfen keine Default Values haben",
|
|
13
|
+
category: "Best Practices",
|
|
14
|
+
recommended: true,
|
|
15
|
+
},
|
|
16
|
+
fixable: "code",
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
noDefaultValue: "DTO property '{{propertyName}}' darf keinen Default Value haben. Verwende stattdessen explizite Typ-Annotation ohne Initialisierung.",
|
|
20
|
+
noDefaultValueInConstructor: "DTO-Properties dürfen nicht im Constructor initialisiert werden. Verwende stattdessen die create()-Methode.",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
create(context) {
|
|
24
|
+
function isDtoClass(node) {
|
|
25
|
+
return node.id && node.id.name && node.id.name.endsWith('Dto');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function hasDefaultValue(member) {
|
|
29
|
+
// Für Getter: Prüfe Return-Statements im Body
|
|
30
|
+
if (member.type === "MethodDefinition" && member.kind === "get" && member.value?.body) {
|
|
31
|
+
const returnStatements = member.value.body.body.filter(stmt =>
|
|
32
|
+
stmt.type === "ReturnStatement" && stmt.argument
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Wenn es Returns mit Literal-Werten gibt (außer undefined), ist das ein Default
|
|
36
|
+
return returnStatements.some(stmt => {
|
|
37
|
+
const arg = stmt.argument;
|
|
38
|
+
return arg.type === "ObjectExpression" ||
|
|
39
|
+
(arg.type === "Literal" && arg.value !== undefined) ||
|
|
40
|
+
(arg.type === "ArrayExpression");
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Für reguläre Properties: Prüfe ob value vorhanden ist
|
|
45
|
+
return member.value !== null && member.value !== undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isPrivateProperty(propertyName) {
|
|
49
|
+
return propertyName.startsWith('_');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function inferTypeFromValue(value) {
|
|
53
|
+
if (!value) return "unknown";
|
|
54
|
+
switch (value.type) {
|
|
55
|
+
case "Literal":
|
|
56
|
+
if (typeof value.value === "string") return "string";
|
|
57
|
+
if (typeof value.value === "number") return "number";
|
|
58
|
+
if (typeof value.value === "boolean") return "boolean";
|
|
59
|
+
return "unknown";
|
|
60
|
+
case "ArrayExpression": return "any[]";
|
|
61
|
+
case "ObjectExpression": return "object";
|
|
62
|
+
case "NewExpression": return "object";
|
|
63
|
+
case "Identifier":
|
|
64
|
+
if (value.name === "true" || value.name === "false") return "boolean";
|
|
65
|
+
if (value.name === "null") return "null";
|
|
66
|
+
if (value.name === "undefined") return "undefined";
|
|
67
|
+
return "unknown";
|
|
68
|
+
default: return "unknown";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function checkPropertyForDefaultValue(member) {
|
|
73
|
+
if (!member.key?.name) return;
|
|
74
|
+
|
|
75
|
+
const propertyName = member.key.name;
|
|
76
|
+
|
|
77
|
+
// Überspringe private Properties und statische Properties
|
|
78
|
+
if (isPrivateProperty(propertyName) || member.static) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Für Getter: Prüfe ob der Body einen Default-Wert zurückgibt
|
|
83
|
+
if (member.type === "MethodDefinition" && member.kind === "get" && member.value?.body) {
|
|
84
|
+
const returnStatements = member.value.body.body.filter(stmt =>
|
|
85
|
+
stmt.type === "ReturnStatement" && stmt.argument
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Wenn alle Returns Literal-Werte haben (außer undefined), ist das ein Default
|
|
89
|
+
const hasDefaultReturn = returnStatements.some(stmt => {
|
|
90
|
+
const arg = stmt.argument;
|
|
91
|
+
return arg.type === "ObjectExpression" ||
|
|
92
|
+
(arg.type === "Literal" && arg.value !== undefined) ||
|
|
93
|
+
(arg.type === "ArrayExpression");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (hasDefaultReturn) {
|
|
97
|
+
context.report({
|
|
98
|
+
node: member,
|
|
99
|
+
messageId: "noDefaultValue",
|
|
100
|
+
data: { propertyName },
|
|
101
|
+
fix(fixer) {
|
|
102
|
+
// Für Getter: Entferne die Returns mit Default-Werten
|
|
103
|
+
const statementsToRemove = returnStatements.filter(stmt => {
|
|
104
|
+
const arg = stmt.argument;
|
|
105
|
+
return arg.type === "ObjectExpression" ||
|
|
106
|
+
(arg.type === "Literal" && arg.value !== undefined) ||
|
|
107
|
+
(arg.type === "ArrayExpression");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (statementsToRemove.length > 0) {
|
|
111
|
+
const ranges = statementsToRemove.map(stmt => stmt.range);
|
|
112
|
+
// Entferne die Statements von hinten nach vorne
|
|
113
|
+
return ranges.reverse().map(range => fixer.removeRange(range));
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Prüfe ob Default Value vorhanden ist (für reguläre Properties)
|
|
123
|
+
if (hasDefaultValue(member)) {
|
|
124
|
+
context.report({
|
|
125
|
+
node: member,
|
|
126
|
+
messageId: "noDefaultValue",
|
|
127
|
+
data: { propertyName },
|
|
128
|
+
fix(fixer) {
|
|
129
|
+
if (member.typeAnnotation) {
|
|
130
|
+
// Hat bereits Type Annotation, entferne nur den Default Value
|
|
131
|
+
const valueStart = member.value.range[0];
|
|
132
|
+
// Finde das "=" Zeichen vor dem Wert
|
|
133
|
+
let equalPos = valueStart - 1;
|
|
134
|
+
const sourceCode = context.getSourceCode();
|
|
135
|
+
const text = sourceCode.getText();
|
|
136
|
+
while (equalPos > 0 && text[equalPos] !== '=') {
|
|
137
|
+
equalPos--;
|
|
138
|
+
}
|
|
139
|
+
// Entferne alles von "=" bis zum Ende des Wertes (inklusive Whitespace)
|
|
140
|
+
return fixer.removeRange([equalPos - 1, member.value.range[1]]);
|
|
141
|
+
} else {
|
|
142
|
+
// Keine Type Annotation, infere Typ und ersetze
|
|
143
|
+
const inferredType = inferTypeFromValue(member.value);
|
|
144
|
+
if (inferredType !== "unknown") {
|
|
145
|
+
return fixer.replaceText(member, `${propertyName}: ${inferredType};`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
ClassDeclaration(node) {
|
|
156
|
+
if (!isDtoClass(node)) return;
|
|
157
|
+
|
|
158
|
+
node.body.body.forEach(member => {
|
|
159
|
+
// Unterstütze verschiedene Property-Typen für TypeScript
|
|
160
|
+
if ((member.type === "PropertyDefinition" ||
|
|
161
|
+
member.type === "ClassProperty" ||
|
|
162
|
+
member.type === "TSPropertySignature" ||
|
|
163
|
+
(member.type === "MethodDefinition" && member.kind === "get")) &&
|
|
164
|
+
member.key?.name &&
|
|
165
|
+
member.kind !== "method" &&
|
|
166
|
+
member.kind !== "constructor") {
|
|
167
|
+
checkPropertyForDefaultValue(member);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
ClassExpression(node) {
|
|
173
|
+
if (!isDtoClass(node)) return;
|
|
174
|
+
|
|
175
|
+
node.body.body.forEach(member => {
|
|
176
|
+
if ((member.type === "PropertyDefinition" ||
|
|
177
|
+
member.type === "ClassProperty" ||
|
|
178
|
+
member.type === "TSPropertySignature" ||
|
|
179
|
+
(member.type === "MethodDefinition" && member.kind === "get")) &&
|
|
180
|
+
member.key?.name &&
|
|
181
|
+
member.kind !== "method" &&
|
|
182
|
+
member.kind !== "constructor") {
|
|
183
|
+
checkPropertyForDefaultValue(member);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
MethodDefinition(node) {
|
|
189
|
+
if (node.kind === "constructor") {
|
|
190
|
+
const classNode = node.parent.parent;
|
|
191
|
+
if (!isDtoClass(classNode)) return;
|
|
192
|
+
|
|
193
|
+
if (node.value.body) {
|
|
194
|
+
node.value.body.body.forEach(stmt => {
|
|
195
|
+
if (stmt.type === "ExpressionStatement" &&
|
|
196
|
+
stmt.expression.type === "AssignmentExpression" &&
|
|
197
|
+
stmt.expression.left.type === "MemberExpression" &&
|
|
198
|
+
stmt.expression.left.object.type === "ThisExpression") {
|
|
199
|
+
const propertyName = stmt.expression.left.property.name;
|
|
200
|
+
if (!isPrivateProperty(propertyName)) {
|
|
201
|
+
context.report({
|
|
202
|
+
node: stmt,
|
|
203
|
+
messageId: "noDefaultValueInConstructor",
|
|
204
|
+
data: { propertyName },
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export default {
|
|
217
|
+
rules: {
|
|
218
|
+
"no-dto-default-values": noDtoDefaultValuesRule,
|
|
219
|
+
},
|
|
220
|
+
};
|