@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,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Ensure all controller methods have complete Swagger documentation in English
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "enforce complete English Swagger documentation for all controller methods",
|
|
12
|
+
category: "Best Practices",
|
|
13
|
+
recommended: true,
|
|
14
|
+
},
|
|
15
|
+
fixable: null,
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: {
|
|
18
|
+
missingApiOperation: "Controller method '{{methodName}}' is missing @ApiOperation decorator",
|
|
19
|
+
missingApiResponse: "Controller method '{{methodName}}' is missing @ApiResponse with status: {{status}}",
|
|
20
|
+
emptyApiOperation: "Controller method '{{methodName}}' has empty @ApiOperation summary or description",
|
|
21
|
+
germanDocumentation: "Swagger documentation must be in English. Found German text: '{{text}}'",
|
|
22
|
+
missingSwaggerTag: "Controller class is missing @ApiTags decorator",
|
|
23
|
+
missingApiBearerAuth: "Controller class is missing @ApiBearerAuth decorator",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
create(context) {
|
|
28
|
+
// Common German words that shouldn't appear in API documentation
|
|
29
|
+
const germanWords = [
|
|
30
|
+
// Eindeutig deutsche Artikel & Pronomen
|
|
31
|
+
"der", "die", "das", "ein", "eine", "einen", "einem", "einer", "eines",
|
|
32
|
+
"alle", "aller", "dieser", "diese", "dieses", "jeder", "jede", "jedes",
|
|
33
|
+
|
|
34
|
+
// Eindeutig deutsche Verben
|
|
35
|
+
"ist", "sind", "wird", "werden", "hat", "haben", "kann", "können", "soll", "sollen",
|
|
36
|
+
"gibt", "geben", "ruft", "abrufen", "holt", "holen", "erstellt", "erstellen",
|
|
37
|
+
"aktualisiert", "aktualisieren", "löscht", "löschen", "zurück", "zurückgeben",
|
|
38
|
+
"erfolgreich", "fehlgeschlagen", "abgerufen", "gelöscht", "erstellt",
|
|
39
|
+
|
|
40
|
+
// Eindeutig deutsche Nomen (keine englischen Homonyme!)
|
|
41
|
+
"daten", "fehler", "serverfehler", "liste", "gesundheit", "koordinate", "koordinaten",
|
|
42
|
+
"karten", "karte", "benutzer", "nutzer", "antwort", "anfrage", "informationen",
|
|
43
|
+
"objekt", "objekte", "wert", "werte", "eigenschaft", "eigenschaften",
|
|
44
|
+
|
|
45
|
+
// Deutsche Adjektive/Adverbien
|
|
46
|
+
"neue", "neues", "neuer", "neuen", "bestimmten", "bestimmte", "bestimmter",
|
|
47
|
+
"ungültige", "ungültiger", "ungültiges", "gültige", "gültiger", "gültiges",
|
|
48
|
+
"vollständige", "vollständiger", "verfügbare", "verfügbarer",
|
|
49
|
+
|
|
50
|
+
// Deutsche Präpositionen & Konjunktionen
|
|
51
|
+
"mit", "von", "für", "beim", "nach", "oder", "und", "aber", "wenn", "dass",
|
|
52
|
+
"durch", "über", "unter", "zwischen", "ohne", "während", "seit",
|
|
53
|
+
|
|
54
|
+
// Deutsche Status-/Antwort-Wörter (eindeutig deutsch)
|
|
55
|
+
"nicht", "gefunden", "vorhanden", "verfügbar", "möglich", "unmöglich",
|
|
56
|
+
"korrekt", "inkorrekt",
|
|
57
|
+
|
|
58
|
+
// Deutsche Fragewörter
|
|
59
|
+
"wo", "wie", "was", "wann", "warum", "welche", "welcher", "welches",
|
|
60
|
+
|
|
61
|
+
// Deutsche Zahlwörter
|
|
62
|
+
"eins", "zwei", "drei", "vier", "fünf", "sechs", "sieben", "acht", "neun", "zehn"
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
function isControllerClass(node) {
|
|
66
|
+
if (node.type !== "ClassDeclaration") return false;
|
|
67
|
+
|
|
68
|
+
return node.decorators && node.decorators.some(decorator => {
|
|
69
|
+
return decorator.expression &&
|
|
70
|
+
((decorator.expression.type === "Identifier" && decorator.expression.name === "Controller") ||
|
|
71
|
+
(decorator.expression.type === "CallExpression" &&
|
|
72
|
+
decorator.expression.callee.name === "Controller"));
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function containsGermanWords(text) {
|
|
77
|
+
if (!text || typeof text !== "string") return null;
|
|
78
|
+
|
|
79
|
+
const lowerText = text.toLowerCase();
|
|
80
|
+
|
|
81
|
+
for (const word of germanWords) {
|
|
82
|
+
// Use word boundaries to avoid false positives
|
|
83
|
+
const regex = new RegExp(`\\b${word}\\b`, "i");
|
|
84
|
+
if (regex.test(lowerText)) {
|
|
85
|
+
return word;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function checkStringForGerman(stringNode) {
|
|
93
|
+
if (stringNode && stringNode.type === "Literal" && typeof stringNode.value === "string") {
|
|
94
|
+
const germanWord = containsGermanWords(stringNode.value);
|
|
95
|
+
if (germanWord) {
|
|
96
|
+
context.report({
|
|
97
|
+
node: stringNode,
|
|
98
|
+
messageId: "germanDocumentation",
|
|
99
|
+
data: {
|
|
100
|
+
text: stringNode.value.substring(0, 80) + (stringNode.value.length > 80 ? "..." : "")
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function hasDecorator(decorators, decoratorName) {
|
|
108
|
+
if (!decorators) return false;
|
|
109
|
+
|
|
110
|
+
return decorators.some(decorator => {
|
|
111
|
+
const name = decorator.expression?.name || decorator.expression?.callee?.name;
|
|
112
|
+
return name === decoratorName;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function hasApiResponseDecorator(decorators, statuses) {
|
|
117
|
+
if (!decorators) return false;
|
|
118
|
+
|
|
119
|
+
const statusArray = Array.isArray(statuses) ? statuses : [statuses];
|
|
120
|
+
|
|
121
|
+
return statusArray.some(status => {
|
|
122
|
+
return decorators.some(decorator => {
|
|
123
|
+
if (decorator.expression?.type === "CallExpression" &&
|
|
124
|
+
decorator.expression.callee.name === "ApiResponse") {
|
|
125
|
+
|
|
126
|
+
const args = decorator.expression.arguments;
|
|
127
|
+
if (args.length > 0 && args[0].type === "ObjectExpression") {
|
|
128
|
+
const statusProp = args[0].properties.find(prop =>
|
|
129
|
+
prop.key && prop.key.name === "status"
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (statusProp && statusProp.value) {
|
|
133
|
+
return statusProp.value.value === status || statusProp.value.raw === status.toString();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getHttpMethodType(decorators) {
|
|
143
|
+
if (!decorators) return null;
|
|
144
|
+
|
|
145
|
+
const httpMethods = ["Get", "Post", "Put", "Delete", "Patch"];
|
|
146
|
+
for (const decorator of decorators) {
|
|
147
|
+
const decoratorName = decorator.expression?.name || decorator.expression?.callee?.name;
|
|
148
|
+
if (httpMethods.includes(decoratorName)) {
|
|
149
|
+
return decoratorName.toLowerCase();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function validateClassDecorators(node) {
|
|
156
|
+
if (!hasDecorator(node.decorators, "ApiTags")) {
|
|
157
|
+
context.report({
|
|
158
|
+
node: node,
|
|
159
|
+
messageId: "missingSwaggerTag"
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const filename = context.getFilename();
|
|
164
|
+
const isPublicController = filename.includes("/Public/");
|
|
165
|
+
|
|
166
|
+
if (!hasDecorator(node.decorators, "ApiBearerAuth") && !isPublicController) {
|
|
167
|
+
context.report({
|
|
168
|
+
node: node,
|
|
169
|
+
messageId: "missingApiBearerAuth"
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function validateApiOperation(member) {
|
|
175
|
+
const methodName = member.key.name;
|
|
176
|
+
|
|
177
|
+
if (!hasDecorator(member.decorators, "ApiOperation")) {
|
|
178
|
+
context.report({
|
|
179
|
+
node: member,
|
|
180
|
+
messageId: "missingApiOperation",
|
|
181
|
+
data: { methodName }
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check ApiOperation content for German text
|
|
187
|
+
member.decorators.forEach(decorator => {
|
|
188
|
+
if (decorator.expression?.type === "CallExpression" &&
|
|
189
|
+
decorator.expression.callee.name === "ApiOperation") {
|
|
190
|
+
|
|
191
|
+
const args = decorator.expression.arguments;
|
|
192
|
+
if (args.length > 0 && args[0].type === "ObjectExpression") {
|
|
193
|
+
let hasSummary = false;
|
|
194
|
+
let hasDescription = false;
|
|
195
|
+
|
|
196
|
+
args[0].properties.forEach(prop => {
|
|
197
|
+
if (prop.key && prop.key.name === "summary") {
|
|
198
|
+
hasSummary = true;
|
|
199
|
+
checkStringForGerman(prop.value);
|
|
200
|
+
} else if (prop.key && prop.key.name === "description") {
|
|
201
|
+
hasDescription = true;
|
|
202
|
+
checkStringForGerman(prop.value);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (!hasSummary || !hasDescription) {
|
|
207
|
+
context.report({
|
|
208
|
+
node: member,
|
|
209
|
+
messageId: "emptyApiOperation",
|
|
210
|
+
data: { methodName }
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function validateApiResponseDecorators(member) {
|
|
219
|
+
const httpMethod = getHttpMethodType(member.decorators);
|
|
220
|
+
if (!httpMethod) return;
|
|
221
|
+
|
|
222
|
+
const methodName = member.key.name;
|
|
223
|
+
|
|
224
|
+
// Check for required status codes
|
|
225
|
+
let successCodes = [200];
|
|
226
|
+
if (httpMethod === "post") {
|
|
227
|
+
successCodes = [200, 201];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const hasSuccessResponse = hasApiResponseDecorator(member.decorators, successCodes);
|
|
231
|
+
const has500Response = hasApiResponseDecorator(member.decorators, 500);
|
|
232
|
+
|
|
233
|
+
if (!hasSuccessResponse) {
|
|
234
|
+
const expectedCodes = successCodes.join(" or ");
|
|
235
|
+
context.report({
|
|
236
|
+
node: member,
|
|
237
|
+
messageId: "missingApiResponse",
|
|
238
|
+
data: { methodName, status: expectedCodes }
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!has500Response) {
|
|
243
|
+
context.report({
|
|
244
|
+
node: member,
|
|
245
|
+
messageId: "missingApiResponse",
|
|
246
|
+
data: { methodName, status: "500" }
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check ApiResponse content for German text
|
|
251
|
+
if (member.decorators) {
|
|
252
|
+
member.decorators.forEach(decorator => {
|
|
253
|
+
if (decorator.expression?.type === "CallExpression" &&
|
|
254
|
+
decorator.expression.callee.name === "ApiResponse") {
|
|
255
|
+
|
|
256
|
+
const args = decorator.expression.arguments;
|
|
257
|
+
if (args.length > 0 && args[0].type === "ObjectExpression") {
|
|
258
|
+
args[0].properties.forEach(prop => {
|
|
259
|
+
if (prop.key && prop.key.name === "description") {
|
|
260
|
+
checkStringForGerman(prop.value);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function validateApiParamDecorators(member) {
|
|
270
|
+
if (!member.decorators) return;
|
|
271
|
+
|
|
272
|
+
member.decorators.forEach(decorator => {
|
|
273
|
+
if (decorator.expression?.type === "CallExpression" &&
|
|
274
|
+
(decorator.expression.callee.name === "ApiParam" ||
|
|
275
|
+
decorator.expression.callee.name === "ApiQuery" ||
|
|
276
|
+
decorator.expression.callee.name === "ApiBody")) {
|
|
277
|
+
|
|
278
|
+
const args = decorator.expression.arguments;
|
|
279
|
+
if (args.length > 0 && args[0].type === "ObjectExpression") {
|
|
280
|
+
args[0].properties.forEach(prop => {
|
|
281
|
+
if (prop.key && prop.key.name === "description") {
|
|
282
|
+
checkStringForGerman(prop.value);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
ClassDeclaration(node) {
|
|
292
|
+
if (!isControllerClass(node)) return;
|
|
293
|
+
|
|
294
|
+
validateClassDecorators(node);
|
|
295
|
+
|
|
296
|
+
node.body.body.forEach(member => {
|
|
297
|
+
if (member.type === "MethodDefinition" &&
|
|
298
|
+
member.kind === "method" &&
|
|
299
|
+
member.accessibility === "public") {
|
|
300
|
+
|
|
301
|
+
const httpMethod = getHttpMethodType(member.decorators);
|
|
302
|
+
if (httpMethod) {
|
|
303
|
+
validateApiOperation(member);
|
|
304
|
+
validateApiResponseDecorators(member);
|
|
305
|
+
validateApiParamDecorators(member);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
},
|
|
312
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Ensure all controller methods have proper Swagger documentation including success and error status codes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "enforce that all controller methods have proper @ApiResponse documentation",
|
|
12
|
+
category: "Best Practices",
|
|
13
|
+
recommended: true,
|
|
14
|
+
},
|
|
15
|
+
fixable: null,
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: {
|
|
18
|
+
missingApiResponse: "Controller method '{{methodName}}' is missing @ApiResponse with status: {{status}}. All controller methods should document potential server errors (BaseController.sendError defaults to 500)",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
create(context) {
|
|
23
|
+
function isControllerClass(node) {
|
|
24
|
+
if (node.type !== "ClassDeclaration") return false;
|
|
25
|
+
|
|
26
|
+
return node.decorators && node.decorators.some(decorator => {
|
|
27
|
+
return decorator.expression &&
|
|
28
|
+
((decorator.expression.type === "Identifier" && decorator.expression.name === "Controller") ||
|
|
29
|
+
(decorator.expression.type === "CallExpression" &&
|
|
30
|
+
decorator.expression.callee.name === "Controller"));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function hasApiResponseDecorator(decorators, statuses) {
|
|
35
|
+
if (!decorators) return false;
|
|
36
|
+
|
|
37
|
+
const statusArray = Array.isArray(statuses) ? statuses : [statuses];
|
|
38
|
+
|
|
39
|
+
return statusArray.some(status => {
|
|
40
|
+
return decorators.some(decorator => {
|
|
41
|
+
if (decorator.expression.type === "CallExpression" &&
|
|
42
|
+
decorator.expression.callee.name === "ApiResponse") {
|
|
43
|
+
|
|
44
|
+
const args = decorator.expression.arguments;
|
|
45
|
+
if (args.length > 0 && args[0].type === "ObjectExpression") {
|
|
46
|
+
const statusProp = args[0].properties.find(prop =>
|
|
47
|
+
prop.key && prop.key.name === "status"
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (statusProp && statusProp.value) {
|
|
51
|
+
return statusProp.value.value === status || statusProp.value.raw === status.toString();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getHttpMethodType(decorators) {
|
|
61
|
+
if (!decorators) return null;
|
|
62
|
+
|
|
63
|
+
const httpMethods = ["Get", "Post", "Put", "Delete", "Patch"];
|
|
64
|
+
for (const decorator of decorators) {
|
|
65
|
+
const decoratorName = decorator.expression?.name || decorator.expression?.callee?.name;
|
|
66
|
+
if (httpMethods.includes(decoratorName)) {
|
|
67
|
+
return decoratorName.toLowerCase();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function validateApiResponseDecorators(member) {
|
|
74
|
+
const httpMethod = getHttpMethodType(member.decorators);
|
|
75
|
+
if (!httpMethod) return;
|
|
76
|
+
|
|
77
|
+
const methodName = member.key.name;
|
|
78
|
+
|
|
79
|
+
// Check for success status codes based on HTTP method
|
|
80
|
+
let successCodes = [200];
|
|
81
|
+
if (httpMethod === "post") {
|
|
82
|
+
successCodes = [200, 201]; // POST can return 200 or 201
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const hasSuccessResponse = hasApiResponseDecorator(member.decorators, successCodes);
|
|
86
|
+
const has500Response = hasApiResponseDecorator(member.decorators, 500);
|
|
87
|
+
|
|
88
|
+
if (!hasSuccessResponse) {
|
|
89
|
+
const expectedCodes = successCodes.join(" or ");
|
|
90
|
+
context.report({
|
|
91
|
+
node: member,
|
|
92
|
+
message: `Controller method '${methodName}' is missing @ApiResponse with status: ${expectedCodes}. All controller methods should document successful responses.`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!has500Response) {
|
|
97
|
+
context.report({
|
|
98
|
+
node: member,
|
|
99
|
+
message: `Controller method '${methodName}' is missing @ApiResponse with status: 500. All controller methods should document potential server errors (BaseController.sendError defaults to 500).`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
ClassDeclaration(node) {
|
|
106
|
+
if (!isControllerClass(node)) return;
|
|
107
|
+
|
|
108
|
+
node.body.body.forEach(member => {
|
|
109
|
+
if (member.type === "MethodDefinition" &&
|
|
110
|
+
member.kind === "method" &&
|
|
111
|
+
member.accessibility === "public") {
|
|
112
|
+
|
|
113
|
+
validateApiResponseDecorators(member);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|