@checkdigit/eslint-plugin 7.6.0-PR.75-7ee9 → 7.6.0-PR.75-a611
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/dist-mjs/agent/fetch-response-body-json.mjs +1 -1
- package/dist-mjs/agent/fetch-then.mjs +4 -72
- package/dist-mjs/agent/fetch.mjs +2 -2
- package/dist-mjs/agent/no-expect-assertion.mjs +394 -0
- package/dist-mjs/agent/no-fixture.mjs +34 -280
- package/dist-mjs/agent/response-reference.mjs +3 -6
- package/dist-mjs/index.mjs +15 -17
- package/dist-types/agent/fetch-then.d.ts +1 -1
- package/dist-types/agent/{no-supertest.d.ts → no-expect-assertion.d.ts} +1 -1
- package/dist-types/agent/response-reference.d.ts +0 -1
- package/package.json +1 -1
- package/src/agent/fetch-response-body-json.ts +1 -1
- package/src/agent/fetch-then.ts +87 -88
- package/src/agent/fetch.ts +4 -2
- package/src/agent/no-expect-assertion.ts +570 -0
- package/src/agent/no-fixture.ts +46 -424
- package/src/agent/response-reference.ts +11 -11
- package/src/index.ts +16 -16
- package/dist-mjs/agent/no-supertest.mjs +0 -346
- package/src/agent/no-supertest.ts +0 -517
package/src/agent/no-fixture.ts
CHANGED
|
@@ -9,110 +9,60 @@
|
|
|
9
9
|
import { strict as assert } from 'node:assert';
|
|
10
10
|
|
|
11
11
|
import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
|
12
|
-
import type { Scope, ScopeManager, Variable } from '@typescript-eslint/scope-manager';
|
|
13
12
|
import type { SourceCode } from '@typescript-eslint/utils/ts-eslint';
|
|
14
13
|
|
|
15
|
-
import {
|
|
16
|
-
getEnclosingFunction,
|
|
17
|
-
getEnclosingScopeNode,
|
|
18
|
-
getEnclosingStatement,
|
|
19
|
-
getParent,
|
|
20
|
-
isUsedInArrayOrAsArgument,
|
|
21
|
-
} from '../library/ts-tree';
|
|
14
|
+
import { getParent } from '../library/ts-tree';
|
|
22
15
|
import getDocumentationUrl from '../get-documentation-url';
|
|
23
16
|
import { getIndentation } from '../library/format';
|
|
24
17
|
import { isValidPropertyName } from '../library/variable';
|
|
25
|
-
import { analyzeResponseReferences } from './response-reference';
|
|
26
|
-
import {
|
|
27
|
-
getResponseBodyRetrievalText,
|
|
28
|
-
getResponseHeadersRetrievalText,
|
|
29
|
-
getResponseStatusRetrievalText,
|
|
30
|
-
hasAssertions,
|
|
31
|
-
} from './fetch';
|
|
32
18
|
import { replaceEndpointUrlPrefixWithBasePath } from './url';
|
|
33
19
|
|
|
34
20
|
export const ruleId = 'no-fixture';
|
|
35
21
|
|
|
36
22
|
interface FixtureCallInformation {
|
|
37
|
-
|
|
38
|
-
| TSESTree.AwaitExpression
|
|
39
|
-
| TSESTree.ReturnStatement
|
|
40
|
-
| TSESTree.VariableDeclaration
|
|
41
|
-
| TSESTree.CallExpression
|
|
42
|
-
| TSESTree.ExpressionStatement;
|
|
43
|
-
fixtureNode: TSESTree.AwaitExpression | TSESTree.CallExpression;
|
|
44
|
-
variableDeclaration?: TSESTree.VariableDeclaration;
|
|
45
|
-
variableAssignment?: TSESTree.ExpressionStatement;
|
|
23
|
+
fixtureNode: TSESTree.CallExpression;
|
|
46
24
|
requestBody?: TSESTree.Expression;
|
|
47
25
|
requestHeaders?: { name: TSESTree.Expression; value: TSESTree.Expression }[];
|
|
48
26
|
requestHeadersObjectLiteral?: TSESTree.ObjectExpression;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
27
|
+
statusAssertion?: TSESTree.CallExpressionArgument[];
|
|
28
|
+
nonStatusAssertions?: TSESTree.CallExpressionArgument[][];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isStatusAssertion(expectArguments: TSESTree.CallExpressionArgument[]) {
|
|
32
|
+
if (expectArguments.length === 1) {
|
|
33
|
+
const [maybeStatusAssertion] = expectArguments;
|
|
34
|
+
assert.ok(maybeStatusAssertion);
|
|
35
|
+
if (
|
|
36
|
+
(maybeStatusAssertion.type === AST_NODE_TYPES.MemberExpression &&
|
|
37
|
+
maybeStatusAssertion.object.type === AST_NODE_TYPES.Identifier &&
|
|
38
|
+
maybeStatusAssertion.object.name === 'StatusCodes') ||
|
|
39
|
+
(maybeStatusAssertion.type === AST_NODE_TYPES.Literal && typeof maybeStatusAssertion.value === 'number')
|
|
40
|
+
) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
54
45
|
}
|
|
55
46
|
|
|
56
47
|
// recursively analyze the fixture/supertest call chain to collect information of request/response
|
|
57
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
58
48
|
function analyzeFixtureCall(call: TSESTree.CallExpression, results: FixtureCallInformation, sourceCode: SourceCode) {
|
|
49
|
+
sourceCode.getText(call);
|
|
50
|
+
results.fixtureNode = call;
|
|
51
|
+
|
|
52
|
+
let nextCall;
|
|
59
53
|
const parent = getParent(call);
|
|
60
54
|
assert.ok(parent, 'parent should exist for fixture/supertest call node');
|
|
61
55
|
|
|
62
|
-
|
|
63
|
-
if (parent.type === AST_NODE_TYPES.ReturnStatement) {
|
|
64
|
-
// direct return, no variable declaration or await
|
|
65
|
-
results.fixtureNode = call;
|
|
66
|
-
results.rootNode = parent;
|
|
67
|
-
} else if (
|
|
68
|
-
parent.type === AST_NODE_TYPES.ArrayExpression ||
|
|
69
|
-
parent.type === AST_NODE_TYPES.CallExpression ||
|
|
70
|
-
parent.type === AST_NODE_TYPES.ArrowFunctionExpression
|
|
71
|
-
) {
|
|
72
|
-
// direct return, no variable declaration or await
|
|
73
|
-
results.fixtureNode = call;
|
|
74
|
-
results.rootNode = call;
|
|
75
|
-
} else if (parent.type === AST_NODE_TYPES.AwaitExpression) {
|
|
76
|
-
results.fixtureNode = call;
|
|
77
|
-
const enclosingStatement = getEnclosingStatement(parent);
|
|
78
|
-
assert.ok(enclosingStatement);
|
|
79
|
-
const awaitParent = getParent(parent);
|
|
80
|
-
if (awaitParent?.type === AST_NODE_TYPES.MemberExpression) {
|
|
81
|
-
results.rootNode = parent;
|
|
82
|
-
results.inlineStatementNode = enclosingStatement;
|
|
83
|
-
if (awaitParent.property.type === AST_NODE_TYPES.Identifier && awaitParent.property.name === 'body') {
|
|
84
|
-
results.inlineBodyReference = awaitParent;
|
|
85
|
-
}
|
|
86
|
-
if (
|
|
87
|
-
awaitParent.property.type === AST_NODE_TYPES.Identifier &&
|
|
88
|
-
(awaitParent.property.name === 'status' || awaitParent.property.name === 'statusCode')
|
|
89
|
-
) {
|
|
90
|
-
results.inlineStatusReference = awaitParent;
|
|
91
|
-
}
|
|
92
|
-
if (
|
|
93
|
-
awaitParent.property.type === AST_NODE_TYPES.Identifier &&
|
|
94
|
-
(awaitParent.property.name === 'header' || awaitParent.property.name === 'headers')
|
|
95
|
-
) {
|
|
96
|
-
results.inlineHeadersReference = awaitParent;
|
|
97
|
-
}
|
|
98
|
-
} else if (enclosingStatement.type === AST_NODE_TYPES.VariableDeclaration) {
|
|
99
|
-
results.variableDeclaration = enclosingStatement;
|
|
100
|
-
results.rootNode = enclosingStatement;
|
|
101
|
-
} else if (
|
|
102
|
-
enclosingStatement.type === AST_NODE_TYPES.ExpressionStatement &&
|
|
103
|
-
enclosingStatement.expression.type === AST_NODE_TYPES.AssignmentExpression
|
|
104
|
-
) {
|
|
105
|
-
results.variableAssignment = enclosingStatement;
|
|
106
|
-
results.rootNode = enclosingStatement;
|
|
107
|
-
} else {
|
|
108
|
-
results.rootNode = parent;
|
|
109
|
-
}
|
|
110
|
-
} else if (parent.type === AST_NODE_TYPES.MemberExpression && parent.property.type === AST_NODE_TYPES.Identifier) {
|
|
56
|
+
if (parent.type === AST_NODE_TYPES.MemberExpression && parent.property.type === AST_NODE_TYPES.Identifier) {
|
|
111
57
|
if (parent.property.name === 'expect') {
|
|
112
58
|
// supertest assertions
|
|
113
59
|
const assertionCall = getParent(parent);
|
|
114
60
|
assert.ok(assertionCall && assertionCall.type === AST_NODE_TYPES.CallExpression);
|
|
115
|
-
|
|
61
|
+
if (isStatusAssertion(assertionCall.arguments)) {
|
|
62
|
+
results.statusAssertion = assertionCall.arguments;
|
|
63
|
+
} else {
|
|
64
|
+
results.nonStatusAssertions = [...(results.nonStatusAssertions ?? []), assertionCall.arguments];
|
|
65
|
+
}
|
|
116
66
|
nextCall = assertionCall;
|
|
117
67
|
} else if (parent.property.name === 'send') {
|
|
118
68
|
// request body
|
|
@@ -132,162 +82,14 @@ function analyzeFixtureCall(call: TSESTree.CallExpression, results: FixtureCallI
|
|
|
132
82
|
}
|
|
133
83
|
nextCall = setRequestHeaderCall;
|
|
134
84
|
}
|
|
135
|
-
} else {
|
|
136
|
-
throw new Error(`Unexpected expression in fixture/supertest call ${sourceCode.getText(parent)}.`);
|
|
137
85
|
}
|
|
138
86
|
if (nextCall) {
|
|
139
87
|
analyzeFixtureCall(nextCall, results, sourceCode);
|
|
140
88
|
}
|
|
141
89
|
}
|
|
142
90
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
fixtureCallInformation: FixtureCallInformation,
|
|
146
|
-
sourceCode: SourceCode,
|
|
147
|
-
responseVariableName: string,
|
|
148
|
-
destructuringResponseHeadersVariable: Variable | undefined,
|
|
149
|
-
) {
|
|
150
|
-
let statusAssertion: string | undefined;
|
|
151
|
-
const nonStatusAssertions: string[] = [];
|
|
152
|
-
for (const expectArguments of fixtureCallInformation.assertions ?? []) {
|
|
153
|
-
if (expectArguments.length === 1) {
|
|
154
|
-
const [assertionArgument] = expectArguments;
|
|
155
|
-
assert.ok(assertionArgument);
|
|
156
|
-
if (
|
|
157
|
-
(assertionArgument.type === AST_NODE_TYPES.MemberExpression &&
|
|
158
|
-
assertionArgument.object.type === AST_NODE_TYPES.Identifier &&
|
|
159
|
-
assertionArgument.object.name === 'StatusCodes') ||
|
|
160
|
-
assertionArgument.type === AST_NODE_TYPES.Literal ||
|
|
161
|
-
sourceCode.getText(assertionArgument as TSESTree.Node).includes('StatusCodes.')
|
|
162
|
-
) {
|
|
163
|
-
// status code assertion
|
|
164
|
-
statusAssertion = `assert.equal(${responseVariableName}.status, ${sourceCode.getText(assertionArgument)})`;
|
|
165
|
-
} else if (assertionArgument.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
166
|
-
// callback assertion using arrow function
|
|
167
|
-
let functionBody = sourceCode.getText(assertionArgument.body);
|
|
168
|
-
|
|
169
|
-
const [originalResponseArgument] = assertionArgument.params;
|
|
170
|
-
assert.ok(originalResponseArgument?.type === AST_NODE_TYPES.Identifier);
|
|
171
|
-
const originalResponseArgumentName = originalResponseArgument.name;
|
|
172
|
-
if (originalResponseArgumentName !== responseVariableName) {
|
|
173
|
-
functionBody = functionBody.replace(
|
|
174
|
-
new RegExp(`\\b${originalResponseArgumentName}\\b`, 'ug'),
|
|
175
|
-
responseVariableName,
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
nonStatusAssertions.push(`assert.doesNotThrow(()=>${functionBody})`);
|
|
179
|
-
} else if (assertionArgument.type === AST_NODE_TYPES.Identifier) {
|
|
180
|
-
// callback assertion using function reference
|
|
181
|
-
nonStatusAssertions.push(
|
|
182
|
-
`assert.doesNotThrow(()=>${sourceCode.getText(assertionArgument)}(${responseVariableName}))`,
|
|
183
|
-
);
|
|
184
|
-
} else if (
|
|
185
|
-
assertionArgument.type === AST_NODE_TYPES.ObjectExpression ||
|
|
186
|
-
assertionArgument.type === AST_NODE_TYPES.CallExpression
|
|
187
|
-
) {
|
|
188
|
-
// body deep equal assertion
|
|
189
|
-
nonStatusAssertions.push(
|
|
190
|
-
`assert.deepEqual(await ${responseVariableName}.json(), ${sourceCode.getText(assertionArgument)})`,
|
|
191
|
-
);
|
|
192
|
-
} else {
|
|
193
|
-
throw new Error(`Unexpected Supertest assertion argument: ".expect(${sourceCode.getText(assertionArgument)})`);
|
|
194
|
-
}
|
|
195
|
-
} else if (expectArguments.length === 2) {
|
|
196
|
-
// header assertion
|
|
197
|
-
const [headerName, headerValue] = expectArguments;
|
|
198
|
-
assert.ok(headerName && headerValue);
|
|
199
|
-
const headersReference =
|
|
200
|
-
destructuringResponseHeadersVariable !== undefined
|
|
201
|
-
? destructuringResponseHeadersVariable.name
|
|
202
|
-
: `${responseVariableName}.headers`;
|
|
203
|
-
if (headerValue.type === AST_NODE_TYPES.Literal && headerValue.value instanceof RegExp) {
|
|
204
|
-
nonStatusAssertions.push(
|
|
205
|
-
`assert.ok(${headersReference}.get(${sourceCode.getText(headerName)}).match(${sourceCode.getText(headerValue)}))`,
|
|
206
|
-
);
|
|
207
|
-
} else {
|
|
208
|
-
nonStatusAssertions.push(
|
|
209
|
-
`assert.equal(${headersReference}.get(${sourceCode.getText(headerName)}), ${sourceCode.getText(headerValue)})`,
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return {
|
|
215
|
-
statusAssertion,
|
|
216
|
-
nonStatusAssertions,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function getResponseVariableNameToUse(
|
|
221
|
-
methodName: string,
|
|
222
|
-
urlArgumentNode: TSESTree.CallExpressionArgument,
|
|
223
|
-
originalUrlArgumentText: string,
|
|
224
|
-
scopeManager: ScopeManager,
|
|
225
|
-
fixtureCallInformation: FixtureCallInformation,
|
|
226
|
-
scopeVariablesMap: Map<Scope, string[]>,
|
|
227
|
-
) {
|
|
228
|
-
if (fixtureCallInformation.variableAssignment) {
|
|
229
|
-
assert.ok(
|
|
230
|
-
fixtureCallInformation.variableAssignment.expression.type === AST_NODE_TYPES.AssignmentExpression &&
|
|
231
|
-
fixtureCallInformation.variableAssignment.expression.left.type === AST_NODE_TYPES.Identifier,
|
|
232
|
-
);
|
|
233
|
-
return fixtureCallInformation.variableAssignment.expression.left.name;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (fixtureCallInformation.variableDeclaration) {
|
|
237
|
-
const firstDeclaration = fixtureCallInformation.variableDeclaration.declarations[0];
|
|
238
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
239
|
-
if (firstDeclaration !== undefined && firstDeclaration.id.type === AST_NODE_TYPES.Identifier) {
|
|
240
|
-
return firstDeclaration.id.name;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const enclosingScopeNode = getEnclosingScopeNode(fixtureCallInformation.rootNode);
|
|
245
|
-
scopeManager.getDeclaredVariables(fixtureCallInformation.rootNode);
|
|
246
|
-
assert.ok(enclosingScopeNode);
|
|
247
|
-
const scope = scopeManager.acquire(enclosingScopeNode);
|
|
248
|
-
assert.ok(scope !== null);
|
|
249
|
-
let scopeVariables = scopeVariablesMap.get(scope);
|
|
250
|
-
if (!scopeVariables) {
|
|
251
|
-
scopeVariables = [...scope.set.keys()];
|
|
252
|
-
scopeVariablesMap.set(scope, scopeVariables);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
let responseVariableNameBase = 'response';
|
|
256
|
-
if (urlArgumentNode.type === AST_NODE_TYPES.Literal || urlArgumentNode.type === AST_NODE_TYPES.TemplateLiteral) {
|
|
257
|
-
const urlWithoutQuotes = originalUrlArgumentText.replace(/['"`]/gu, '');
|
|
258
|
-
const urlWithoutQuery = urlWithoutQuotes.includes('?')
|
|
259
|
-
? urlWithoutQuotes.slice(0, urlWithoutQuotes.indexOf('?'))
|
|
260
|
-
: urlWithoutQuotes;
|
|
261
|
-
const parts = urlWithoutQuery.startsWith('${')
|
|
262
|
-
? urlWithoutQuery.split('/').slice(1)
|
|
263
|
-
: // eslint-disable-next-line no-magic-numbers
|
|
264
|
-
urlWithoutQuery.split('/').slice(3);
|
|
265
|
-
|
|
266
|
-
responseVariableNameBase = [...parts, methodName.toLocaleLowerCase()]
|
|
267
|
-
.map((part) => part.split(/[-=]/u))
|
|
268
|
-
.flat()
|
|
269
|
-
.filter((part) => part.trim() !== '' && !/\$\{.*\}/u.test(part)) // keep only non-empty parts that are not path parameters
|
|
270
|
-
.map((part) => `${part[0]?.toUpperCase() ?? ''}${part.slice(1)}`)
|
|
271
|
-
.join('');
|
|
272
|
-
responseVariableNameBase = `${responseVariableNameBase[0]?.toLowerCase() ?? ''}${responseVariableNameBase.slice(1)}Response`;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
let responseVariableCounter = 0;
|
|
276
|
-
let responseVariableNameToUse;
|
|
277
|
-
while (responseVariableNameToUse === undefined) {
|
|
278
|
-
responseVariableNameToUse = `${responseVariableNameBase}${responseVariableCounter === 0 ? '' : responseVariableCounter.toString()}`;
|
|
279
|
-
if (scopeVariables.includes(responseVariableNameToUse)) {
|
|
280
|
-
responseVariableNameToUse = undefined;
|
|
281
|
-
}
|
|
282
|
-
responseVariableCounter++;
|
|
283
|
-
}
|
|
284
|
-
scopeVariables.push(responseVariableNameToUse);
|
|
285
|
-
return responseVariableNameToUse;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function isResponseBodyRedefinition(responseBodyReference: TSESTree.MemberExpression): boolean {
|
|
289
|
-
const parent = getParent(responseBodyReference);
|
|
290
|
-
return parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier;
|
|
91
|
+
function getExpectAssertion(expectArguments: TSESTree.CallExpressionArgument[], sourceCode: SourceCode) {
|
|
92
|
+
return `expect(${expectArguments.map((arg) => sourceCode.getText(arg)).join(', ')})`;
|
|
291
93
|
}
|
|
292
94
|
|
|
293
95
|
const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
|
|
@@ -308,28 +110,16 @@ const rule: ESLintUtils.RuleModule<'unknownError' | 'preferNativeFetch'> = creat
|
|
|
308
110
|
schema: [],
|
|
309
111
|
},
|
|
310
112
|
defaultOptions: [],
|
|
311
|
-
// eslint-disable-next-line max-lines-per-function
|
|
312
113
|
create(context) {
|
|
313
114
|
const sourceCode = context.sourceCode;
|
|
314
115
|
const scopeManager = sourceCode.scopeManager;
|
|
315
116
|
assert.ok(scopeManager !== null);
|
|
316
|
-
const scopeVariablesMap = new Map<Scope, string[]>();
|
|
317
117
|
|
|
318
118
|
return {
|
|
319
|
-
// eslint-disable-next-line max-lines-per-function
|
|
320
119
|
'CallExpression[callee.object.object.name="fixture"][callee.object.property.name="api"]': (
|
|
321
120
|
fixtureCall: TSESTree.CallExpression,
|
|
322
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
323
121
|
) => {
|
|
324
122
|
try {
|
|
325
|
-
if (
|
|
326
|
-
hasAssertions(fixtureCall) &&
|
|
327
|
-
(isUsedInArrayOrAsArgument(fixtureCall) || getEnclosingFunction(fixtureCall)?.async === false)
|
|
328
|
-
) {
|
|
329
|
-
// skip and leave it to "fetch-then" rule to handle it because no "await" can be used here
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
123
|
const fixtureFunction = fixtureCall.callee; // e.g. fixture.api.get
|
|
334
124
|
assert.ok(fixtureFunction.type === AST_NODE_TYPES.MemberExpression);
|
|
335
125
|
const indentation = getIndentation(fixtureCall, sourceCode);
|
|
@@ -340,16 +130,6 @@ const rule: ESLintUtils.RuleModule<'unknownError' | 'preferNativeFetch'> = creat
|
|
|
340
130
|
const fixtureCallInformation = {} as FixtureCallInformation;
|
|
341
131
|
analyzeFixtureCall(fixtureCall, fixtureCallInformation, sourceCode);
|
|
342
132
|
|
|
343
|
-
const {
|
|
344
|
-
variable: responseVariable,
|
|
345
|
-
bodyReferences: responseBodyReferences,
|
|
346
|
-
headersReferences: responseHeadersReferences,
|
|
347
|
-
statusReferences: responseStatusReferences,
|
|
348
|
-
destructuringBodyVariable: destructuringResponseBodyVariable,
|
|
349
|
-
destructuringStatusVariable: destructuringResponseStatusVariable,
|
|
350
|
-
destructuringHeadersVariable: destructuringResponseHeadersVariable,
|
|
351
|
-
} = analyzeResponseReferences(fixtureCallInformation.variableDeclaration, scopeManager);
|
|
352
|
-
|
|
353
133
|
// convert url from `/sample-service/v1/ping` to `${BASE_PATH}/ping`
|
|
354
134
|
const originalUrlArgumentText = sourceCode.getText(urlArgumentNode);
|
|
355
135
|
const fetchUrlArgumentText = replaceEndpointUrlPrefixWithBasePath(originalUrlArgumentText);
|
|
@@ -383,182 +163,24 @@ const rule: ESLintUtils.RuleModule<'unknownError' | 'preferNativeFetch'> = creat
|
|
|
383
163
|
'}',
|
|
384
164
|
].join(`\n${indentation}`);
|
|
385
165
|
|
|
386
|
-
const responseVariableNameToUse = getResponseVariableNameToUse(
|
|
387
|
-
methodNameToUse,
|
|
388
|
-
urlArgumentNode,
|
|
389
|
-
originalUrlArgumentText,
|
|
390
|
-
scopeManager,
|
|
391
|
-
fixtureCallInformation,
|
|
392
|
-
scopeVariablesMap,
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
const isResponseBodyVariableRedefinitionNeeded =
|
|
396
|
-
destructuringResponseBodyVariable !== undefined ||
|
|
397
|
-
fixtureCallInformation.inlineBodyReference !== undefined ||
|
|
398
|
-
(responseBodyReferences.length > 0 && !responseBodyReferences.some(isResponseBodyRedefinition));
|
|
399
|
-
const redefineResponseBodyVariableName = `${responseVariableNameToUse}Body`;
|
|
400
|
-
|
|
401
|
-
const isResponseStatusVariableRedefinitionNeeded =
|
|
402
|
-
destructuringResponseStatusVariable !== undefined ||
|
|
403
|
-
fixtureCallInformation.inlineStatusReference !== undefined;
|
|
404
|
-
const redefineResponseStatusVariableName = `${responseVariableNameToUse}Status`;
|
|
405
|
-
|
|
406
|
-
const isResponseHeadersVariableRedefinitionNeeded =
|
|
407
|
-
(destructuringResponseHeadersVariable !== undefined &&
|
|
408
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
409
|
-
(destructuringResponseHeadersVariable as TSESTree.ObjectPattern).type === AST_NODE_TYPES.ObjectPattern) ||
|
|
410
|
-
fixtureCallInformation.inlineHeadersReference !== undefined;
|
|
411
|
-
const redefineResponseHeadersVariableName = `${responseVariableNameToUse}Headers`;
|
|
412
|
-
|
|
413
|
-
const isResponseVariableRedefinitionNeeded =
|
|
414
|
-
(fixtureCallInformation.variableAssignment === undefined &&
|
|
415
|
-
responseVariable === undefined &&
|
|
416
|
-
fixtureCallInformation.assertions !== undefined) ||
|
|
417
|
-
isResponseBodyVariableRedefinitionNeeded ||
|
|
418
|
-
isResponseStatusVariableRedefinitionNeeded ||
|
|
419
|
-
isResponseHeadersVariableRedefinitionNeeded;
|
|
420
|
-
|
|
421
|
-
const responseBodyHeadersVariableRedefineLines = isResponseVariableRedefinitionNeeded
|
|
422
|
-
? [
|
|
423
|
-
// eslint-disable-next-line no-nested-ternary
|
|
424
|
-
...(destructuringResponseBodyVariable
|
|
425
|
-
? [
|
|
426
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
427
|
-
`${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseBodyVariable as TSESTree.ObjectPattern).type === AST_NODE_TYPES.ObjectPattern ? sourceCode.getText(destructuringResponseBodyVariable as TSESTree.ObjectPattern) : (destructuringResponseBodyVariable as Variable).name} = ${getResponseBodyRetrievalText(responseVariableNameToUse)}`,
|
|
428
|
-
]
|
|
429
|
-
: isResponseBodyVariableRedefinitionNeeded
|
|
430
|
-
? [
|
|
431
|
-
`const ${redefineResponseBodyVariableName} = ${getResponseBodyRetrievalText(responseVariableNameToUse)}`,
|
|
432
|
-
]
|
|
433
|
-
: []),
|
|
434
|
-
// eslint-disable-next-line no-nested-ternary
|
|
435
|
-
...(destructuringResponseStatusVariable
|
|
436
|
-
? [
|
|
437
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
438
|
-
`${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseStatusVariable as TSESTree.ObjectPattern).type === AST_NODE_TYPES.ObjectPattern ? sourceCode.getText(destructuringResponseStatusVariable as TSESTree.ObjectPattern) : (destructuringResponseStatusVariable as Variable).name} = ${getResponseStatusRetrievalText(responseVariableNameToUse)}`,
|
|
439
|
-
]
|
|
440
|
-
: isResponseStatusVariableRedefinitionNeeded
|
|
441
|
-
? [
|
|
442
|
-
`const ${redefineResponseStatusVariableName} = ${getResponseStatusRetrievalText(responseVariableNameToUse)}`,
|
|
443
|
-
]
|
|
444
|
-
: []),
|
|
445
|
-
// eslint-disable-next-line no-nested-ternary
|
|
446
|
-
...(destructuringResponseHeadersVariable
|
|
447
|
-
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
448
|
-
(destructuringResponseHeadersVariable as TSESTree.ObjectPattern).type ===
|
|
449
|
-
AST_NODE_TYPES.ObjectPattern
|
|
450
|
-
? (destructuringResponseHeadersVariable as TSESTree.ObjectPattern).properties.map((property) => {
|
|
451
|
-
assert.ok(property.type === AST_NODE_TYPES.Property);
|
|
452
|
-
assert.equal(property.value.type, AST_NODE_TYPES.Identifier);
|
|
453
|
-
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
454
|
-
return `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${property.value.name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}.get(${property.key.type === AST_NODE_TYPES.Literal ? sourceCode.getText(property.key) : `'${sourceCode.getText(property.key)}'`})`;
|
|
455
|
-
})
|
|
456
|
-
: [
|
|
457
|
-
`${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseHeadersVariable as Variable).name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}`,
|
|
458
|
-
]
|
|
459
|
-
: isResponseHeadersVariableRedefinitionNeeded
|
|
460
|
-
? [
|
|
461
|
-
`const ${redefineResponseHeadersVariableName} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}`,
|
|
462
|
-
]
|
|
463
|
-
: []),
|
|
464
|
-
]
|
|
465
|
-
: [];
|
|
466
|
-
|
|
467
|
-
const { statusAssertion, nonStatusAssertions } = createResponseAssertions(
|
|
468
|
-
fixtureCallInformation,
|
|
469
|
-
sourceCode,
|
|
470
|
-
responseVariableNameToUse,
|
|
471
|
-
destructuringResponseHeadersVariable as Variable | undefined,
|
|
472
|
-
);
|
|
473
|
-
|
|
474
|
-
// add variable declaration if needed
|
|
475
166
|
const fetchCallText = `fetch(${fetchUrlArgumentText}, ${fetchRequestArgumentLines})`;
|
|
476
|
-
const fetchStatementText =
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
...nonStatusAssertions,
|
|
488
|
-
].join(`;\n${indentation}`);
|
|
167
|
+
const fetchStatementText = [
|
|
168
|
+
fetchCallText,
|
|
169
|
+
...(fixtureCallInformation.statusAssertion === undefined
|
|
170
|
+
? []
|
|
171
|
+
: [getExpectAssertion(fixtureCallInformation.statusAssertion, sourceCode)]),
|
|
172
|
+
...(fixtureCallInformation.nonStatusAssertions === undefined
|
|
173
|
+
? []
|
|
174
|
+
: fixtureCallInformation.nonStatusAssertions.map((assertion) =>
|
|
175
|
+
getExpectAssertion(assertion, sourceCode),
|
|
176
|
+
)),
|
|
177
|
+
].join(`\n${indentation}.`);
|
|
489
178
|
|
|
490
179
|
context.report({
|
|
491
|
-
node:
|
|
180
|
+
node: fixtureCallInformation.fixtureNode,
|
|
492
181
|
messageId: 'preferNativeFetch',
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (fixtureCallInformation.inlineStatementNode) {
|
|
496
|
-
const preInlineDeclaration = [
|
|
497
|
-
fetchStatementText,
|
|
498
|
-
`${appendingAssignmentAndAssertionText};\n${indentation}`,
|
|
499
|
-
].join(``);
|
|
500
|
-
yield fixer.insertTextBefore(fixtureCallInformation.inlineStatementNode, preInlineDeclaration);
|
|
501
|
-
} else {
|
|
502
|
-
yield fixer.replaceText(nodeToReplace, fetchStatementText);
|
|
503
|
-
|
|
504
|
-
const needEndingSemiColon = sourceCode.getText(nodeToReplace).endsWith(';');
|
|
505
|
-
yield fixer.insertTextAfter(
|
|
506
|
-
nodeToReplace,
|
|
507
|
-
needEndingSemiColon ? `${appendingAssignmentAndAssertionText};` : appendingAssignmentAndAssertionText,
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// handle response body references
|
|
512
|
-
for (const responseBodyReference of responseBodyReferences) {
|
|
513
|
-
yield fixer.replaceText(
|
|
514
|
-
responseBodyReference,
|
|
515
|
-
isResponseBodyVariableRedefinitionNeeded || !isResponseBodyRedefinition(responseBodyReference)
|
|
516
|
-
? redefineResponseBodyVariableName
|
|
517
|
-
: getResponseBodyRetrievalText(responseVariableNameToUse),
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
if (fixtureCallInformation.inlineBodyReference) {
|
|
521
|
-
yield fixer.replaceText(fixtureCallInformation.inlineBodyReference, redefineResponseBodyVariableName);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// handle response headers references
|
|
525
|
-
for (const responseHeadersReference of responseHeadersReferences) {
|
|
526
|
-
const parent = getParent(responseHeadersReference);
|
|
527
|
-
assert.ok(parent);
|
|
528
|
-
let headerName;
|
|
529
|
-
if (parent.type === AST_NODE_TYPES.MemberExpression) {
|
|
530
|
-
const headerNameNode = parent.property;
|
|
531
|
-
headerName = parent.computed
|
|
532
|
-
? sourceCode.getText(headerNameNode)
|
|
533
|
-
: `'${sourceCode.getText(headerNameNode)}'`;
|
|
534
|
-
} else if (parent.type === AST_NODE_TYPES.CallExpression) {
|
|
535
|
-
const headerNameNode = parent.arguments[0];
|
|
536
|
-
headerName = sourceCode.getText(headerNameNode);
|
|
537
|
-
}
|
|
538
|
-
assert.ok(headerName !== undefined);
|
|
539
|
-
yield fixer.replaceText(parent, `${responseVariableNameToUse}.headers.get(${headerName})`);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// convert response.statusCode to response.status
|
|
543
|
-
for (const responseStatusReference of responseStatusReferences) {
|
|
544
|
-
if (
|
|
545
|
-
responseStatusReference.property.type === AST_NODE_TYPES.Identifier &&
|
|
546
|
-
responseStatusReference.property.name === 'statusCode'
|
|
547
|
-
) {
|
|
548
|
-
yield fixer.replaceText(responseStatusReference.property, `status`);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// handle direct return statement without await, e.g. "return fixture.api.get(...);"
|
|
553
|
-
if (
|
|
554
|
-
fixtureCallInformation.rootNode.type === AST_NODE_TYPES.ReturnStatement &&
|
|
555
|
-
fixtureCallInformation.assertions !== undefined
|
|
556
|
-
) {
|
|
557
|
-
yield fixer.insertTextAfter(
|
|
558
|
-
fixtureCallInformation.rootNode,
|
|
559
|
-
`\n${indentation}return ${responseVariableNameToUse};`,
|
|
560
|
-
);
|
|
561
|
-
}
|
|
182
|
+
fix(fixer) {
|
|
183
|
+
return fixer.replaceText(fixtureCallInformation.fixtureNode, fetchStatementText);
|
|
562
184
|
},
|
|
563
185
|
});
|
|
564
186
|
} catch (error) {
|
|
@@ -28,7 +28,7 @@ export function analyzeResponseReferences(
|
|
|
28
28
|
): {
|
|
29
29
|
variable?: Variable;
|
|
30
30
|
bodyReferences: TSESTree.MemberExpression[];
|
|
31
|
-
headersReferences: TSESTree.MemberExpression[];
|
|
31
|
+
// headersReferences: TSESTree.MemberExpression[];
|
|
32
32
|
statusReferences: TSESTree.MemberExpression[];
|
|
33
33
|
destructuringBodyVariable?: Variable | TSESTree.ObjectPattern;
|
|
34
34
|
destructuringHeadersVariable?: Variable | TSESTree.ObjectPattern;
|
|
@@ -38,7 +38,7 @@ export function analyzeResponseReferences(
|
|
|
38
38
|
const results: {
|
|
39
39
|
variable?: Variable;
|
|
40
40
|
bodyReferences: TSESTree.MemberExpression[];
|
|
41
|
-
headersReferences: TSESTree.MemberExpression[];
|
|
41
|
+
// headersReferences: TSESTree.MemberExpression[];
|
|
42
42
|
statusReferences: TSESTree.MemberExpression[];
|
|
43
43
|
destructuringBodyVariable?: Variable | TSESTree.ObjectPattern;
|
|
44
44
|
destructuringHeadersVariable?: Variable | TSESTree.ObjectPattern;
|
|
@@ -46,7 +46,7 @@ export function analyzeResponseReferences(
|
|
|
46
46
|
destructuringHeadersReferences?: TSESTree.MemberExpression[] | undefined;
|
|
47
47
|
} = {
|
|
48
48
|
bodyReferences: [],
|
|
49
|
-
headersReferences: [],
|
|
49
|
+
// headersReferences: [],
|
|
50
50
|
statusReferences: [],
|
|
51
51
|
};
|
|
52
52
|
if (!variableDeclaration) {
|
|
@@ -72,13 +72,13 @@ export function analyzeResponseReferences(
|
|
|
72
72
|
node.property.type === AST_NODE_TYPES.Identifier &&
|
|
73
73
|
node.property.name === 'body',
|
|
74
74
|
);
|
|
75
|
-
// e.g. response.headers / response.header / response.get()
|
|
76
|
-
results.headersReferences = responseReferences.filter(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
);
|
|
75
|
+
// // e.g. response.headers / response.header / response.get()
|
|
76
|
+
// results.headersReferences = responseReferences.filter(
|
|
77
|
+
// (node): node is TSESTree.MemberExpression =>
|
|
78
|
+
// node?.type === AST_NODE_TYPES.MemberExpression &&
|
|
79
|
+
// node.property.type === AST_NODE_TYPES.Identifier &&
|
|
80
|
+
// (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
|
|
81
|
+
// );
|
|
82
82
|
// e.g. response.status / response.statusCode
|
|
83
83
|
results.statusReferences = responseReferences.filter(
|
|
84
84
|
(node): node is TSESTree.MemberExpression =>
|
|
@@ -104,7 +104,7 @@ export function analyzeResponseReferences(
|
|
|
104
104
|
// header reference through destruction/renaming, e.g. "const { headers } = ..."
|
|
105
105
|
identifierParent.type === AST_NODE_TYPES.Property &&
|
|
106
106
|
identifierParent.key.type === AST_NODE_TYPES.Identifier &&
|
|
107
|
-
identifierParent.key.name === 'headers'
|
|
107
|
+
(identifierParent.key.name === 'headers' || identifierParent.key.name === 'header')
|
|
108
108
|
) {
|
|
109
109
|
results.destructuringHeadersVariable = responseVariable;
|
|
110
110
|
results.destructuringHeadersReferences = responseVariable.references
|