@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,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce that Entity-specific methods (toEntity, fromEntity, fromEntityArray) are only allowed in EntityDto classes
|
|
3
|
+
* @author Echoes of Order Team
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
//------------------------------------------------------------------------------
|
|
7
|
+
// Rule Definition
|
|
8
|
+
//------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
11
|
+
export default {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
docs: {
|
|
15
|
+
description: "Entity-specific methods (toEntity, fromEntity, fromEntityArray) are only allowed in EntityDto classes",
|
|
16
|
+
category: "Architecture",
|
|
17
|
+
recommended: true,
|
|
18
|
+
},
|
|
19
|
+
fixable: null,
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
entityMethodInNonEntityDto: "Method '{{methodName}}' is only allowed in Entity DTOs (classes ending with 'EntityDto' in /dto/Entity/). Found in '{{className}}' which is a {{dtoType}}.",
|
|
23
|
+
entityMethodMustBeStatic: "Method '{{methodName}}' in Entity DTO '{{className}}' must be static.",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
create(context) {
|
|
28
|
+
//--------------------------------------------------------------------------
|
|
29
|
+
// Helpers
|
|
30
|
+
//--------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
const ENTITY_SPECIFIC_METHODS = [
|
|
33
|
+
"toEntity",
|
|
34
|
+
"fromEntity",
|
|
35
|
+
"fromEntityArray",
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if a file is an Entity DTO
|
|
40
|
+
* @param {string} filename - The filename
|
|
41
|
+
* @returns {boolean} True if it's an Entity DTO
|
|
42
|
+
*/
|
|
43
|
+
function isEntityDtoFile(filename) {
|
|
44
|
+
return (
|
|
45
|
+
(filename.includes("/dto/Entity/") || filename.includes("/test-fixtures/")) &&
|
|
46
|
+
(filename.endsWith("EntityDto.ts") || filename.includes("EntityDto"))
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if a class name indicates it's an Entity DTO
|
|
52
|
+
* @param {string} className - The class name
|
|
53
|
+
* @returns {boolean} True if it's an Entity DTO
|
|
54
|
+
*/
|
|
55
|
+
function isEntityDtoClass(className) {
|
|
56
|
+
return className.endsWith("EntityDto");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get the DTO type from class name
|
|
61
|
+
* @param {string} className - The class name
|
|
62
|
+
* @returns {string} The DTO type
|
|
63
|
+
*/
|
|
64
|
+
function getDtoType(className) {
|
|
65
|
+
if (className.endsWith("RequestDto")) {
|
|
66
|
+
return "Request DTO";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (className.endsWith("ResponseDto")) {
|
|
70
|
+
return "Response DTO";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (className.endsWith("FilterDto")) {
|
|
74
|
+
return "Filter DTO";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (className.endsWith("ConfigDto")) {
|
|
78
|
+
return "Config DTO";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (className.endsWith("Dto")) {
|
|
82
|
+
return "DTO";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return "non-Entity DTO";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if a method is an entity-specific method
|
|
90
|
+
* @param {string} methodName - The method name
|
|
91
|
+
* @returns {boolean} True if it's an entity-specific method
|
|
92
|
+
*/
|
|
93
|
+
function isEntitySpecificMethod(methodName) {
|
|
94
|
+
return ENTITY_SPECIFIC_METHODS.includes(methodName);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//--------------------------------------------------------------------------
|
|
98
|
+
// Public
|
|
99
|
+
//--------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
MethodDefinition(node) {
|
|
103
|
+
const filename = context.getFilename();
|
|
104
|
+
const methodName = node.key.name;
|
|
105
|
+
|
|
106
|
+
// Only check entity-specific methods
|
|
107
|
+
if (!isEntitySpecificMethod(methodName)) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Find the ClassDeclaration by walking up the AST
|
|
112
|
+
let parent = node.parent;
|
|
113
|
+
while (parent && parent.type !== "ClassDeclaration") {
|
|
114
|
+
parent = parent.parent;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!parent || !parent.id) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const className = parent.id.name;
|
|
122
|
+
const isEntityDto = isEntityDtoFile(filename) && isEntityDtoClass(className);
|
|
123
|
+
|
|
124
|
+
// If it's NOT an Entity DTO, report error
|
|
125
|
+
if (!isEntityDto) {
|
|
126
|
+
const dtoType = getDtoType(className);
|
|
127
|
+
|
|
128
|
+
context.report({
|
|
129
|
+
node,
|
|
130
|
+
messageId: "entityMethodInNonEntityDto",
|
|
131
|
+
data: {
|
|
132
|
+
methodName,
|
|
133
|
+
className,
|
|
134
|
+
dtoType,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If it IS an Entity DTO, ensure the method is static
|
|
142
|
+
if (isEntityDto && !node.static) {
|
|
143
|
+
context.report({
|
|
144
|
+
node,
|
|
145
|
+
messageId: "entityMethodMustBeStatic",
|
|
146
|
+
data: {
|
|
147
|
+
methodName,
|
|
148
|
+
className,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "problem",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Entity DTOs dürfen keine Request DTOs als Properties haben",
|
|
6
|
+
category: "Best Practices",
|
|
7
|
+
recommended: true,
|
|
8
|
+
},
|
|
9
|
+
fixable: null,
|
|
10
|
+
schema: [],
|
|
11
|
+
messages: {
|
|
12
|
+
requestDtoInEntityDto: "Entity-DTO '{{dtoName}}' darf keine Request-DTO '{{requestDtoName}}' als Property haben. Verwende stattdessen normale DTOs oder Entity-DTOs.",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
create(context) {
|
|
16
|
+
const filename = context.getFilename();
|
|
17
|
+
|
|
18
|
+
// Nur in Entity-DTO-Dateien anwenden
|
|
19
|
+
if (!filename.includes("/dto/Entity/") && !filename.includes("test-fixtures")) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Prüfe ob es eine Entity-DTO-Datei ist
|
|
24
|
+
if (!filename.endsWith("EntityDto.ts") && !filename.includes("EntityDto")) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
ClassDeclaration(node) {
|
|
30
|
+
const className = node.id.name;
|
|
31
|
+
|
|
32
|
+
// Prüfe alle Properties der Klasse
|
|
33
|
+
node.body.body.forEach(member => {
|
|
34
|
+
if (member.type === "PropertyDefinition" ||
|
|
35
|
+
(member.type === "TSParameterProperty" && member.parameter.type === "TSTypeAnnotation")) {
|
|
36
|
+
|
|
37
|
+
let typeAnnotation = null;
|
|
38
|
+
if (member.type === "PropertyDefinition" && member.typeAnnotation) {
|
|
39
|
+
typeAnnotation = member.typeAnnotation.typeAnnotation;
|
|
40
|
+
} else if (member.type === "TSParameterProperty" && member.parameter.typeAnnotation) {
|
|
41
|
+
typeAnnotation = member.parameter.typeAnnotation.typeAnnotation;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeAnnotation) {
|
|
45
|
+
// Prüfe auf Request-DTO-Typen
|
|
46
|
+
if (typeAnnotation.type === "TSTypeReference") {
|
|
47
|
+
const typeName = typeAnnotation.typeName.name;
|
|
48
|
+
if (typeName && typeName.endsWith("RequestDto")) {
|
|
49
|
+
context.report({
|
|
50
|
+
node: member,
|
|
51
|
+
messageId: "requestDtoInEntityDto",
|
|
52
|
+
data: {
|
|
53
|
+
dtoName: className,
|
|
54
|
+
requestDtoName: typeName,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Prüfe auf Array-Typen mit Request-DTOs
|
|
61
|
+
if (typeAnnotation.type === "TSArrayType") {
|
|
62
|
+
const elementType = typeAnnotation.elementType;
|
|
63
|
+
if (elementType.type === "TSTypeReference") {
|
|
64
|
+
const typeName = elementType.typeName.name;
|
|
65
|
+
if (typeName && typeName.endsWith("RequestDto")) {
|
|
66
|
+
context.report({
|
|
67
|
+
node: member,
|
|
68
|
+
messageId: "requestDtoInEntityDto",
|
|
69
|
+
data: {
|
|
70
|
+
dtoName: className,
|
|
71
|
+
requestDtoName: typeName,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Prüfe auf Union-Typen
|
|
79
|
+
if (typeAnnotation.type === "TSUnionType") {
|
|
80
|
+
typeAnnotation.types.forEach(unionType => {
|
|
81
|
+
if (unionType.type === "TSTypeReference") {
|
|
82
|
+
const typeName = unionType.typeName.name;
|
|
83
|
+
if (typeName && typeName.endsWith("RequestDto")) {
|
|
84
|
+
context.report({
|
|
85
|
+
node: member,
|
|
86
|
+
messageId: "requestDtoInEntityDto",
|
|
87
|
+
data: {
|
|
88
|
+
dtoName: className,
|
|
89
|
+
requestDtoName: typeName,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce that id, createdAt, and updatedAt are optional in Entity DTOs
|
|
3
|
+
* @author Echoes of Order Team
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
//------------------------------------------------------------------------------
|
|
7
|
+
// Rule Definition
|
|
8
|
+
//------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
11
|
+
const enforceEntityDtoOptionalAutoFieldsRule = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
docs: {
|
|
15
|
+
description: "id, createdAt, and updatedAt must be optional in Entity DTOs (generated by database/TypeORM)",
|
|
16
|
+
category: "Architecture",
|
|
17
|
+
recommended: true,
|
|
18
|
+
},
|
|
19
|
+
fixable: "code",
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
idMustBeOptional: "Property 'id' in Entity DTO '{{className}}' must be optional (id?: string). The ID is generated by the database.",
|
|
23
|
+
createdAtMustBeOptional: "Property 'createdAt' in Entity DTO '{{className}}' must be optional (createdAt?: Date). The field is set by TypeORM automatically.",
|
|
24
|
+
updatedAtMustBeOptional: "Property 'updatedAt' in Entity DTO '{{className}}' must be optional (updatedAt?: Date). The field is set by TypeORM automatically.",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
create(context) {
|
|
29
|
+
const filename = context.getFilename();
|
|
30
|
+
|
|
31
|
+
// Only check Entity DTO files
|
|
32
|
+
const isEntityDtoFile = filename.includes("/dto/Entity/") && filename.endsWith("EntityDto.ts");
|
|
33
|
+
|
|
34
|
+
if (!isEntityDtoFile) {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Skip BaseEntityDto
|
|
39
|
+
if (filename.endsWith("/dto/BaseEntityDto.ts")) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const AUTO_GENERATED_FIELDS = {
|
|
44
|
+
"id": "idMustBeOptional",
|
|
45
|
+
"createdAt": "createdAtMustBeOptional",
|
|
46
|
+
"updatedAt": "updatedAtMustBeOptional",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
PropertyDefinition(node) {
|
|
51
|
+
// Find the containing class
|
|
52
|
+
let parent = node.parent;
|
|
53
|
+
while (parent && parent.type !== "ClassDeclaration") {
|
|
54
|
+
parent = parent.parent;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!parent || !parent.id) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const className = parent.id.name;
|
|
62
|
+
|
|
63
|
+
// Only check Entity DTOs
|
|
64
|
+
if (!className.endsWith("EntityDto")) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const propertyName = node.key?.name;
|
|
69
|
+
|
|
70
|
+
// Check if this is one of the auto-generated fields
|
|
71
|
+
if (propertyName && AUTO_GENERATED_FIELDS[propertyName]) {
|
|
72
|
+
// Check if property is optional
|
|
73
|
+
const isOptional = node.optional === true;
|
|
74
|
+
|
|
75
|
+
if (!isOptional) {
|
|
76
|
+
context.report({
|
|
77
|
+
node,
|
|
78
|
+
messageId: AUTO_GENERATED_FIELDS[propertyName],
|
|
79
|
+
data: { className },
|
|
80
|
+
fix(fixer) {
|
|
81
|
+
// Find the property name in the source
|
|
82
|
+
const sourceCode = context.getSourceCode();
|
|
83
|
+
const propertyText = sourceCode.getText(node.key);
|
|
84
|
+
|
|
85
|
+
// Add ? after property name
|
|
86
|
+
return fixer.replaceText(node.key, `${propertyText}?`);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default {
|
|
97
|
+
rules: {
|
|
98
|
+
"enforce-entity-dto-optional-auto-fields": enforceEntityDtoOptionalAutoFieldsRule,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce required methods in Entity DTOs and validate fromRequestDto parameter types
|
|
3
|
+
* @author Echoes of Order Team
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
//------------------------------------------------------------------------------
|
|
7
|
+
// Rule Definition
|
|
8
|
+
//------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
11
|
+
export default {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
docs: {
|
|
15
|
+
description: "Entity DTOs must have fromEntity and toEntity methods. fromEntityArray and fromRequestDto are optional but must use correct types.",
|
|
16
|
+
category: "Architecture",
|
|
17
|
+
recommended: true,
|
|
18
|
+
},
|
|
19
|
+
fixable: null,
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
missingFromEntity: "Entity DTO '{{className}}' must have a static 'fromEntity' method.",
|
|
23
|
+
missingToEntity: "Entity DTO '{{className}}' must have a static 'toEntity' method.",
|
|
24
|
+
invalidFromRequestDtoParameter: "Entity DTO '{{className}}' has 'fromRequestDto' method but parameter type '{{paramType}}' does not match expected '{{expectedType}}'.",
|
|
25
|
+
fromRequestDtoMustBeStatic: "Method 'fromRequestDto' in Entity DTO '{{className}}' must be static.",
|
|
26
|
+
optionalMethodMustBeStatic: "Method '{{methodName}}' in Entity DTO '{{className}}' must be static.",
|
|
27
|
+
requiredMethodMustBeStatic: "Method '{{methodName}}' in Entity DTO '{{className}}' must be static.",
|
|
28
|
+
forbiddenMethod: "Entity DTO '{{className}}' must not have method '{{methodName}}'. Only 'fromEntity', 'toEntity', and optionally 'fromEntityArray', 'fromRequestDto' are allowed.",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
create(context) {
|
|
33
|
+
//--------------------------------------------------------------------------
|
|
34
|
+
// Helpers
|
|
35
|
+
//--------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
const REQUIRED_METHODS = ["fromEntity", "toEntity"];
|
|
38
|
+
const ALLOWED_OPTIONAL_METHODS = ["fromEntityArray", "fromRequestDto"];
|
|
39
|
+
const ALL_ALLOWED_METHODS = [...REQUIRED_METHODS, ...ALLOWED_OPTIONAL_METHODS];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if a file is an Entity DTO
|
|
43
|
+
* @param {string} filename - The filename
|
|
44
|
+
* @returns {boolean} True if it's an Entity DTO
|
|
45
|
+
*/
|
|
46
|
+
function isEntityDtoFile(filename) {
|
|
47
|
+
return (
|
|
48
|
+
filename.includes("/dto/Entity/") &&
|
|
49
|
+
filename.endsWith("EntityDto.ts")
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if a class name indicates it's an Entity DTO
|
|
55
|
+
* @param {string} className - The class name
|
|
56
|
+
* @returns {boolean} True if it's an Entity DTO
|
|
57
|
+
*/
|
|
58
|
+
function isEntityDtoClass(className) {
|
|
59
|
+
return className && className.endsWith("EntityDto");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get expected RequestDto name from EntityDto name
|
|
64
|
+
* @param {string} entityDtoName - The Entity DTO name (e.g., "ItemEntityDto")
|
|
65
|
+
* @returns {string} The expected RequestDto name (e.g., "ItemRequestDto")
|
|
66
|
+
*/
|
|
67
|
+
function getExpectedRequestDtoName(entityDtoName) {
|
|
68
|
+
// Remove "EntityDto" suffix and add "RequestDto"
|
|
69
|
+
return entityDtoName.replace(/EntityDto$/, "RequestDto");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get parameter type name from TypeAnnotation
|
|
74
|
+
* @param {object} param - The parameter node
|
|
75
|
+
* @returns {string|null} The type name or null
|
|
76
|
+
*/
|
|
77
|
+
function getParameterTypeName(param) {
|
|
78
|
+
if (!param || !param.typeAnnotation) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const typeAnnotation = param.typeAnnotation.typeAnnotation;
|
|
83
|
+
|
|
84
|
+
if (typeAnnotation.type === "TSTypeReference") {
|
|
85
|
+
if (typeAnnotation.typeName.type === "Identifier") {
|
|
86
|
+
return typeAnnotation.typeName.name;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if a method name is allowed in Entity DTOs
|
|
95
|
+
* @param {string} methodName - The method name
|
|
96
|
+
* @returns {boolean} True if allowed
|
|
97
|
+
*/
|
|
98
|
+
function isAllowedMethod(methodName) {
|
|
99
|
+
return ALL_ALLOWED_METHODS.includes(methodName);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//--------------------------------------------------------------------------
|
|
103
|
+
// Public
|
|
104
|
+
//--------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
let classNode = null;
|
|
107
|
+
let className = null;
|
|
108
|
+
let foundMethods = {};
|
|
109
|
+
let methodNodes = {};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
ClassDeclaration(node) {
|
|
113
|
+
const filename = context.getFilename();
|
|
114
|
+
|
|
115
|
+
// Only check Entity DTOs
|
|
116
|
+
if (!isEntityDtoFile(filename)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
classNode = node;
|
|
121
|
+
className = node.id?.name;
|
|
122
|
+
|
|
123
|
+
// Only check classes that are Entity DTOs
|
|
124
|
+
if (!isEntityDtoClass(className)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Reset tracking
|
|
129
|
+
foundMethods = {};
|
|
130
|
+
methodNodes = {};
|
|
131
|
+
|
|
132
|
+
// Special case: Skip BaseEntityDto
|
|
133
|
+
if (className === "BaseEntityDto" && filename.endsWith("/dto/BaseEntityDto.ts")) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Collect all static methods
|
|
138
|
+
node.body.body.forEach((member) => {
|
|
139
|
+
if (member.type === "MethodDefinition" && member.key && member.key.name) {
|
|
140
|
+
const methodName = member.key.name;
|
|
141
|
+
|
|
142
|
+
// Track found methods
|
|
143
|
+
foundMethods[methodName] = {
|
|
144
|
+
isStatic: member.static === true,
|
|
145
|
+
node: member,
|
|
146
|
+
};
|
|
147
|
+
methodNodes[methodName] = member;
|
|
148
|
+
|
|
149
|
+
// Check if method is allowed
|
|
150
|
+
if (!isAllowedMethod(methodName) && member.kind !== "constructor") {
|
|
151
|
+
context.report({
|
|
152
|
+
node: member,
|
|
153
|
+
messageId: "forbiddenMethod",
|
|
154
|
+
data: {
|
|
155
|
+
className,
|
|
156
|
+
methodName,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check if required methods are static
|
|
162
|
+
if (REQUIRED_METHODS.includes(methodName) && !member.static) {
|
|
163
|
+
context.report({
|
|
164
|
+
node: member,
|
|
165
|
+
messageId: "requiredMethodMustBeStatic",
|
|
166
|
+
data: {
|
|
167
|
+
className,
|
|
168
|
+
methodName,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check if optional methods are static (fromEntityArray, fromRequestDto)
|
|
174
|
+
if (ALLOWED_OPTIONAL_METHODS.includes(methodName) && !member.static) {
|
|
175
|
+
context.report({
|
|
176
|
+
node: member,
|
|
177
|
+
messageId: "optionalMethodMustBeStatic",
|
|
178
|
+
data: {
|
|
179
|
+
className,
|
|
180
|
+
methodName,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check fromRequestDto parameter type
|
|
186
|
+
if (methodName === "fromRequestDto") {
|
|
187
|
+
|
|
188
|
+
// Check parameter type
|
|
189
|
+
if (member.value && member.value.params && member.value.params.length > 0) {
|
|
190
|
+
const firstParam = member.value.params[0];
|
|
191
|
+
const paramTypeName = getParameterTypeName(firstParam);
|
|
192
|
+
const expectedTypeName = getExpectedRequestDtoName(className);
|
|
193
|
+
|
|
194
|
+
if (paramTypeName && paramTypeName !== expectedTypeName) {
|
|
195
|
+
context.report({
|
|
196
|
+
node: firstParam,
|
|
197
|
+
messageId: "invalidFromRequestDtoParameter",
|
|
198
|
+
data: {
|
|
199
|
+
className,
|
|
200
|
+
paramType: paramTypeName,
|
|
201
|
+
expectedType: expectedTypeName,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
"Program:exit"(node) {
|
|
212
|
+
if (!classNode || !className) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const filename = context.getFilename();
|
|
217
|
+
|
|
218
|
+
// Only check Entity DTOs
|
|
219
|
+
if (!isEntityDtoFile(filename) || !isEntityDtoClass(className)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Special case: Skip BaseEntityDto
|
|
224
|
+
if (className === "BaseEntityDto" && filename.endsWith("/dto/BaseEntityDto.ts")) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check for missing required methods
|
|
229
|
+
REQUIRED_METHODS.forEach((methodName) => {
|
|
230
|
+
if (!foundMethods[methodName]) {
|
|
231
|
+
context.report({
|
|
232
|
+
node: classNode,
|
|
233
|
+
messageId: `missing${methodName.charAt(0).toUpperCase()}${methodName.slice(1)}`,
|
|
234
|
+
data: { className },
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Reset for next file
|
|
240
|
+
classNode = null;
|
|
241
|
+
className = null;
|
|
242
|
+
foundMethods = {};
|
|
243
|
+
methodNodes = {};
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|