@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,519 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint-Regel: Prüft korrekte Typ-Übereinstimmung zwischen Entity und DTO Properties
|
|
3
|
+
* Entity-Typen sollten korrekt zu DTO-Typen transformiert werden:
|
|
4
|
+
* - AuraDefinitionEntity -> AuraDefinitionEntityDto
|
|
5
|
+
* - CharacterEntity -> CharacterEntityDto
|
|
6
|
+
* - etc.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
12
|
+
export default {
|
|
13
|
+
meta: {
|
|
14
|
+
type: "problem",
|
|
15
|
+
docs: {
|
|
16
|
+
description: "Entity-DTO Properties müssen korrekte DTO-Typen haben",
|
|
17
|
+
category: "Architecture",
|
|
18
|
+
recommended: true,
|
|
19
|
+
},
|
|
20
|
+
hasSuggestions: true,
|
|
21
|
+
schema: [],
|
|
22
|
+
messages: {
|
|
23
|
+
incorrectEntityType: "Entity-DTO '{{dtoName}}' Property '{{prop}}' hat falschen Typ: DTO hat '{{dtoType}}', Entity hat '{{entityType}}'. Erwartet: {{expectedDtoType}}",
|
|
24
|
+
incorrectOptionality: "Entity-DTO '{{dtoName}}' Property '{{prop}}' hat falsche Optionalität: DTO hat '{{dtoType}}', Entity hat '{{entityType}}'",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
create(context) {
|
|
29
|
+
const filename = context.getFilename();
|
|
30
|
+
|
|
31
|
+
// Test-Fixtures werden jetzt auch von der Regel geprüft
|
|
32
|
+
|
|
33
|
+
function transformEntityToDto(entityType) {
|
|
34
|
+
// Korrekte Transformation von Entity-Typen zu DTO-Typen
|
|
35
|
+
if (entityType.endsWith("Entity")) {
|
|
36
|
+
return entityType.replace("Entity", "EntityDto");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Handle Array-Types: RaceTranslationEntity[] -> RaceTranslationEntityDto[]
|
|
40
|
+
if (entityType.endsWith("Entity[]")) {
|
|
41
|
+
return entityType.replace("Entity[]", "EntityDto[]");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Handle Generic-Types: BackpackRefItemEntity<IsBackpack> -> BackpackRefItemEntityDto<IsBackpack>
|
|
45
|
+
if (entityType.includes("Entity<")) {
|
|
46
|
+
return entityType.replace(/Entity</g, "EntityDto<");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Handle Union-Types: RaceTranslationEntity | null -> RaceTranslationEntityDto | null
|
|
50
|
+
if (entityType.includes("Entity") && entityType.includes("|")) {
|
|
51
|
+
return entityType.replace(/Entity/g, "EntityDto");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return entityType;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractBaseType(typeString) {
|
|
58
|
+
// Entferne Union-Types und extrahiere den Basis-Typ
|
|
59
|
+
if (typeString.includes("|")) {
|
|
60
|
+
return typeString.split("|")[0].trim();
|
|
61
|
+
}
|
|
62
|
+
return typeString;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isOptional(typeString) {
|
|
66
|
+
return typeString.includes("?") || typeString.includes("undefined");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isComplexObjectType(typeString) {
|
|
70
|
+
// Prüfe ob es sich um einen komplexen Objekt-Typ handelt
|
|
71
|
+
return typeString.includes("{complex-object}") ||
|
|
72
|
+
typeString.includes("Settings") ||
|
|
73
|
+
typeString.includes("Maps") ||
|
|
74
|
+
typeString.includes("Data") ||
|
|
75
|
+
typeString.includes("Config") ||
|
|
76
|
+
(typeString.includes("{") && typeString.includes("}") && typeString.length > 50) ||
|
|
77
|
+
// Spezielle Behandlung für Texture-spezifische Typen
|
|
78
|
+
(typeString.includes("colorAdjustment") && typeString.includes("intensity")) ||
|
|
79
|
+
(typeString.includes("ambient") && typeString.includes("diffuse") && typeString.includes("displacement"));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isTextureEntityProperty(entityType, dtoType) {
|
|
83
|
+
// Spezielle Behandlung für TextureEntityDto Properties
|
|
84
|
+
const isTextureSettings = (entityType.includes("colorAdjustment") && entityType.includes("intensity")) &&
|
|
85
|
+
(dtoType.includes("TextureSettings") || dtoType.includes("Settings"));
|
|
86
|
+
|
|
87
|
+
const isTextureMaps = (entityType.includes("ambient") && entityType.includes("diffuse") && entityType.includes("displacement")) &&
|
|
88
|
+
(dtoType.includes("TextureMaps") || dtoType.includes("Maps"));
|
|
89
|
+
|
|
90
|
+
return isTextureSettings || isTextureMaps;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function extractConditionalType(typeString) {
|
|
94
|
+
// Extrahiert Conditional Type: "T extends IsItem ? number : null" -> "T extends IsItem ? number : null"
|
|
95
|
+
const conditionalMatch = typeString.match(/(T\s+extends\s+\w+\s+\?\s+[^:]+:\s+[^|]+)/);
|
|
96
|
+
return conditionalMatch ? conditionalMatch[1] : null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function transformConditionalType(conditionalType) {
|
|
100
|
+
if (!conditionalType) return null;
|
|
101
|
+
|
|
102
|
+
// Transformiere Entity Conditional Type zu DTO Conditional Type
|
|
103
|
+
// "T extends IsItem ? Relation<BackpackRefItemEntity<IsBackpack>> : null"
|
|
104
|
+
// -> "T extends IsItem ? BackpackRefItemEntityDto<IsBackpack> : null"
|
|
105
|
+
|
|
106
|
+
let transformed = conditionalType;
|
|
107
|
+
|
|
108
|
+
// Entferne Relation<> Wrapper für DTOs
|
|
109
|
+
transformed = transformed.replace(/Relation<([^>]+)>/g, "$1");
|
|
110
|
+
|
|
111
|
+
// Ersetze Entity-Typen mit DTO-Typen
|
|
112
|
+
transformed = transformed.replace(/BackpackRefItemEntity</g, "BackpackRefItemEntityDto<");
|
|
113
|
+
transformed = transformed.replace(/Entity</g, "EntityDto<");
|
|
114
|
+
transformed = transformed.replace(/Entity\b/g, "EntityDto");
|
|
115
|
+
|
|
116
|
+
return transformed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function typesMatch(entityType, dtoType, propertyName) {
|
|
120
|
+
const AUTO_MANAGED_FIELDS = ["id", "createdAt", "updatedAt"];
|
|
121
|
+
|
|
122
|
+
const entityOptional = isOptional(entityType);
|
|
123
|
+
const dtoOptional = isOptional(dtoType);
|
|
124
|
+
|
|
125
|
+
// Spezielle Behandlung für auto-managed Fields (id, createdAt, updatedAt)
|
|
126
|
+
// Diese Felder MÜSSEN im DTO optional sein (Type | undefined), auch wenn sie in der Entity required sind
|
|
127
|
+
if (AUTO_MANAGED_FIELDS.includes(propertyName)) {
|
|
128
|
+
// Für auto-managed fields: Überspringen der Optionalitäts-Prüfung
|
|
129
|
+
// Die enforce-entity-dto-optional-auto-fields Regel übernimmt diese Prüfung vollständig
|
|
130
|
+
// Prüfe nur den Basis-Typ (ohne Optionalität)
|
|
131
|
+
const normalizedEntityType = normalizeUnionType(entityType);
|
|
132
|
+
const normalizedDtoType = normalizeUnionType(dtoType);
|
|
133
|
+
|
|
134
|
+
if (normalizedEntityType === normalizedDtoType) {
|
|
135
|
+
return { match: true }; // Basis-Typ stimmt überein
|
|
136
|
+
} else {
|
|
137
|
+
// Nur Typ-Fehler melden, keine Optionalitäts-Fehler
|
|
138
|
+
const expectedDtoType = transformEntityToDto(entityType);
|
|
139
|
+
return {
|
|
140
|
+
match: false,
|
|
141
|
+
reason: "type",
|
|
142
|
+
expectedDtoType: expectedDtoType
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
// Für normale Properties: Optionalität muss übereinstimmen
|
|
147
|
+
if (entityOptional !== dtoOptional) {
|
|
148
|
+
return {
|
|
149
|
+
match: false,
|
|
150
|
+
reason: "optionality"
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Handle Conditional Types (T extends IsItem ? number : null)
|
|
156
|
+
if (entityType.includes("extends") && entityType.includes("?")) {
|
|
157
|
+
// Für Conditional Types: Prüfe ob DTO den gleichen Conditional Type hat
|
|
158
|
+
if (dtoType.includes("extends") && dtoType.includes("?")) {
|
|
159
|
+
// Beide sind Conditional Types - prüfe ob sie strukturell übereinstimmen
|
|
160
|
+
const entityConditional = extractConditionalType(entityType);
|
|
161
|
+
const dtoConditional = extractConditionalType(dtoType);
|
|
162
|
+
|
|
163
|
+
if (entityConditional && dtoConditional) {
|
|
164
|
+
// Transformiere Entity Conditional Type zu DTO Conditional Type
|
|
165
|
+
const expectedDtoConditional = transformConditionalType(entityConditional);
|
|
166
|
+
|
|
167
|
+
if (expectedDtoConditional === dtoConditional) {
|
|
168
|
+
return { match: true };
|
|
169
|
+
} else {
|
|
170
|
+
return {
|
|
171
|
+
match: false,
|
|
172
|
+
reason: "type",
|
|
173
|
+
expectedDtoType: expectedDtoConditional
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
// Entity hat Conditional Type, DTO nicht - das ist ein Fehler
|
|
179
|
+
const expectedDtoType = transformConditionalType(extractConditionalType(entityType));
|
|
180
|
+
return {
|
|
181
|
+
match: false,
|
|
182
|
+
reason: "type",
|
|
183
|
+
expectedDtoType: expectedDtoType
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Transformiere Entity-Typ zu erwartetem DTO-Typ
|
|
189
|
+
const expectedDtoType = transformEntityToDto(entityType);
|
|
190
|
+
|
|
191
|
+
// Normalisiere Union-Types für Vergleich (Reihenfolge egal)
|
|
192
|
+
const normalizedExpectedType = normalizeUnionType(expectedDtoType);
|
|
193
|
+
const normalizedDtoType = normalizeUnionType(dtoType);
|
|
194
|
+
|
|
195
|
+
// Prüfe Typ-Übereinstimmung
|
|
196
|
+
if (normalizedExpectedType !== normalizedDtoType) {
|
|
197
|
+
// Spezielle Behandlung für komplexe Objekt-Typen
|
|
198
|
+
if (isComplexObjectType(normalizedExpectedType) && isComplexObjectType(normalizedDtoType)) {
|
|
199
|
+
return { match: true }; // Komplexe Objekt-Typen als kompatibel betrachten
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Spezielle Behandlung für TextureEntityDto
|
|
203
|
+
if (isTextureEntityProperty(normalizedExpectedType, normalizedDtoType)) {
|
|
204
|
+
return { match: true }; // Texture-Properties als kompatibel betrachten
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
match: false,
|
|
209
|
+
reason: "type",
|
|
210
|
+
expectedDtoType: expectedDtoType
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { match: true };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function normalizeUnionType(typeString) {
|
|
218
|
+
// Entferne ? und undefined für Optionalität
|
|
219
|
+
let normalized = typeString.replace(/\?$/, '').replace(/\| undefined/g, '');
|
|
220
|
+
|
|
221
|
+
// Entferne Default-Werte (z.B. "= null", "= undefined")
|
|
222
|
+
normalized = normalized.replace(/\s*=\s*[^|]+/g, '');
|
|
223
|
+
|
|
224
|
+
// Wenn es Union-Types gibt, sortiere sie alphabetisch
|
|
225
|
+
if (normalized.includes('|')) {
|
|
226
|
+
const types = normalized.split('|').map(t => t.trim()).sort();
|
|
227
|
+
normalized = types.join(' | ');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return normalized;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function extractEntityProperties(content) {
|
|
234
|
+
const properties = new Map();
|
|
235
|
+
const lines = content.split('\n');
|
|
236
|
+
|
|
237
|
+
// Finde alle TypeORM-Dekoratoren und ihre zugehörigen Properties
|
|
238
|
+
for (let i = 0; i < lines.length; i++) {
|
|
239
|
+
const line = lines[i].trim();
|
|
240
|
+
|
|
241
|
+
// Suche nach TypeORM-Dekoratoren
|
|
242
|
+
if (line.startsWith('@Column') ||
|
|
243
|
+
line.startsWith('@ManyToOne') ||
|
|
244
|
+
line.startsWith('@OneToMany') ||
|
|
245
|
+
line.startsWith('@CreateDateColumn') ||
|
|
246
|
+
line.startsWith('@UpdateDateColumn') ||
|
|
247
|
+
line.startsWith('@PrimaryGeneratedColumn') ||
|
|
248
|
+
line.startsWith('@PrimaryColumn')) {
|
|
249
|
+
|
|
250
|
+
// Suche nach der zugehörigen Property-Definition
|
|
251
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
252
|
+
const nextLine = lines[j].trim();
|
|
253
|
+
|
|
254
|
+
// Ignoriere leere Zeilen und Kommentare
|
|
255
|
+
if (nextLine === '' || nextLine.startsWith('//') || nextLine.startsWith('*')) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Prüfe, ob es eine Property-Definition ist
|
|
260
|
+
if (nextLine.match(/^\w+\s*:\s*[^;]+;$/)) {
|
|
261
|
+
const match = nextLine.match(/^(\w+)\s*:\s*([^;]+);$/);
|
|
262
|
+
if (match) {
|
|
263
|
+
const [, propName, propType] = match;
|
|
264
|
+
const cleanType = propType.trim();
|
|
265
|
+
properties.set(propName, { type: cleanType });
|
|
266
|
+
}
|
|
267
|
+
break; // Property gefunden, weiter zum nächsten Dekorator
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Stoppe bei anderen Dekoratoren oder Klassen-Ende
|
|
271
|
+
if (nextLine.startsWith('@') ||
|
|
272
|
+
nextLine === '}' ||
|
|
273
|
+
nextLine.startsWith('import ') ||
|
|
274
|
+
nextLine.startsWith('export ') ||
|
|
275
|
+
nextLine.startsWith('class ') ||
|
|
276
|
+
nextLine.startsWith('interface ')) {
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return properties;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const dtoInfoMap = new Map();
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
ClassDeclaration(node) {
|
|
290
|
+
const className = node.id.name;
|
|
291
|
+
|
|
292
|
+
// Nur Entity-DTOs verarbeiten
|
|
293
|
+
if (!className.endsWith("EntityDto")) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const dtoProperties = new Map();
|
|
298
|
+
|
|
299
|
+
// Sammle alle Properties der DTO-Klasse
|
|
300
|
+
for (const member of node.body.body) {
|
|
301
|
+
if (member.type === "PropertyDefinition") {
|
|
302
|
+
const propName = member.key.name;
|
|
303
|
+
let propType = "unknown";
|
|
304
|
+
|
|
305
|
+
if (member.typeAnnotation && member.typeAnnotation.typeAnnotation) {
|
|
306
|
+
propType = extractTypeString(member.typeAnnotation.typeAnnotation);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function extractTypeString(typeAnnotation) {
|
|
310
|
+
if (typeAnnotation.type === "TSUnionType") {
|
|
311
|
+
const types = typeAnnotation.types.map(t => extractTypeString(t));
|
|
312
|
+
return types.join(" | ");
|
|
313
|
+
} else if (typeAnnotation.type === "TSConditionalType") {
|
|
314
|
+
// Handle Conditional Types: T extends IsItem ? number : null
|
|
315
|
+
const checkType = extractTypeString(typeAnnotation.checkType);
|
|
316
|
+
const extendsType = extractTypeString(typeAnnotation.extendsType);
|
|
317
|
+
const trueType = extractTypeString(typeAnnotation.trueType);
|
|
318
|
+
const falseType = extractTypeString(typeAnnotation.falseType);
|
|
319
|
+
return `${checkType} extends ${extendsType} ? ${trueType} : ${falseType}`;
|
|
320
|
+
} else if (typeAnnotation.type === "TSTypeReference") {
|
|
321
|
+
// Handle both typeParameters and typeArguments
|
|
322
|
+
const typeParams = typeAnnotation.typeParameters?.params || typeAnnotation.typeArguments?.params;
|
|
323
|
+
if (typeParams && typeParams.length > 0) {
|
|
324
|
+
const paramStrings = typeParams.map(p => extractTypeString(p));
|
|
325
|
+
return `${typeAnnotation.typeName.name}<${paramStrings.join(", ")}>`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const typeName = typeAnnotation.typeName.name;
|
|
329
|
+
|
|
330
|
+
// Für Interface-Referenzen (wie TextureSettings, TextureMaps)
|
|
331
|
+
// verwende einen vereinfachten Typ-Namen
|
|
332
|
+
if (typeName.includes("Settings") || typeName.includes("Maps") ||
|
|
333
|
+
typeName.includes("Data") || typeName.includes("Config")) {
|
|
334
|
+
return `{complex-object}`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return typeName;
|
|
338
|
+
} else if (typeAnnotation.type === "TSStringKeyword") {
|
|
339
|
+
return "string";
|
|
340
|
+
} else if (typeAnnotation.type === "TSNumberKeyword") {
|
|
341
|
+
return "number";
|
|
342
|
+
} else if (typeAnnotation.type === "TSBooleanKeyword") {
|
|
343
|
+
return "boolean";
|
|
344
|
+
} else if (typeAnnotation.type === "TSNullKeyword") {
|
|
345
|
+
return "null";
|
|
346
|
+
} else if (typeAnnotation.type === "TSUndefinedKeyword") {
|
|
347
|
+
return "undefined";
|
|
348
|
+
} else if (typeAnnotation.type === "TSArrayType") {
|
|
349
|
+
const elementType = extractTypeString(typeAnnotation.elementType);
|
|
350
|
+
return `${elementType}[]`;
|
|
351
|
+
} else if (typeAnnotation.type === "TSTypeLiteral") {
|
|
352
|
+
const members = typeAnnotation.members.map(member => {
|
|
353
|
+
if (member.type === "TSPropertySignature") {
|
|
354
|
+
const key = member.key.name;
|
|
355
|
+
const valueType = member.typeAnnotation ? extractTypeString(member.typeAnnotation.typeAnnotation) : "unknown";
|
|
356
|
+
const optional = member.optional ? "?" : "";
|
|
357
|
+
return `${key}${optional}: ${valueType}`;
|
|
358
|
+
}
|
|
359
|
+
return "unknown";
|
|
360
|
+
});
|
|
361
|
+
return `{ ${members.join(", ")} }`;
|
|
362
|
+
} else if (typeAnnotation.type === "TSLiteralType") {
|
|
363
|
+
if (typeAnnotation.literal.type === "StringLiteral") {
|
|
364
|
+
return `"${typeAnnotation.literal.value}"`;
|
|
365
|
+
} else if (typeAnnotation.literal.type === "NumericLiteral") {
|
|
366
|
+
return typeAnnotation.literal.value.toString();
|
|
367
|
+
} else if (typeAnnotation.literal.type === "BooleanLiteral") {
|
|
368
|
+
return typeAnnotation.literal.value.toString();
|
|
369
|
+
} else if (typeAnnotation.literal.type === "Literal") {
|
|
370
|
+
// Handle ESLint AST Literal node
|
|
371
|
+
if (typeof typeAnnotation.literal.value === "boolean") {
|
|
372
|
+
return typeAnnotation.literal.value.toString();
|
|
373
|
+
} else if (typeof typeAnnotation.literal.value === "string") {
|
|
374
|
+
return `"${typeAnnotation.literal.value}"`;
|
|
375
|
+
} else if (typeof typeAnnotation.literal.value === "number") {
|
|
376
|
+
return typeAnnotation.literal.value.toString();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return "unknown";
|
|
380
|
+
} else if (typeAnnotation.type === "TSIndexedAccessType") {
|
|
381
|
+
// Handle indexed access types like T[K]
|
|
382
|
+
const objectType = extractTypeString(typeAnnotation.objectType);
|
|
383
|
+
const indexType = extractTypeString(typeAnnotation.indexType);
|
|
384
|
+
return `${objectType}[${indexType}]`;
|
|
385
|
+
} else if (typeAnnotation.type === "TSMappedType") {
|
|
386
|
+
// Handle mapped types like { [K in keyof T]: T[K] }
|
|
387
|
+
return "mapped-type";
|
|
388
|
+
} else if (typeAnnotation.type === "TSTemplateLiteralType") {
|
|
389
|
+
// Handle template literal types
|
|
390
|
+
return "template-literal";
|
|
391
|
+
}
|
|
392
|
+
return "unknown";
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Prüfe auf optional (?)
|
|
396
|
+
if (member.optional) {
|
|
397
|
+
propType += "?";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
dtoProperties.set(propName, { type: propType, node: member });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
dtoInfoMap.set(className, { dtoProperties, filename: context.getFilename() });
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
"Program:exit"() {
|
|
408
|
+
// Nach dem Parsen aller Dateien, vergleiche DTOs mit Entities
|
|
409
|
+
for (const [dtoName, dtoInfo] of dtoInfoMap) {
|
|
410
|
+
const entityName = dtoName.replace("EntityDto", "Entity");
|
|
411
|
+
let entityPath;
|
|
412
|
+
|
|
413
|
+
if (dtoInfo.filename.includes("/test-fixtures/")) {
|
|
414
|
+
// Für Test-Fixtures: Suche nach entsprechenden Entity-Dateien
|
|
415
|
+
if (dtoInfo.filename.includes("TempEntityRelationDto.ts")) {
|
|
416
|
+
entityPath = dtoInfo.filename.replace("TempEntityRelationDto.ts", "MockPlayableRaceEntity.ts");
|
|
417
|
+
} else if (dtoInfo.filename.includes("TempCorrectEntityRelationDto.ts")) {
|
|
418
|
+
entityPath = dtoInfo.filename.replace("TempCorrectEntityRelationDto.ts", "MockPlayableRaceEntity.ts");
|
|
419
|
+
} else if (dtoInfo.filename.includes("ConditionalTypeEntityDto.ts")) {
|
|
420
|
+
// Für ConditionalType Test-Fixtures
|
|
421
|
+
entityPath = dtoInfo.filename.replace("/dto/Entity/ConditionalTypeEntityDto.ts", "/entity/ConditionalTypeEntity.ts");
|
|
422
|
+
} else {
|
|
423
|
+
// Fallback für andere Test-Fixtures
|
|
424
|
+
entityPath = dtoInfo.filename.replace("InvalidDtoEntityTypeMatchingEntityDto.ts", "StatTranslationsEntity.ts");
|
|
425
|
+
entityPath = entityPath.replace("ValidDtoEntityTypeMatchingEntityDto.ts", "StatTranslationsEntity.ts");
|
|
426
|
+
entityPath = entityPath.replace("TempTestEntityDto.ts", "StatTranslationsEntity.ts");
|
|
427
|
+
entityPath = entityPath.replace("TempTestEntityDto2.ts", "StatTranslationsEntity.ts");
|
|
428
|
+
entityPath = entityPath.replace("TempComprehensiveEntityDto.ts", "StatTranslationsEntity.ts");
|
|
429
|
+
}
|
|
430
|
+
} else {
|
|
431
|
+
// Für normale DTOs: Standard-Pfad-Transformation
|
|
432
|
+
entityPath = dtoInfo.filename.replace("/dto/Entity/", "/entity/").replace("EntityDto.ts", "Entity.ts");
|
|
433
|
+
|
|
434
|
+
// Spezielle Behandlung für PlayableRaceEntityDto
|
|
435
|
+
if (dtoInfo.filename.includes("PlayableRaceEntityDto.ts")) {
|
|
436
|
+
entityPath = dtoInfo.filename.replace("/dto/Entity/Eoo/Profile/Race/PlayableRaceEntityDto.ts", "/entity/Eoo/Data/PlayableRaceEntity.ts");
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
let entityProperties;
|
|
442
|
+
|
|
443
|
+
// Für Test-Fixtures: Verwende vordefinierte Entity-Properties
|
|
444
|
+
if (dtoInfo.filename.includes("/test-fixtures/")) {
|
|
445
|
+
if (dtoInfo.filename.includes("TempEntityRelationDto.ts") || dtoInfo.filename.includes("TempCorrectEntityRelationDto.ts")) {
|
|
446
|
+
// MockPlayableRaceEntity Properties
|
|
447
|
+
entityProperties = new Map([
|
|
448
|
+
['id', { type: 'string' }],
|
|
449
|
+
['translations', { type: 'RaceTranslationEntity[]' }],
|
|
450
|
+
['raceAbilities', { type: 'RaceAbilityEntity[]' }]
|
|
451
|
+
]);
|
|
452
|
+
} else if (dtoInfo.filename.includes("ConditionalTypeEntityDto.ts")) {
|
|
453
|
+
// ConditionalTypeEntity Properties (für Generic/Conditional Type Tests)
|
|
454
|
+
entityProperties = new Map([
|
|
455
|
+
['id', { type: 'string' }],
|
|
456
|
+
['slotNumber', { type: 'T extends IsBackpack ? null : number' }],
|
|
457
|
+
['backpackSlotNumber', { type: 'T extends IsBackpack ? number : null' }],
|
|
458
|
+
['isEquipped', { type: 'T extends IsEquipped ? true : false' }],
|
|
459
|
+
['backpackRefItem', { type: 'T extends IsItem ? ConditionalTypeEntity<IsBackpack> : null' }]
|
|
460
|
+
]);
|
|
461
|
+
} else {
|
|
462
|
+
// StatTranslationsEntity Properties
|
|
463
|
+
entityProperties = new Map([
|
|
464
|
+
['id', { type: 'string' }],
|
|
465
|
+
['name', { type: 'string' }],
|
|
466
|
+
['description', { type: 'string | null' }]
|
|
467
|
+
]);
|
|
468
|
+
}
|
|
469
|
+
} else {
|
|
470
|
+
// Für echte Backend-Dateien: Parse Entity-Datei
|
|
471
|
+
try {
|
|
472
|
+
// Lese die Entity-Datei synchron
|
|
473
|
+
const entityContent = readFileSync(entityPath, 'utf8');
|
|
474
|
+
entityProperties = extractEntityProperties(entityContent);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
return; // Skip this DTO if entity parsing fails
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Vergleiche DTO-Properties mit Entity-Properties
|
|
481
|
+
for (const [propName, dtoProp] of dtoInfo.dtoProperties) {
|
|
482
|
+
const entityProp = entityProperties.get(propName);
|
|
483
|
+
|
|
484
|
+
if (entityProp) {
|
|
485
|
+
const typeMatch = typesMatch(entityProp.type, dtoProp.type, propName);
|
|
486
|
+
|
|
487
|
+
if (!typeMatch.match) {
|
|
488
|
+
if (typeMatch.reason === "optionality") {
|
|
489
|
+
context.report({
|
|
490
|
+
node: dtoProp.node,
|
|
491
|
+
messageId: "incorrectOptionality",
|
|
492
|
+
data: {
|
|
493
|
+
dtoName,
|
|
494
|
+
prop: propName,
|
|
495
|
+
dtoType: dtoProp.type,
|
|
496
|
+
entityType: entityProp.type,
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
} else if (typeMatch.reason === "type") {
|
|
500
|
+
context.report({
|
|
501
|
+
node: dtoProp.node,
|
|
502
|
+
messageId: "incorrectEntityType",
|
|
503
|
+
data: {
|
|
504
|
+
dtoName,
|
|
505
|
+
prop: propName,
|
|
506
|
+
dtoType: dtoProp.type,
|
|
507
|
+
entityType: entityProp.type,
|
|
508
|
+
expectedDtoType: typeMatch.expectedDtoType,
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
},
|
|
519
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint-Regel: DTO Namenskonvention
|
|
3
|
+
*
|
|
4
|
+
* Diese Regel stellt sicher, dass:
|
|
5
|
+
* 1. DTOs im dto/Entity Ordner mit "EntityDto" enden
|
|
6
|
+
* 2. "EntityEntityDto" ist verboten (doppeltes Entity)
|
|
7
|
+
* 3. Andere DTO-Typen (Request, Response, etc.) haben ihre eigenen Konventionen
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
meta: {
|
|
12
|
+
type: "problem",
|
|
13
|
+
docs: {
|
|
14
|
+
description: "DTOs im dto/Entity Ordner müssen mit 'EntityDto' enden, aber nicht 'EntityEntityDto'",
|
|
15
|
+
category: "Best Practices",
|
|
16
|
+
recommended: true,
|
|
17
|
+
},
|
|
18
|
+
fixable: null,
|
|
19
|
+
schema: [],
|
|
20
|
+
messages: {
|
|
21
|
+
entityDtoMustEndWithEntityDto: "DTO im dto/Entity Ordner muss mit 'EntityDto' enden, nicht '{{currentName}}'. Erwartet: '{{expectedName}}'",
|
|
22
|
+
entityDtoCannotHaveDoubleEntity: "DTO im dto/Entity Ordner darf nicht 'EntityEntityDto' enthalten. Verwende '{{suggestedName}}'",
|
|
23
|
+
nonEntityDtoCannotEndWithEntityDto: "DTO außerhalb des dto/Entity Ordners darf nicht mit 'EntityDto' enden. Verwende '{{suggestedName}}'",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
create(context) {
|
|
28
|
+
const filename = context.getFilename();
|
|
29
|
+
|
|
30
|
+
// Prüfe, ob es sich um einen dto/Entity Ordner handelt
|
|
31
|
+
const isEntityDtoFolder = filename.includes("/dto/Entity/");
|
|
32
|
+
|
|
33
|
+
// Prüfe, ob es sich um andere DTO-Ordner handelt
|
|
34
|
+
const isRequestDtoFolder = filename.includes("/dto/Request/");
|
|
35
|
+
const isResponseDtoFolder = filename.includes("/dto/Response/");
|
|
36
|
+
const isFilterDtoFolder = filename.includes("/dto/Filter/");
|
|
37
|
+
const isCommonDtoFolder = filename.includes("/dto/Common/");
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
ExportDefaultDeclaration(node) {
|
|
41
|
+
// Prüfe nur, wenn es sich um eine Klassendeklaration handelt
|
|
42
|
+
if (node.declaration.type === "ClassDeclaration") {
|
|
43
|
+
const className = node.declaration.id?.name;
|
|
44
|
+
if (!className) {
|
|
45
|
+
return; // Anonyme Klassen ignorieren
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// DTOs im dto/Entity Ordner
|
|
49
|
+
if (isEntityDtoFolder) {
|
|
50
|
+
// Prüfe auf doppeltes "Entity" (EntityEntityDto)
|
|
51
|
+
if (className.includes("EntityEntityDto")) {
|
|
52
|
+
const suggestedName = className.replace("EntityEntityDto", "EntityDto");
|
|
53
|
+
context.report({
|
|
54
|
+
node: node.declaration.id,
|
|
55
|
+
messageId: "entityDtoCannotHaveDoubleEntity",
|
|
56
|
+
data: {
|
|
57
|
+
suggestedName,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Prüfe, ob die Klasse mit "EntityDto" endet
|
|
64
|
+
if (!className.endsWith("EntityDto")) {
|
|
65
|
+
const expectedName = className.endsWith("Dto")
|
|
66
|
+
? className.replace("Dto", "EntityDto")
|
|
67
|
+
: className + "EntityDto";
|
|
68
|
+
|
|
69
|
+
context.report({
|
|
70
|
+
node: node.declaration.id,
|
|
71
|
+
messageId: "entityDtoMustEndWithEntityDto",
|
|
72
|
+
data: {
|
|
73
|
+
currentName: className,
|
|
74
|
+
expectedName,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// DTOs außerhalb des dto/Entity Ordners
|
|
81
|
+
else if (isRequestDtoFolder || isResponseDtoFolder || isFilterDtoFolder || isCommonDtoFolder) {
|
|
82
|
+
// Diese DTOs dürfen NICHT mit "EntityDto" enden
|
|
83
|
+
if (className.endsWith("EntityDto")) {
|
|
84
|
+
const suggestedName = className.replace("EntityDto", "Dto");
|
|
85
|
+
context.report({
|
|
86
|
+
node: node.declaration.id,
|
|
87
|
+
messageId: "nonEntityDtoCannotEndWithEntityDto",
|
|
88
|
+
data: {
|
|
89
|
+
suggestedName,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
};
|