@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,127 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const noDtoDuplicatesRule = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: "problem",
|
|
7
|
+
docs: {
|
|
8
|
+
description: "Prevents duplicate DTO class names across the dto directory",
|
|
9
|
+
category: "Architecture",
|
|
10
|
+
recommended: true,
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
dtoDuplicateFound: "DTO-Klasse '{{className}}' existiert bereits in '{{existingFile}}'. Duplikate sind nicht erlaubt. Aktuelle Datei: '{{currentFile}}'. Entscheide selbst, welche Datei wir behalten! Lösche alle unnötigen - passe ggf. den Namen der Datei+Klasse an und korrigiere die Imports.",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
return {
|
|
19
|
+
ClassDeclaration(node) {
|
|
20
|
+
const filename = context.getFilename();
|
|
21
|
+
const className = node.id?.name;
|
|
22
|
+
|
|
23
|
+
if (!className) return;
|
|
24
|
+
|
|
25
|
+
// Prüfe nur Dateien im dto-Verzeichnis
|
|
26
|
+
if (!filename.includes("/dto/") || !filename.endsWith(".ts")) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Prüfe, ob es sich um eine DTO-Klasse handelt (endet mit "Dto")
|
|
31
|
+
if (!className.endsWith("Dto")) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// In Tests verwenden wir gemockte Dateien
|
|
36
|
+
|
|
37
|
+
// Suche nach anderen Dateien mit dem gleichen Klassennamen
|
|
38
|
+
const dtoDirectory = findDtoDirectory(filename);
|
|
39
|
+
if (!dtoDirectory) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const duplicateFiles = findDuplicateFiles(dtoDirectory, className, filename);
|
|
44
|
+
|
|
45
|
+
if (duplicateFiles.length > 0) {
|
|
46
|
+
// Berichte den ersten gefundenen Duplikat
|
|
47
|
+
const existingFile = duplicateFiles[0];
|
|
48
|
+
context.report({
|
|
49
|
+
node: node,
|
|
50
|
+
messageId: "dtoDuplicateFound",
|
|
51
|
+
data: {
|
|
52
|
+
className: className,
|
|
53
|
+
currentFile: filename,
|
|
54
|
+
existingFile: existingFile
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Hilfsmethode um das dto-Verzeichnis zu finden
|
|
64
|
+
function findDtoDirectory(filename) {
|
|
65
|
+
const pathParts = filename.split(path.sep);
|
|
66
|
+
const dtoIndex = pathParts.findIndex(part => part === "dto");
|
|
67
|
+
|
|
68
|
+
if (dtoIndex === -1) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Konstruiere den Pfad bis zum dto-Verzeichnis
|
|
73
|
+
return pathParts.slice(0, dtoIndex + 1).join(path.sep);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Hilfsmethode um Duplikate zu finden
|
|
77
|
+
function findDuplicateFiles(dtoDirectory, className, currentFile) {
|
|
78
|
+
const duplicateFiles = [];
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// Rekursiv durchsuche das dto-Verzeichnis
|
|
82
|
+
const searchDirectory = (dir) => {
|
|
83
|
+
if (!fs.existsSync(dir)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const items = fs.readdirSync(dir);
|
|
88
|
+
|
|
89
|
+
for (const item of items) {
|
|
90
|
+
const fullPath = path.join(dir, item);
|
|
91
|
+
const stat = fs.statSync(fullPath);
|
|
92
|
+
|
|
93
|
+
if (stat.isDirectory()) {
|
|
94
|
+
// Rekursiv in Unterverzeichnisse suchen
|
|
95
|
+
searchDirectory(fullPath);
|
|
96
|
+
} else if (stat.isFile() && item.endsWith('.ts')) {
|
|
97
|
+
// Überspringe die aktuelle Datei
|
|
98
|
+
if (fullPath === currentFile) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
104
|
+
|
|
105
|
+
// Suche nach der Klasse mit dem gleichen Namen
|
|
106
|
+
const classRegex = new RegExp(`export\\s+(default\\s+)?class\\s+${className}\\b`);
|
|
107
|
+
if (classRegex.test(content)) {
|
|
108
|
+
duplicateFiles.push(fullPath);
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// Ignoriere Dateien, die nicht gelesen werden können
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
searchDirectory(dtoDirectory);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
// Ignoriere Fehler beim Durchsuchen der Dateien
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return duplicateFiles;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default noDtoDuplicatesRule;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* ESLint-Regel: Verhindert die Verwendung von DTOs in Entity-Dateien
|
|
4
|
+
* Für JSONB-Felder müssen eigene Typen definiert werden
|
|
5
|
+
*/
|
|
6
|
+
export default {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Verhindert die Verwendung von DTOs in Entity-Dateien. Für JSONB-Felder müssen eigene Typen definiert werden.",
|
|
11
|
+
category: "Architecture",
|
|
12
|
+
recommended: true,
|
|
13
|
+
},
|
|
14
|
+
hasSuggestions: true,
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
dtoInEntity: "Entity '{{entityName}}' verwendet DTO '{{dtoName}}' in Property '{{propertyName}}'. Für JSONB-Felder müssen eigene Typen definiert werden.",
|
|
18
|
+
suggestType: "Definiere einen eigenen Typ für '{{propertyName}}' anstatt '{{dtoName}}' zu verwenden.",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
create(context) {
|
|
23
|
+
const filename = context.getFilename();
|
|
24
|
+
|
|
25
|
+
// Regel nur auf Entity-Dateien im backend anwenden
|
|
26
|
+
if (!filename.includes("/backend/src/entity/") || !filename.endsWith(".ts")) {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Prüfe, ob es sich um eine Entity-Datei handelt
|
|
31
|
+
const isEntityFile = filename.includes("/entity/") &&
|
|
32
|
+
!filename.includes("/dto/") &&
|
|
33
|
+
!filename.includes("/service/") &&
|
|
34
|
+
!filename.includes("/controller/");
|
|
35
|
+
|
|
36
|
+
if (!isEntityFile) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
ClassDeclaration(node) {
|
|
42
|
+
if (!node.id?.name) return;
|
|
43
|
+
|
|
44
|
+
const className = node.id.name;
|
|
45
|
+
|
|
46
|
+
// Prüfe alle Properties der Klasse
|
|
47
|
+
node.body.body.forEach(member => {
|
|
48
|
+
if (member.type === "PropertyDefinition" && member.key?.name) {
|
|
49
|
+
const propertyName = member.key.name;
|
|
50
|
+
|
|
51
|
+
// Prüfe Type-Annotation
|
|
52
|
+
if (member.typeAnnotation?.typeAnnotation) {
|
|
53
|
+
const typeAnnotation = member.typeAnnotation.typeAnnotation;
|
|
54
|
+
|
|
55
|
+
// Prüfe auf DTO-Verwendung
|
|
56
|
+
if (typeAnnotation.type === "TSTypeReference") {
|
|
57
|
+
const typeName = typeAnnotation.typeName?.name;
|
|
58
|
+
|
|
59
|
+
// Prüfe, ob der Typ ein DTO ist (endet mit "Dto" oder "EntityDto")
|
|
60
|
+
if (typeName && (typeName.endsWith("Dto") || typeName.endsWith("EntityDto"))) {
|
|
61
|
+
context.report({
|
|
62
|
+
node: member,
|
|
63
|
+
messageId: "dtoInEntity",
|
|
64
|
+
data: {
|
|
65
|
+
entityName: className,
|
|
66
|
+
dtoName: typeName,
|
|
67
|
+
propertyName: propertyName,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Prüfe Union-Types
|
|
74
|
+
if (typeAnnotation.type === "TSUnionType") {
|
|
75
|
+
typeAnnotation.types.forEach(unionType => {
|
|
76
|
+
if (unionType.type === "TSTypeReference") {
|
|
77
|
+
const typeName = unionType.typeName?.name;
|
|
78
|
+
|
|
79
|
+
if (typeName && (typeName.endsWith("Dto") || typeName.endsWith("EntityDto"))) {
|
|
80
|
+
context.report({
|
|
81
|
+
node: member,
|
|
82
|
+
messageId: "dtoInEntity",
|
|
83
|
+
data: {
|
|
84
|
+
entityName: className,
|
|
85
|
+
dtoName: typeName,
|
|
86
|
+
propertyName: propertyName,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Verbietet import("...").Type Syntax in Typ-Annotationen
|
|
3
|
+
* @author Echoes of Order Team
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Verbietet import(\"...\").Type Syntax in Typ-Annotationen. Verwende stattdessen explizite Imports.",
|
|
11
|
+
category: "Best Practices",
|
|
12
|
+
recommended: true,
|
|
13
|
+
},
|
|
14
|
+
messages: {
|
|
15
|
+
noDynamicImportInType: "import(\"{{path}}\").{{type}} ist verboten. Importiere '{{type}}' stattdessen direkt: import {{type}} from \"{{path}}\";",
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
create (context) {
|
|
21
|
+
function checkForDynamicImport (node) {
|
|
22
|
+
if (
|
|
23
|
+
node.type === "TSImportType" &&
|
|
24
|
+
node.argument &&
|
|
25
|
+
node.argument.type === "Literal" &&
|
|
26
|
+
typeof node.argument.value === "string"
|
|
27
|
+
) {
|
|
28
|
+
const importPath = node.argument.value;
|
|
29
|
+
const typeName = node.qualifier?.name || node.qualifier?.right?.name || "Type";
|
|
30
|
+
|
|
31
|
+
context.report({
|
|
32
|
+
messageId: "noDynamicImportInType",
|
|
33
|
+
node,
|
|
34
|
+
data: {
|
|
35
|
+
path: importPath,
|
|
36
|
+
type: typeName,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
TSImportType (node) {
|
|
44
|
+
checkForDynamicImport(node);
|
|
45
|
+
},
|
|
46
|
+
TSTypeReference (node) {
|
|
47
|
+
if (node.typeName && node.typeName.type === "TSQualifiedName") {
|
|
48
|
+
const current = node.typeName;
|
|
49
|
+
if (
|
|
50
|
+
current.left.type === "TSImportType" &&
|
|
51
|
+
current.left.argument &&
|
|
52
|
+
current.left.argument.type === "Literal"
|
|
53
|
+
) {
|
|
54
|
+
const importPath = current.left.argument.value;
|
|
55
|
+
const typeName = current.right.name;
|
|
56
|
+
|
|
57
|
+
context.report({
|
|
58
|
+
messageId: "noDynamicImportInType",
|
|
59
|
+
node,
|
|
60
|
+
data: {
|
|
61
|
+
path: importPath,
|
|
62
|
+
type: typeName,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint-Regel: no-dynamic-imports-in-controllers
|
|
3
|
+
* Verbotet dynamische Imports in Controller-Dateien
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
7
|
+
const noDynamicImportsInControllersRule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Verbotet dynamische Imports in Controller-Dateien",
|
|
12
|
+
category: "Best Practices",
|
|
13
|
+
recommended: true,
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
noDynamicImport: "Dynamic imports inside classes are not allowed. Import the class at top level and instantiate it with 'new' instead.",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
let classDepth = 0;
|
|
22
|
+
const reported = new WeakSet();
|
|
23
|
+
|
|
24
|
+
const isImportCall = (node) => {
|
|
25
|
+
return (
|
|
26
|
+
node &&
|
|
27
|
+
node.type === "CallExpression" &&
|
|
28
|
+
((node.callee && node.callee.type === "Identifier" && node.callee.name === "import") ||
|
|
29
|
+
(node.callee && node.callee.type === "Import"))
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const isImportExpression = (node) => {
|
|
34
|
+
return node && (node.type === "ImportExpression" || isImportCall(node));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const findInnerImport = (node) => {
|
|
38
|
+
if (!node) return null;
|
|
39
|
+
if (node.type === "ImportExpression") return node;
|
|
40
|
+
if (isImportCall(node)) return node;
|
|
41
|
+
if (node.type === "AwaitExpression") return findInnerImport(node.argument);
|
|
42
|
+
if (node.type === "ParenthesizedExpression") return findInnerImport(node.expression);
|
|
43
|
+
if (node.type === "MemberExpression") return findInnerImport(node.object);
|
|
44
|
+
return null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const reportOnceIfInsideClass = (importNode) => {
|
|
48
|
+
if (!importNode) return;
|
|
49
|
+
if (classDepth <= 0) return;
|
|
50
|
+
if (reported.has(importNode)) return;
|
|
51
|
+
reported.add(importNode);
|
|
52
|
+
context.report({ node: importNode, messageId: "noDynamicImport" });
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
ClassBody () {
|
|
57
|
+
classDepth += 1;
|
|
58
|
+
},
|
|
59
|
+
"ClassBody:exit" () {
|
|
60
|
+
classDepth -= 1;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
ImportExpression (node) {
|
|
64
|
+
reportOnceIfInsideClass(node);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
CallExpression (node) {
|
|
68
|
+
if (isImportCall(node)) {
|
|
69
|
+
reportOnceIfInsideClass(node);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
AwaitExpression (node) {
|
|
74
|
+
const inner = findInnerImport(node);
|
|
75
|
+
reportOnceIfInsideClass(inner);
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
ParenthesizedExpression (node) {
|
|
79
|
+
const inner = findInnerImport(node);
|
|
80
|
+
reportOnceIfInsideClass(inner);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
MemberExpression (node) {
|
|
84
|
+
const inner = findInnerImport(node);
|
|
85
|
+
reportOnceIfInsideClass(inner);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default {
|
|
92
|
+
rules: {
|
|
93
|
+
"no-dynamic-imports-in-controllers": noDynamicImportsInControllersRule,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint-Regel: Verbietet den Import von Entities in Controllern
|
|
3
|
+
* Controller sollten nur DTOs verwenden, nie direkt mit Entities arbeiten
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
7
|
+
const noEntityImportsInControllersRule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Controller dürfen keine Entities importieren, nur DTOs verwenden",
|
|
12
|
+
category: "Architecture",
|
|
13
|
+
recommended: true,
|
|
14
|
+
},
|
|
15
|
+
hasSuggestions: true,
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: {
|
|
18
|
+
noEntityImport: "Controller dürfen keine Entities importieren. Verwende stattdessen DTOs: '{{entityPath}}'",
|
|
19
|
+
useDto: "Ersetze den Entity-Import durch den entsprechenden DTO aus @/dto/",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
create(context) {
|
|
23
|
+
const controllerFiles = /Controller\.ts$/;
|
|
24
|
+
const filename = context.getFilename();
|
|
25
|
+
const isControllerFile = controllerFiles.test(filename) &&
|
|
26
|
+
!/BaseController\.ts$/.test(filename) &&
|
|
27
|
+
filename.includes("/controller/");
|
|
28
|
+
|
|
29
|
+
if (!isControllerFile) return {};
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
ImportDeclaration(node) {
|
|
33
|
+
const importPath = node.source.value;
|
|
34
|
+
|
|
35
|
+
// Prüfe auf Imports aus @/entity
|
|
36
|
+
if (typeof importPath === 'string' && importPath.startsWith('@/entity')) {
|
|
37
|
+
context.report({
|
|
38
|
+
node,
|
|
39
|
+
messageId: "noEntityImport",
|
|
40
|
+
data: {
|
|
41
|
+
entityPath: importPath,
|
|
42
|
+
},
|
|
43
|
+
suggest: [
|
|
44
|
+
{
|
|
45
|
+
desc: "Verwende stattdessen einen DTO aus @/dto/",
|
|
46
|
+
fix(fixer) {
|
|
47
|
+
// Extrahiere den Entity-Namen und schlage DTO-Import vor
|
|
48
|
+
const entityName = importPath.split('/').pop();
|
|
49
|
+
const dtoPath = importPath.replace('@/entity', '@/dto');
|
|
50
|
+
const suggestedDtoPath = dtoPath.replace(/Entity$/, 'Dto');
|
|
51
|
+
|
|
52
|
+
return fixer.replaceText(
|
|
53
|
+
node.source,
|
|
54
|
+
`"${suggestedDtoPath}"`
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
return; // Verhindert doppelte Meldungen
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Prüfe auch auf relative Imports zu Entities (nur wenn nicht bereits @/entity gefunden)
|
|
64
|
+
if (typeof importPath === 'string' &&
|
|
65
|
+
(importPath.includes('../entity/') ||
|
|
66
|
+
importPath.includes('./entity/'))) {
|
|
67
|
+
context.report({
|
|
68
|
+
node,
|
|
69
|
+
messageId: "noEntityImport",
|
|
70
|
+
data: {
|
|
71
|
+
entityPath: importPath,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Prüfe auch auf dynamische Imports
|
|
78
|
+
ImportExpression(node) {
|
|
79
|
+
if (node.source.type === 'Literal') {
|
|
80
|
+
const importPath = node.source.value;
|
|
81
|
+
|
|
82
|
+
if (typeof importPath === 'string' && importPath.startsWith('@/entity')) {
|
|
83
|
+
context.report({
|
|
84
|
+
node,
|
|
85
|
+
messageId: "noEntityImport",
|
|
86
|
+
data: {
|
|
87
|
+
entityPath: importPath,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default {
|
|
98
|
+
rules: {
|
|
99
|
+
"no-entity-imports-in-controllers": noEntityImportsInControllersRule,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "problem",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Prevent usage of Entity references in Swagger documentation",
|
|
6
|
+
category: "Best Practices",
|
|
7
|
+
recommended: true,
|
|
8
|
+
},
|
|
9
|
+
fixable: null,
|
|
10
|
+
schema: [],
|
|
11
|
+
messages: {
|
|
12
|
+
entityInSwaggerSchema: "Swagger schema reference '{{reference}}' should use DTO instead of Entity. Replace '{{entityName}}' with '{{dtoName}}'",
|
|
13
|
+
entityInApiProperty: "ApiProperty type '{{type}}' should use DTO instead of Entity. Replace '{{entityName}}' with '{{dtoName}}'",
|
|
14
|
+
entityInApiBody: "ApiBody type '{{type}}' should use DTO instead of Entity. Replace '{{entityName}}' with '{{dtoName}}'",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
const entityPattern = /Entity$/;
|
|
19
|
+
const dtoPattern = /Dto$/;
|
|
20
|
+
|
|
21
|
+
function getEntityName(reference) {
|
|
22
|
+
const match = reference.match(/#\/components\/schemas\/(.+)/);
|
|
23
|
+
return match ? match[1] : reference;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getDtoName(entityName) {
|
|
27
|
+
return entityName.replace(/Entity$/, "Dto");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function checkStringLiteral(node, messageId) {
|
|
31
|
+
if (node.type === "Literal" && typeof node.value === "string") {
|
|
32
|
+
const value = node.value;
|
|
33
|
+
|
|
34
|
+
// Check for schema references like "#/components/schemas/AbilityEntity"
|
|
35
|
+
if (value.includes("#/components/schemas/") && value.includes("Entity")) {
|
|
36
|
+
const entityName = getEntityName(value);
|
|
37
|
+
if (entityPattern.test(entityName)) {
|
|
38
|
+
const dtoName = getDtoName(entityName);
|
|
39
|
+
context.report({
|
|
40
|
+
node,
|
|
41
|
+
messageId,
|
|
42
|
+
data: {
|
|
43
|
+
reference: value,
|
|
44
|
+
entityName,
|
|
45
|
+
dtoName,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function checkTypeReference(node, messageId) {
|
|
54
|
+
// Check for type references in ApiProperty, ApiBody decorators
|
|
55
|
+
if (node.type === "Identifier" && entityPattern.test(node.name)) {
|
|
56
|
+
const dtoName = getDtoName(node.name);
|
|
57
|
+
context.report({
|
|
58
|
+
node,
|
|
59
|
+
messageId,
|
|
60
|
+
data: {
|
|
61
|
+
type: node.name,
|
|
62
|
+
entityName: node.name,
|
|
63
|
+
dtoName,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function checkObjectExpression(node) {
|
|
70
|
+
if (node.type === "ObjectExpression") {
|
|
71
|
+
node.properties.forEach((prop) => {
|
|
72
|
+
if (prop.type === "Property") {
|
|
73
|
+
// Check schema references in object properties
|
|
74
|
+
if (prop.key && prop.key.name === "$ref") {
|
|
75
|
+
checkStringLiteral(prop.value, "entityInSwaggerSchema");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check items property in arrays
|
|
79
|
+
if (prop.key && prop.key.name === "items" && prop.value.type === "ObjectExpression") {
|
|
80
|
+
prop.value.properties.forEach((itemProp) => {
|
|
81
|
+
if (itemProp.key && itemProp.key.name === "$ref") {
|
|
82
|
+
checkStringLiteral(itemProp.value, "entityInSwaggerSchema");
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
Decorator(node) {
|
|
93
|
+
if (node.expression && node.expression.type === "CallExpression") {
|
|
94
|
+
const callee = node.expression.callee;
|
|
95
|
+
|
|
96
|
+
// Check ApiResponse, ApiBody, ApiProperty decorators
|
|
97
|
+
if (callee.type === "Identifier") {
|
|
98
|
+
const decoratorName = callee.name;
|
|
99
|
+
|
|
100
|
+
if (["ApiResponse", "ApiBody", "ApiProperty"].includes(decoratorName)) {
|
|
101
|
+
node.expression.arguments.forEach((arg) => {
|
|
102
|
+
if (arg.type === "ObjectExpression") {
|
|
103
|
+
arg.properties.forEach((prop) => {
|
|
104
|
+
if (prop.type === "Property") {
|
|
105
|
+
// Check type property
|
|
106
|
+
if (prop.key && prop.key.name === "type") {
|
|
107
|
+
checkTypeReference(prop.value, "entityInApiProperty");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check schema property
|
|
111
|
+
if (prop.key && prop.key.name === "schema") {
|
|
112
|
+
checkObjectExpression(prop.value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// Check for $ref in any string literal
|
|
124
|
+
Literal(node) {
|
|
125
|
+
if (typeof node.value === "string" && node.value.includes("Entity")) {
|
|
126
|
+
const parent = node.parent;
|
|
127
|
+
|
|
128
|
+
// Check if this is in a swagger context
|
|
129
|
+
if (parent && parent.type === "Property" && parent.key) {
|
|
130
|
+
if (parent.key.name === "$ref" ||
|
|
131
|
+
(parent.key.type === "Literal" && parent.key.value === "$ref")) {
|
|
132
|
+
checkStringLiteral(node, "entityInSwaggerSchema");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
};
|