@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,178 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "problem",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Enforce eager loading for translation relations and prevent manual translation loading",
|
|
6
|
+
category: "Best Practices",
|
|
7
|
+
recommended: true,
|
|
8
|
+
},
|
|
9
|
+
messages: {
|
|
10
|
+
missingEagerTrue: "Translation relation 'translations' must have 'eager: true' option. This ensures translations are always loaded automatically.",
|
|
11
|
+
manualTranslationLoading: "Do not manually load 'translations' in relations array. Translation relations use 'eager: true' and are loaded automatically.",
|
|
12
|
+
addEagerTrue: "Add 'eager: true' to the @OneToMany decorator options for 'translations' relation.",
|
|
13
|
+
removeFromRelations: "Remove 'translations' from relations array - it's loaded automatically via eager loading.",
|
|
14
|
+
},
|
|
15
|
+
fixable: "code",
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
create (context) {
|
|
20
|
+
return {
|
|
21
|
+
"PropertyDefinition[key.name='translations']" (node) {
|
|
22
|
+
const decorators = node.decorators;
|
|
23
|
+
if (!decorators || decorators.length === 0) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const oneToManyDecorator = decorators.find((decorator) => {
|
|
28
|
+
if (decorator.expression.type !== "CallExpression") {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const callee = decorator.expression.callee;
|
|
33
|
+
|
|
34
|
+
return callee.type === "Identifier" && callee.name === "OneToMany";
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!oneToManyDecorator) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const args = oneToManyDecorator.expression.arguments;
|
|
42
|
+
const optionsArg = args[2];
|
|
43
|
+
|
|
44
|
+
if (!optionsArg || optionsArg.type !== "ObjectExpression") {
|
|
45
|
+
context.report({
|
|
46
|
+
messageId: "missingEagerTrue",
|
|
47
|
+
node: oneToManyDecorator,
|
|
48
|
+
fix (fixer) {
|
|
49
|
+
const lastArg = args[args.length - 1];
|
|
50
|
+
|
|
51
|
+
return fixer.insertTextAfter(
|
|
52
|
+
lastArg,
|
|
53
|
+
', {\n cascade: true,\n eager: true,\n }',
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const hasEager = optionsArg.properties.some((prop) => {
|
|
62
|
+
if (prop.type !== "Property") {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (prop.key.type !== "Identifier") {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return prop.key.name === "eager";
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const eagerProp = optionsArg.properties.find((prop) => {
|
|
73
|
+
if (prop.type !== "Property") {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (prop.key.type !== "Identifier") {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return prop.key.name === "eager";
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!hasEager) {
|
|
84
|
+
context.report({
|
|
85
|
+
messageId: "missingEagerTrue",
|
|
86
|
+
node: oneToManyDecorator,
|
|
87
|
+
fix (fixer) {
|
|
88
|
+
const lastProperty = optionsArg.properties[optionsArg.properties.length - 1];
|
|
89
|
+
|
|
90
|
+
return fixer.insertTextAfter(
|
|
91
|
+
lastProperty,
|
|
92
|
+
',\n eager: true',
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (eagerProp && eagerProp.type === "Property" && eagerProp.value.type === "Literal" && eagerProp.value.value !== true) {
|
|
101
|
+
context.report({
|
|
102
|
+
messageId: "missingEagerTrue",
|
|
103
|
+
node: eagerProp,
|
|
104
|
+
fix (fixer) {
|
|
105
|
+
return fixer.replaceText(eagerProp.value, "true");
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
ArrayExpression (node) {
|
|
112
|
+
const parent = node.parent;
|
|
113
|
+
if (!parent || parent.type !== "Property") {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (parent.key.type !== "Identifier" || parent.key.name !== "relations") {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
node.elements.forEach((element) => {
|
|
122
|
+
if (!element || element.type !== "Literal") {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const value = element.value;
|
|
127
|
+
if (typeof value !== "string") {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (value === "translations" || value.endsWith(".translations")) {
|
|
132
|
+
context.report({
|
|
133
|
+
messageId: "manualTranslationLoading",
|
|
134
|
+
node: element,
|
|
135
|
+
fix (fixer) {
|
|
136
|
+
const sourceCode = context.getSourceCode();
|
|
137
|
+
const elementIndex = node.elements.indexOf(element);
|
|
138
|
+
const tokenBefore = sourceCode.getTokenBefore(element);
|
|
139
|
+
const tokenAfter = sourceCode.getTokenAfter(element);
|
|
140
|
+
|
|
141
|
+
if (node.elements.length === 1) {
|
|
142
|
+
return fixer.remove(element);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (elementIndex === 0) {
|
|
146
|
+
if (tokenAfter && tokenAfter.value === ",") {
|
|
147
|
+
return fixer.removeRange([element.range[0], tokenAfter.range[1]]);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return fixer.remove(element);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (elementIndex === node.elements.length - 1) {
|
|
154
|
+
if (tokenBefore && tokenBefore.value === ",") {
|
|
155
|
+
return fixer.removeRange([tokenBefore.range[0], element.range[1]]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return fixer.remove(element);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (tokenBefore && tokenBefore.value === ",") {
|
|
162
|
+
return fixer.removeRange([tokenBefore.range[0], element.range[1]]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (tokenAfter && tokenAfter.value === ",") {
|
|
166
|
+
return fixer.removeRange([element.range[0], tokenAfter.range[1]]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return fixer.remove(element);
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
rules: {
|
|
3
|
+
"enforce-entity-creation-pattern": {
|
|
4
|
+
meta: {
|
|
5
|
+
type: "problem",
|
|
6
|
+
docs: {
|
|
7
|
+
description: "Enforce entity creation pattern: use DTOs and convert with .toEntity() before saving",
|
|
8
|
+
category: "Best Practices",
|
|
9
|
+
recommended: true,
|
|
10
|
+
},
|
|
11
|
+
fixable: null,
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
directEntityCreation: "Direct entity creation with 'new {{entityName}}()' is not allowed. Use DTOs and convert with .toEntity() instead.",
|
|
15
|
+
missingToEntityConversion: "Entity must be converted with .toEntity() before saving. Use '{{dtoName}}.toEntity({{dtoVariable}})' instead of direct entity creation.",
|
|
16
|
+
invalidEntityManagerUsage: "entityManager.create() should only be used with DTOs that have been converted with .toEntity().",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
create(context) {
|
|
21
|
+
const entityDtos = new Map();
|
|
22
|
+
const entityManagerCalls = new Map();
|
|
23
|
+
|
|
24
|
+
function isEntityDtoFile(filename) {
|
|
25
|
+
return (filename.includes("/dto/Entity/") || filename.includes("test-fixtures")) &&
|
|
26
|
+
(filename.endsWith("EntityDto.ts") || filename.includes("EntityDto"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getEntityNameFromDto(dtoName) {
|
|
30
|
+
if (dtoName.endsWith("EntityDto")) {
|
|
31
|
+
return dtoName.replace("EntityDto", "Entity");
|
|
32
|
+
}
|
|
33
|
+
return dtoName;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isInsideToEntityMethod(node) {
|
|
37
|
+
let parent = node.parent;
|
|
38
|
+
while (parent) {
|
|
39
|
+
if (parent.type === "MethodDefinition" || parent.type === "Property") {
|
|
40
|
+
if (parent.key && (parent.key.name === "toEntity" || parent.key.name === "fromRequestDto")) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
parent = parent.parent;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isEntityManagerCall(node) {
|
|
50
|
+
if (node.type === "CallExpression" && node.callee) {
|
|
51
|
+
if (node.callee.type === "MemberExpression") {
|
|
52
|
+
const objectName = node.callee.object.name;
|
|
53
|
+
const methodName = node.callee.property.name;
|
|
54
|
+
|
|
55
|
+
// Check for entityManager.create, transactionalEntityManager.create, etc.
|
|
56
|
+
if ((objectName === "entityManager" ||
|
|
57
|
+
objectName === "transactionalEntityManager" ||
|
|
58
|
+
objectName === "manager") &&
|
|
59
|
+
methodName === "create") {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
ClassDeclaration(node) {
|
|
69
|
+
const filename = context.getFilename();
|
|
70
|
+
const className = node.id.name;
|
|
71
|
+
|
|
72
|
+
if (isEntityDtoFile(filename) && className.endsWith("EntityDto")) {
|
|
73
|
+
entityDtos.set(className, {
|
|
74
|
+
node,
|
|
75
|
+
hasToEntityMethod: node.body.body.some(member =>
|
|
76
|
+
member.type === "MethodDefinition" &&
|
|
77
|
+
member.key.name === "toEntity" &&
|
|
78
|
+
member.static
|
|
79
|
+
),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
NewExpression(node) {
|
|
85
|
+
const filename = context.getFilename();
|
|
86
|
+
const entityName = node.callee.name;
|
|
87
|
+
|
|
88
|
+
if (entityName && entityName.endsWith("Entity")) {
|
|
89
|
+
// Allow entity creation inside toEntity methods
|
|
90
|
+
if (isInsideToEntityMethod(node)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check if this is a direct entity creation outside of toEntity
|
|
95
|
+
const dtoName = entityName.replace("Entity", "EntityDto");
|
|
96
|
+
const dtoInfo = entityDtos.get(dtoName);
|
|
97
|
+
|
|
98
|
+
if (dtoInfo) {
|
|
99
|
+
context.report({
|
|
100
|
+
node,
|
|
101
|
+
messageId: "directEntityCreation",
|
|
102
|
+
data: { entityName },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
CallExpression(node) {
|
|
109
|
+
if (isEntityManagerCall(node)) {
|
|
110
|
+
// Check if the second argument is a direct entity creation
|
|
111
|
+
if (node.arguments && node.arguments.length >= 2) {
|
|
112
|
+
const secondArg = node.arguments[1];
|
|
113
|
+
|
|
114
|
+
if (secondArg.type === "NewExpression" &&
|
|
115
|
+
secondArg.callee.name &&
|
|
116
|
+
secondArg.callee.name.endsWith("Entity")) {
|
|
117
|
+
|
|
118
|
+
const entityName = secondArg.callee.name;
|
|
119
|
+
const dtoName = entityName.replace("Entity", "EntityDto");
|
|
120
|
+
|
|
121
|
+
context.report({
|
|
122
|
+
node: secondArg,
|
|
123
|
+
messageId: "missingToEntityConversion",
|
|
124
|
+
data: {
|
|
125
|
+
dtoName,
|
|
126
|
+
dtoVariable: "dtoData"
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "problem",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Enforce that Entity DTOs use convertEntityToDto in fromEntity method",
|
|
6
|
+
category: "Best Practices",
|
|
7
|
+
recommended: true,
|
|
8
|
+
},
|
|
9
|
+
messages: {
|
|
10
|
+
missingConvertImport: "Entity-DTO '{{className}}' muss convertEntityToDto importieren: import { convertEntityToDto } from '@/dto/BaseEntityDto'",
|
|
11
|
+
fromEntityMustUseConvert: "fromEntity-Methode in '{{className}}' muss 'return convertEntityToDto(...)' verwenden, um zirkuläre Abhängigkeiten zu vermeiden",
|
|
12
|
+
invalidConvertStructure: "convertEntityToDto in '{{className}}' muss 3 Argumente haben: (entity, factory, populate)",
|
|
13
|
+
factoryMustBeArrowFunction: "Zweites Argument von convertEntityToDto muss eine Arrow-Function sein: () => new {{className}}()",
|
|
14
|
+
populateMustBeArrowFunction: "Drittes Argument von convertEntityToDto muss eine Arrow-Function sein: (dto, entity) => {{ ... }}",
|
|
15
|
+
},
|
|
16
|
+
fixable: null,
|
|
17
|
+
schema: [],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
create (context) {
|
|
21
|
+
const filename = context.getFilename();
|
|
22
|
+
|
|
23
|
+
// Skip non-DTO files
|
|
24
|
+
if (!filename.includes("/dto/Entity/")) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Skip BaseEntityDto itself
|
|
29
|
+
if (filename.endsWith("/BaseEntityDto.ts")) {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Skip test files
|
|
34
|
+
if (filename.includes(".test.") || filename.includes(".spec.") || filename.includes("__tests__")) {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let hasConvertImport = false;
|
|
39
|
+
let className = null;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
ImportDeclaration (node) {
|
|
43
|
+
// Check for convertEntityToDto import (absolute or relative)
|
|
44
|
+
const isBaseImport = node.source.value === "@/dto/BaseEntityDto" ||
|
|
45
|
+
node.source.value.endsWith("/BaseEntityDto") ||
|
|
46
|
+
node.source.value === "./BaseEntityDto";
|
|
47
|
+
|
|
48
|
+
if (isBaseImport) {
|
|
49
|
+
const hasConvert = node.specifiers.some(
|
|
50
|
+
(spec) => spec.type === "ImportSpecifier" && spec.imported.name === "convertEntityToDto",
|
|
51
|
+
);
|
|
52
|
+
if (hasConvert) {
|
|
53
|
+
hasConvertImport = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
ClassDeclaration (node) {
|
|
59
|
+
// Only check Entity DTOs
|
|
60
|
+
if (!node.id || !node.id.name.endsWith("EntityDto")) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
className = node.id.name;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
MethodDefinition (node) {
|
|
68
|
+
// Only check static fromEntity methods
|
|
69
|
+
if (!node.static || !node.key || node.key.name !== "fromEntity") {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check if import exists
|
|
74
|
+
if (!hasConvertImport) {
|
|
75
|
+
context.report({
|
|
76
|
+
node,
|
|
77
|
+
messageId: "missingConvertImport",
|
|
78
|
+
data: { className },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check if method body uses convertEntityToDto
|
|
85
|
+
const methodBody = node.value.body;
|
|
86
|
+
if (!methodBody || methodBody.type !== "BlockStatement") {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Find return statement
|
|
91
|
+
const returnStatement = methodBody.body.find((stmt) => stmt.type === "ReturnStatement");
|
|
92
|
+
if (!returnStatement || !returnStatement.argument) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check if return statement calls convertEntityToDto
|
|
97
|
+
const returnArg = returnStatement.argument;
|
|
98
|
+
if (
|
|
99
|
+
returnArg.type !== "CallExpression"
|
|
100
|
+
|| !returnArg.callee
|
|
101
|
+
|| returnArg.callee.name !== "convertEntityToDto"
|
|
102
|
+
) {
|
|
103
|
+
context.report({
|
|
104
|
+
node: returnStatement,
|
|
105
|
+
messageId: "fromEntityMustUseConvert",
|
|
106
|
+
data: { className },
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Validate convertEntityToDto arguments
|
|
113
|
+
const args = returnArg.arguments;
|
|
114
|
+
if (args.length !== 3) {
|
|
115
|
+
context.report({
|
|
116
|
+
node: returnArg,
|
|
117
|
+
messageId: "invalidConvertStructure",
|
|
118
|
+
data: { className },
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Validate factory function (second argument)
|
|
125
|
+
const factoryArg = args[1];
|
|
126
|
+
if (factoryArg.type !== "ArrowFunctionExpression") {
|
|
127
|
+
context.report({
|
|
128
|
+
node: factoryArg,
|
|
129
|
+
messageId: "factoryMustBeArrowFunction",
|
|
130
|
+
data: { className },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Validate populate function (third argument)
|
|
135
|
+
const populateArg = args[2];
|
|
136
|
+
if (populateArg.type !== "ArrowFunctionExpression") {
|
|
137
|
+
context.report({
|
|
138
|
+
node: populateArg,
|
|
139
|
+
messageId: "populateMustBeArrowFunction",
|
|
140
|
+
data: { className },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Validate populate function has 2 parameters (dto, entity)
|
|
145
|
+
if (populateArg.type === "ArrowFunctionExpression" && populateArg.params.length !== 2) {
|
|
146
|
+
context.report({
|
|
147
|
+
node: populateArg,
|
|
148
|
+
messageId: "populateMustBeArrowFunction",
|
|
149
|
+
data: { className },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Enforce that EntityDto.create() methods never have id parameter or set id property
|
|
3
|
+
* @author Echoes of Order Team
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "EntityDto.create() methods dürfen kein id-Feld haben (weder als Parameter noch in der Zuweisung)",
|
|
11
|
+
category: "Architecture",
|
|
12
|
+
recommended: true,
|
|
13
|
+
},
|
|
14
|
+
messages: {
|
|
15
|
+
noIdParameter: "EntityDto.create() Methode darf keinen 'id' Parameter haben. Die ID wird von der Datenbank generiert.",
|
|
16
|
+
noIdAssignment: "EntityDto.create() Methode darf kein 'id' Feld setzen. Die ID wird von der Datenbank generiert.",
|
|
17
|
+
noCreatedAtParameter: "EntityDto.create() Methode darf keinen 'createdAt' Parameter haben. Das Feld wird von TypeORM automatisch gesetzt.",
|
|
18
|
+
noCreatedAtAssignment: "EntityDto.create() Methode darf kein 'createdAt' Feld setzen. Das Feld wird von TypeORM automatisch gesetzt.",
|
|
19
|
+
noUpdatedAtParameter: "EntityDto.create() Methode darf keinen 'updatedAt' Parameter haben. Das Feld wird von TypeORM automatisch gesetzt.",
|
|
20
|
+
noUpdatedAtAssignment: "EntityDto.create() Methode darf kein 'updatedAt' Feld setzen. Das Feld wird von TypeORM automatisch gesetzt.",
|
|
21
|
+
},
|
|
22
|
+
schema: [],
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
create (context) {
|
|
26
|
+
const filename = context.getFilename();
|
|
27
|
+
|
|
28
|
+
const isEntityDtoFile = filename.includes("/dto/Entity/") &&
|
|
29
|
+
filename.endsWith("EntityDto.ts");
|
|
30
|
+
|
|
31
|
+
if (!isEntityDtoFile) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
MethodDefinition (node) {
|
|
37
|
+
if (node.static &&
|
|
38
|
+
node.key?.name === "create" &&
|
|
39
|
+
node.value?.params) {
|
|
40
|
+
|
|
41
|
+
const params = node.value.params;
|
|
42
|
+
|
|
43
|
+
if (params.length > 0 && params[0].type === "Identifier") {
|
|
44
|
+
const param = params[0];
|
|
45
|
+
|
|
46
|
+
if (param.typeAnnotation &&
|
|
47
|
+
param.typeAnnotation.typeAnnotation?.type === "TSTypeLiteral") {
|
|
48
|
+
const properties = param.typeAnnotation.typeAnnotation.members;
|
|
49
|
+
|
|
50
|
+
properties.forEach((prop) => {
|
|
51
|
+
if (prop.type === "TSPropertySignature" &&
|
|
52
|
+
prop.key?.type === "Identifier") {
|
|
53
|
+
const propName = prop.key.name;
|
|
54
|
+
|
|
55
|
+
if (propName === "id") {
|
|
56
|
+
context.report({
|
|
57
|
+
node: prop,
|
|
58
|
+
messageId: "noIdParameter",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (propName === "createdAt") {
|
|
63
|
+
context.report({
|
|
64
|
+
node: prop,
|
|
65
|
+
messageId: "noCreatedAtParameter",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (propName === "updatedAt") {
|
|
70
|
+
context.report({
|
|
71
|
+
node: prop,
|
|
72
|
+
messageId: "noUpdatedAtParameter",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (node.value?.body?.body) {
|
|
81
|
+
const body = node.value.body.body;
|
|
82
|
+
|
|
83
|
+
body.forEach((statement) => {
|
|
84
|
+
if (statement.type === "ExpressionStatement" &&
|
|
85
|
+
statement.expression?.type === "AssignmentExpression" &&
|
|
86
|
+
statement.expression.left?.type === "MemberExpression") {
|
|
87
|
+
const propName = statement.expression.left.property?.name;
|
|
88
|
+
|
|
89
|
+
if (propName === "id") {
|
|
90
|
+
context.report({
|
|
91
|
+
node: statement,
|
|
92
|
+
messageId: "noIdAssignment",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (propName === "createdAt") {
|
|
97
|
+
context.report({
|
|
98
|
+
node: statement,
|
|
99
|
+
messageId: "noCreatedAtAssignment",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (propName === "updatedAt") {
|
|
104
|
+
context.report({
|
|
105
|
+
node: statement,
|
|
106
|
+
messageId: "noUpdatedAtAssignment",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|