@checkdigit/eslint-plugin 7.6.0-PR.75-1b09 → 7.6.0-PR.75-04b9
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-then.mjs +30 -26
- package/dist-mjs/agent/fetch.mjs +7 -6
- package/dist-mjs/agent/no-fixture.mjs +44 -41
- package/dist-mjs/agent/no-supertest.mjs +44 -41
- package/dist-mjs/agent/response-reference.mjs +14 -14
- package/dist-types/agent/fetch-then.d.ts +2 -2
- package/dist-types/agent/fetch.d.ts +3 -3
- package/dist-types/agent/no-fixture.d.ts +2 -2
- package/dist-types/agent/no-supertest.d.ts +2 -2
- package/dist-types/agent/response-reference.d.ts +10 -10
- package/package.json +1 -1
- package/src/agent/fetch-then.ts +49 -47
- package/src/agent/fetch.ts +13 -13
- package/src/agent/no-fixture.ts +87 -84
- package/src/agent/no-supertest.ts +88 -85
- package/src/agent/response-reference.ts +48 -41
|
@@ -8,20 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import { strict as assert } from 'node:assert';
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Expression,
|
|
15
|
-
ExpressionStatement,
|
|
16
|
-
MemberExpression,
|
|
17
|
-
Node,
|
|
18
|
-
ObjectExpression,
|
|
19
|
-
ObjectPattern,
|
|
20
|
-
ReturnStatement,
|
|
21
|
-
SimpleCallExpression,
|
|
22
|
-
VariableDeclaration,
|
|
23
|
-
} from 'estree';
|
|
24
|
-
import { type Rule, type Scope, SourceCode } from 'eslint';
|
|
11
|
+
import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
|
|
12
|
+
import type { Scope, ScopeManager, Variable } from '@typescript-eslint/scope-manager';
|
|
13
|
+
import type { SourceCode } from '@typescript-eslint/utils/ts-eslint';
|
|
25
14
|
|
|
26
15
|
import {
|
|
27
16
|
getEnclosingFunction,
|
|
@@ -29,7 +18,7 @@ import {
|
|
|
29
18
|
getEnclosingStatement,
|
|
30
19
|
getParent,
|
|
31
20
|
isUsedInArrayOrAsArgument,
|
|
32
|
-
} from '../library/tree';
|
|
21
|
+
} from '../library/ts-tree';
|
|
33
22
|
import getDocumentationUrl from '../get-documentation-url';
|
|
34
23
|
import { getIndentation } from '../library/format';
|
|
35
24
|
import { analyzeResponseReferences } from './response-reference';
|
|
@@ -38,86 +27,91 @@ import { getResponseBodyRetrievalText, getResponseHeadersRetrievalText } from '.
|
|
|
38
27
|
export const ruleId = 'no-supertest';
|
|
39
28
|
|
|
40
29
|
interface FixtureCallInformation {
|
|
41
|
-
rootNode:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
30
|
+
rootNode:
|
|
31
|
+
| TSESTree.AwaitExpression
|
|
32
|
+
| TSESTree.ReturnStatement
|
|
33
|
+
| TSESTree.VariableDeclaration
|
|
34
|
+
| TSESTree.CallExpression
|
|
35
|
+
| TSESTree.ExpressionStatement;
|
|
36
|
+
fixtureNode: TSESTree.AwaitExpression | TSESTree.CallExpression;
|
|
37
|
+
variableDeclaration?: TSESTree.VariableDeclaration;
|
|
38
|
+
variableAssignment?: TSESTree.ExpressionStatement;
|
|
39
|
+
requestBody?: TSESTree.Expression;
|
|
40
|
+
requestHeaders?: { name: TSESTree.Expression; value: TSESTree.Expression }[];
|
|
41
|
+
requestHeadersObjectLiteral?: TSESTree.ObjectExpression;
|
|
42
|
+
assertions?: TSESTree.Expression[][];
|
|
43
|
+
inlineStatementNode?: TSESTree.Node;
|
|
44
|
+
inlineBodyReference?: TSESTree.MemberExpression;
|
|
45
|
+
inlineHeadersReference?: TSESTree.MemberExpression;
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
// recursively analyze the fixture/supertest call chain to collect information of request/response
|
|
55
49
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
56
|
-
function analyzeFixtureCall(call:
|
|
50
|
+
function analyzeFixtureCall(call: TSESTree.CallExpression, results: FixtureCallInformation, sourceCode: SourceCode) {
|
|
57
51
|
const parent = getParent(call);
|
|
58
52
|
assert.ok(parent, 'parent should exist for fixture/supertest call node');
|
|
59
53
|
|
|
60
54
|
let nextCall;
|
|
61
|
-
if (parent.type ===
|
|
55
|
+
if (parent.type === AST_NODE_TYPES.ReturnStatement) {
|
|
62
56
|
// direct return, no variable declaration or await
|
|
63
57
|
results.fixtureNode = call;
|
|
64
58
|
results.rootNode = parent;
|
|
65
59
|
} else if (
|
|
66
|
-
parent.type ===
|
|
67
|
-
parent.type ===
|
|
68
|
-
parent.type ===
|
|
60
|
+
parent.type === AST_NODE_TYPES.ArrayExpression ||
|
|
61
|
+
parent.type === AST_NODE_TYPES.CallExpression ||
|
|
62
|
+
parent.type === AST_NODE_TYPES.ArrowFunctionExpression
|
|
69
63
|
) {
|
|
70
64
|
// direct return, no variable declaration or await
|
|
71
65
|
results.fixtureNode = call;
|
|
72
66
|
results.rootNode = call;
|
|
73
|
-
} else if (parent.type ===
|
|
67
|
+
} else if (parent.type === AST_NODE_TYPES.AwaitExpression) {
|
|
74
68
|
results.fixtureNode = call;
|
|
75
69
|
const enclosingStatement = getEnclosingStatement(parent);
|
|
76
70
|
assert.ok(enclosingStatement);
|
|
77
71
|
const awaitParent = getParent(parent);
|
|
78
|
-
if (awaitParent?.type ===
|
|
72
|
+
if (awaitParent?.type === AST_NODE_TYPES.MemberExpression) {
|
|
79
73
|
results.rootNode = parent;
|
|
80
74
|
results.inlineStatementNode = enclosingStatement;
|
|
81
|
-
if (awaitParent.property.type ===
|
|
75
|
+
if (awaitParent.property.type === AST_NODE_TYPES.Identifier && awaitParent.property.name === 'body') {
|
|
82
76
|
results.inlineBodyReference = awaitParent;
|
|
83
77
|
}
|
|
84
78
|
if (
|
|
85
|
-
awaitParent.property.type ===
|
|
79
|
+
awaitParent.property.type === AST_NODE_TYPES.Identifier &&
|
|
86
80
|
(awaitParent.property.name === 'header' || awaitParent.property.name === 'headers')
|
|
87
81
|
) {
|
|
88
82
|
results.inlineHeadersReference = awaitParent;
|
|
89
83
|
}
|
|
90
|
-
} else if (enclosingStatement.type ===
|
|
84
|
+
} else if (enclosingStatement.type === AST_NODE_TYPES.VariableDeclaration) {
|
|
91
85
|
results.variableDeclaration = enclosingStatement;
|
|
92
86
|
results.rootNode = enclosingStatement;
|
|
93
87
|
} else if (
|
|
94
|
-
enclosingStatement.type ===
|
|
95
|
-
enclosingStatement.expression.type ===
|
|
88
|
+
enclosingStatement.type === AST_NODE_TYPES.ExpressionStatement &&
|
|
89
|
+
enclosingStatement.expression.type === AST_NODE_TYPES.AssignmentExpression
|
|
96
90
|
) {
|
|
97
91
|
results.variableAssignment = enclosingStatement;
|
|
98
92
|
results.rootNode = enclosingStatement;
|
|
99
93
|
} else {
|
|
100
94
|
results.rootNode = parent;
|
|
101
95
|
}
|
|
102
|
-
} else if (parent.type ===
|
|
96
|
+
} else if (parent.type === AST_NODE_TYPES.MemberExpression && parent.property.type === AST_NODE_TYPES.Identifier) {
|
|
103
97
|
if (parent.property.name === 'expect') {
|
|
104
98
|
// supertest assertions
|
|
105
99
|
const assertionCall = getParent(parent);
|
|
106
|
-
assert.ok(assertionCall && assertionCall.type ===
|
|
107
|
-
results.assertions = [...(results.assertions ?? []), assertionCall.arguments as Expression[]];
|
|
100
|
+
assert.ok(assertionCall && assertionCall.type === AST_NODE_TYPES.CallExpression);
|
|
101
|
+
results.assertions = [...(results.assertions ?? []), assertionCall.arguments as TSESTree.Expression[]];
|
|
108
102
|
nextCall = assertionCall;
|
|
109
103
|
} else if (parent.property.name === 'send') {
|
|
110
104
|
// request body
|
|
111
105
|
const sendRequestBodyCall = getParent(parent);
|
|
112
|
-
assert.ok(sendRequestBodyCall && sendRequestBodyCall.type ===
|
|
113
|
-
results.requestBody = sendRequestBodyCall.arguments[0] as Expression;
|
|
106
|
+
assert.ok(sendRequestBodyCall && sendRequestBodyCall.type === AST_NODE_TYPES.CallExpression);
|
|
107
|
+
results.requestBody = sendRequestBodyCall.arguments[0] as TSESTree.Expression;
|
|
114
108
|
nextCall = sendRequestBodyCall;
|
|
115
109
|
} else if (parent.property.name === 'set') {
|
|
116
110
|
// request headers
|
|
117
111
|
const setRequestHeaderCall = getParent(parent);
|
|
118
|
-
assert.ok(setRequestHeaderCall && setRequestHeaderCall.type ===
|
|
119
|
-
const [arg1, arg2] = setRequestHeaderCall.arguments as [Expression, Expression];
|
|
120
|
-
if (arg1.type ===
|
|
112
|
+
assert.ok(setRequestHeaderCall && setRequestHeaderCall.type === AST_NODE_TYPES.CallExpression);
|
|
113
|
+
const [arg1, arg2] = setRequestHeaderCall.arguments as [TSESTree.Expression, TSESTree.Expression];
|
|
114
|
+
if (arg1.type === AST_NODE_TYPES.ObjectExpression) {
|
|
121
115
|
results.requestHeadersObjectLiteral = arg1;
|
|
122
116
|
} else {
|
|
123
117
|
results.requestHeaders = [...(results.requestHeaders ?? []), { name: arg1, value: arg2 }];
|
|
@@ -137,7 +131,7 @@ function createResponseAssertions(
|
|
|
137
131
|
fixtureCallInformation: FixtureCallInformation,
|
|
138
132
|
sourceCode: SourceCode,
|
|
139
133
|
responseVariableName: string,
|
|
140
|
-
destructuringResponseHeadersVariable:
|
|
134
|
+
destructuringResponseHeadersVariable: Variable | undefined,
|
|
141
135
|
) {
|
|
142
136
|
let statusAssertion: string | undefined;
|
|
143
137
|
const nonStatusAssertions: string[] = [];
|
|
@@ -146,20 +140,20 @@ function createResponseAssertions(
|
|
|
146
140
|
const [assertionArgument] = expectArguments;
|
|
147
141
|
assert.ok(assertionArgument);
|
|
148
142
|
if (
|
|
149
|
-
(assertionArgument.type ===
|
|
150
|
-
assertionArgument.object.type ===
|
|
143
|
+
(assertionArgument.type === AST_NODE_TYPES.MemberExpression &&
|
|
144
|
+
assertionArgument.object.type === AST_NODE_TYPES.Identifier &&
|
|
151
145
|
assertionArgument.object.name === 'StatusCodes') ||
|
|
152
|
-
assertionArgument.type ===
|
|
146
|
+
assertionArgument.type === AST_NODE_TYPES.Literal ||
|
|
153
147
|
sourceCode.getText(assertionArgument).includes('StatusCodes.')
|
|
154
148
|
) {
|
|
155
149
|
// status code assertion
|
|
156
150
|
statusAssertion = `assert.equal(${responseVariableName}.status, ${sourceCode.getText(assertionArgument)})`;
|
|
157
|
-
} else if (assertionArgument.type ===
|
|
151
|
+
} else if (assertionArgument.type === AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
158
152
|
// callback assertion using arrow function
|
|
159
153
|
let functionBody = sourceCode.getText(assertionArgument.body);
|
|
160
154
|
|
|
161
155
|
const [originalResponseArgument] = assertionArgument.params;
|
|
162
|
-
assert.ok(originalResponseArgument?.type ===
|
|
156
|
+
assert.ok(originalResponseArgument?.type === AST_NODE_TYPES.Identifier);
|
|
163
157
|
const originalResponseArgumentName = originalResponseArgument.name;
|
|
164
158
|
if (originalResponseArgumentName !== responseVariableName) {
|
|
165
159
|
functionBody = functionBody.replace(
|
|
@@ -168,12 +162,15 @@ function createResponseAssertions(
|
|
|
168
162
|
);
|
|
169
163
|
}
|
|
170
164
|
nonStatusAssertions.push(`assert.doesNotThrow(()=>${functionBody})`);
|
|
171
|
-
} else if (assertionArgument.type ===
|
|
165
|
+
} else if (assertionArgument.type === AST_NODE_TYPES.Identifier) {
|
|
172
166
|
// callback assertion using function reference
|
|
173
167
|
nonStatusAssertions.push(
|
|
174
168
|
`assert.doesNotThrow(()=>${sourceCode.getText(assertionArgument)}(${responseVariableName}))`,
|
|
175
169
|
);
|
|
176
|
-
} else if (
|
|
170
|
+
} else if (
|
|
171
|
+
assertionArgument.type === AST_NODE_TYPES.ObjectExpression ||
|
|
172
|
+
assertionArgument.type === AST_NODE_TYPES.CallExpression
|
|
173
|
+
) {
|
|
177
174
|
// body deep equal assertion
|
|
178
175
|
nonStatusAssertions.push(
|
|
179
176
|
`assert.deepEqual(await ${responseVariableName}.json(), ${sourceCode.getText(assertionArgument)})`,
|
|
@@ -189,7 +186,7 @@ function createResponseAssertions(
|
|
|
189
186
|
destructuringResponseHeadersVariable !== undefined
|
|
190
187
|
? destructuringResponseHeadersVariable.name
|
|
191
188
|
: `${responseVariableName}.headers`;
|
|
192
|
-
if (headerValue.type ===
|
|
189
|
+
if (headerValue.type === AST_NODE_TYPES.Literal && headerValue.value instanceof RegExp) {
|
|
193
190
|
nonStatusAssertions.push(
|
|
194
191
|
`assert.ok(${headersReference}.get(${sourceCode.getText(headerName)}).match(${sourceCode.getText(headerValue)}))`,
|
|
195
192
|
);
|
|
@@ -207,21 +204,22 @@ function createResponseAssertions(
|
|
|
207
204
|
}
|
|
208
205
|
|
|
209
206
|
function getResponseVariableNameToUse(
|
|
210
|
-
scopeManager:
|
|
207
|
+
scopeManager: ScopeManager,
|
|
211
208
|
fixtureCallInformation: FixtureCallInformation,
|
|
212
|
-
scopeVariablesMap: Map<Scope
|
|
209
|
+
scopeVariablesMap: Map<Scope, string[]>,
|
|
213
210
|
) {
|
|
214
211
|
if (fixtureCallInformation.variableAssignment) {
|
|
215
212
|
assert.ok(
|
|
216
|
-
fixtureCallInformation.variableAssignment.expression.type ===
|
|
217
|
-
fixtureCallInformation.variableAssignment.expression.left.type ===
|
|
213
|
+
fixtureCallInformation.variableAssignment.expression.type === AST_NODE_TYPES.AssignmentExpression &&
|
|
214
|
+
fixtureCallInformation.variableAssignment.expression.left.type === AST_NODE_TYPES.Identifier,
|
|
218
215
|
);
|
|
219
216
|
return fixtureCallInformation.variableAssignment.expression.left.name;
|
|
220
217
|
}
|
|
221
218
|
|
|
222
219
|
if (fixtureCallInformation.variableDeclaration) {
|
|
223
220
|
const firstDeclaration = fixtureCallInformation.variableDeclaration.declarations[0];
|
|
224
|
-
|
|
221
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
222
|
+
if (firstDeclaration !== undefined && firstDeclaration.id.type === AST_NODE_TYPES.Identifier) {
|
|
225
223
|
return firstDeclaration.id.name;
|
|
226
224
|
}
|
|
227
225
|
}
|
|
@@ -250,12 +248,15 @@ function getResponseVariableNameToUse(
|
|
|
250
248
|
return responseVariableNameToUse;
|
|
251
249
|
}
|
|
252
250
|
|
|
253
|
-
function isResponseBodyRedefinition(responseBodyReference: MemberExpression): boolean {
|
|
251
|
+
function isResponseBodyRedefinition(responseBodyReference: TSESTree.MemberExpression): boolean {
|
|
254
252
|
const parent = getParent(responseBodyReference);
|
|
255
|
-
return parent?.type ===
|
|
253
|
+
return parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier;
|
|
256
254
|
}
|
|
257
255
|
|
|
258
|
-
const
|
|
256
|
+
const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
|
|
257
|
+
|
|
258
|
+
const rule: ESLintUtils.RuleModule<'unknownError' | 'preferNativeFetch'> = createRule({
|
|
259
|
+
name: ruleId,
|
|
259
260
|
meta: {
|
|
260
261
|
type: 'suggestion',
|
|
261
262
|
docs: {
|
|
@@ -269,23 +270,25 @@ const rule: Rule.RuleModule = {
|
|
|
269
270
|
fixable: 'code',
|
|
270
271
|
schema: [],
|
|
271
272
|
},
|
|
273
|
+
defaultOptions: [],
|
|
272
274
|
// eslint-disable-next-line max-lines-per-function
|
|
273
275
|
create(context) {
|
|
274
276
|
const sourceCode = context.sourceCode;
|
|
275
277
|
const scopeManager = sourceCode.scopeManager;
|
|
276
|
-
|
|
278
|
+
assert.ok(scopeManager !== null);
|
|
279
|
+
const scopeVariablesMap = new Map<Scope, string[]>();
|
|
277
280
|
|
|
278
281
|
return {
|
|
279
282
|
// eslint-disable-next-line max-lines-per-function
|
|
280
283
|
'CallExpression[callee.property.name="expect"]': (
|
|
281
|
-
supertestCall: CallExpression,
|
|
284
|
+
supertestCall: TSESTree.CallExpression,
|
|
282
285
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
283
286
|
) => {
|
|
284
|
-
assert.ok(supertestCall.callee.type ===
|
|
287
|
+
assert.ok(supertestCall.callee.type === AST_NODE_TYPES.MemberExpression);
|
|
285
288
|
if (
|
|
286
|
-
supertestCall.callee.object.type ===
|
|
287
|
-
supertestCall.callee.object.callee.type ===
|
|
288
|
-
supertestCall.callee.object.callee.property.type ===
|
|
289
|
+
supertestCall.callee.object.type === AST_NODE_TYPES.CallExpression &&
|
|
290
|
+
supertestCall.callee.object.callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
291
|
+
supertestCall.callee.object.callee.property.type === AST_NODE_TYPES.Identifier &&
|
|
289
292
|
supertestCall.callee.object.callee.property.name === 'expect'
|
|
290
293
|
) {
|
|
291
294
|
// skip nested expect calls, only focus on the top level
|
|
@@ -297,9 +300,8 @@ const rule: Rule.RuleModule = {
|
|
|
297
300
|
return;
|
|
298
301
|
}
|
|
299
302
|
|
|
300
|
-
assert.ok(supertestCall.type === 'CallExpression');
|
|
301
303
|
const fixtureFunction = supertestCall.callee.object;
|
|
302
|
-
if (fixtureFunction.type !==
|
|
304
|
+
if (fixtureFunction.type !== AST_NODE_TYPES.CallExpression) {
|
|
303
305
|
return;
|
|
304
306
|
}
|
|
305
307
|
|
|
@@ -335,7 +337,7 @@ const rule: Rule.RuleModule = {
|
|
|
335
337
|
const isResponseHeadersVariableRedefinitionNeeded =
|
|
336
338
|
(destructuringResponseHeadersVariable !== undefined &&
|
|
337
339
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
338
|
-
(destructuringResponseHeadersVariable as ObjectPattern).type ===
|
|
340
|
+
(destructuringResponseHeadersVariable as TSESTree.ObjectPattern).type === AST_NODE_TYPES.ObjectPattern) ||
|
|
339
341
|
fixtureCallInformation.inlineHeadersReference !== undefined;
|
|
340
342
|
const redefineResponseHeadersVariableName = `${responseVariableNameToUse}Headers`;
|
|
341
343
|
|
|
@@ -352,7 +354,7 @@ const rule: Rule.RuleModule = {
|
|
|
352
354
|
...(destructuringResponseBodyVariable
|
|
353
355
|
? [
|
|
354
356
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
355
|
-
`${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseBodyVariable as ObjectPattern).type ===
|
|
357
|
+
`${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)}`,
|
|
356
358
|
]
|
|
357
359
|
: isResponseBodyVariableRedefinitionNeeded
|
|
358
360
|
? [
|
|
@@ -362,15 +364,16 @@ const rule: Rule.RuleModule = {
|
|
|
362
364
|
// eslint-disable-next-line no-nested-ternary
|
|
363
365
|
...(destructuringResponseHeadersVariable
|
|
364
366
|
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
365
|
-
(destructuringResponseHeadersVariable as ObjectPattern).type ===
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
assert.
|
|
367
|
+
(destructuringResponseHeadersVariable as TSESTree.ObjectPattern).type ===
|
|
368
|
+
AST_NODE_TYPES.ObjectPattern
|
|
369
|
+
? (destructuringResponseHeadersVariable as TSESTree.ObjectPattern).properties.map((property) => {
|
|
370
|
+
assert.ok(property.type === AST_NODE_TYPES.Property);
|
|
371
|
+
assert.ok(property.value.type === AST_NODE_TYPES.Identifier);
|
|
369
372
|
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
370
|
-
return `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${property.value.name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}.get(${property.key.type ===
|
|
373
|
+
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)}'`})`;
|
|
371
374
|
})
|
|
372
375
|
: [
|
|
373
|
-
`${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseHeadersVariable as
|
|
376
|
+
`${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseHeadersVariable as Variable).name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}`,
|
|
374
377
|
]
|
|
375
378
|
: isResponseHeadersVariableRedefinitionNeeded
|
|
376
379
|
? [
|
|
@@ -384,7 +387,7 @@ const rule: Rule.RuleModule = {
|
|
|
384
387
|
fixtureCallInformation,
|
|
385
388
|
sourceCode,
|
|
386
389
|
responseVariableNameToUse,
|
|
387
|
-
destructuringResponseHeadersVariable as
|
|
390
|
+
destructuringResponseHeadersVariable as Variable | undefined,
|
|
388
391
|
);
|
|
389
392
|
|
|
390
393
|
// add variable declaration if needed
|
|
@@ -442,12 +445,12 @@ const rule: Rule.RuleModule = {
|
|
|
442
445
|
const parent = getParent(responseHeadersReference);
|
|
443
446
|
assert.ok(parent);
|
|
444
447
|
let headerName;
|
|
445
|
-
if (parent.type ===
|
|
448
|
+
if (parent.type === AST_NODE_TYPES.MemberExpression) {
|
|
446
449
|
const headerNameNode = parent.property;
|
|
447
450
|
headerName = parent.computed
|
|
448
451
|
? sourceCode.getText(headerNameNode)
|
|
449
452
|
: `'${sourceCode.getText(headerNameNode)}'`;
|
|
450
|
-
} else if (parent.type ===
|
|
453
|
+
} else if (parent.type === AST_NODE_TYPES.CallExpression) {
|
|
451
454
|
const headerNameNode = parent.arguments[0];
|
|
452
455
|
headerName = sourceCode.getText(headerNameNode);
|
|
453
456
|
}
|
|
@@ -458,7 +461,7 @@ const rule: Rule.RuleModule = {
|
|
|
458
461
|
// convert response.statusCode to response.status
|
|
459
462
|
for (const responseStatusReference of responseStatusReferences) {
|
|
460
463
|
if (
|
|
461
|
-
responseStatusReference.property.type ===
|
|
464
|
+
responseStatusReference.property.type === AST_NODE_TYPES.Identifier &&
|
|
462
465
|
responseStatusReference.property.name === 'statusCode'
|
|
463
466
|
) {
|
|
464
467
|
yield fixer.replaceText(responseStatusReference.property, `status`);
|
|
@@ -467,7 +470,7 @@ const rule: Rule.RuleModule = {
|
|
|
467
470
|
|
|
468
471
|
// handle direct return statement without await, e.g. "return fixture.api.get(...);"
|
|
469
472
|
if (
|
|
470
|
-
fixtureCallInformation.rootNode.type ===
|
|
473
|
+
fixtureCallInformation.rootNode.type === AST_NODE_TYPES.ReturnStatement &&
|
|
471
474
|
fixtureCallInformation.assertions !== undefined
|
|
472
475
|
) {
|
|
473
476
|
yield fixer.insertTextAfter(
|
|
@@ -492,6 +495,6 @@ const rule: Rule.RuleModule = {
|
|
|
492
495
|
},
|
|
493
496
|
};
|
|
494
497
|
},
|
|
495
|
-
};
|
|
498
|
+
});
|
|
496
499
|
|
|
497
500
|
export default rule;
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { strict as assert } from 'node:assert';
|
|
10
|
-
|
|
11
|
-
import { type Scope } from 'eslint';
|
|
10
|
+
|
|
12
11
|
import debug from 'debug';
|
|
13
12
|
|
|
14
|
-
import {
|
|
13
|
+
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
|
|
14
|
+
import type { ScopeManager, Variable } from '@typescript-eslint/scope-manager';
|
|
15
|
+
import { getParent } from '../library/ts-tree';
|
|
15
16
|
|
|
16
17
|
const log = debug('eslint-plugin:response-reference');
|
|
17
18
|
|
|
@@ -21,25 +22,25 @@ const log = debug('eslint-plugin:response-reference');
|
|
|
21
22
|
* @param variableDeclaration - variable declaration node
|
|
22
23
|
*/
|
|
23
24
|
export function analyzeResponseReferences(
|
|
24
|
-
variableDeclaration: VariableDeclaration | undefined,
|
|
25
|
-
scopeManager:
|
|
25
|
+
variableDeclaration: TSESTree.VariableDeclaration | undefined,
|
|
26
|
+
scopeManager: ScopeManager,
|
|
26
27
|
): {
|
|
27
|
-
variable?:
|
|
28
|
-
bodyReferences: MemberExpression[];
|
|
29
|
-
headersReferences: MemberExpression[];
|
|
30
|
-
statusReferences: MemberExpression[];
|
|
31
|
-
destructuringBodyVariable?:
|
|
32
|
-
destructuringHeadersVariable?:
|
|
33
|
-
destructuringHeadersReferences?: MemberExpression[] | undefined;
|
|
28
|
+
variable?: Variable;
|
|
29
|
+
bodyReferences: TSESTree.MemberExpression[];
|
|
30
|
+
headersReferences: TSESTree.MemberExpression[];
|
|
31
|
+
statusReferences: TSESTree.MemberExpression[];
|
|
32
|
+
destructuringBodyVariable?: Variable | TSESTree.ObjectPattern;
|
|
33
|
+
destructuringHeadersVariable?: Variable | TSESTree.ObjectPattern;
|
|
34
|
+
destructuringHeadersReferences?: TSESTree.MemberExpression[] | undefined;
|
|
34
35
|
} {
|
|
35
36
|
const results: {
|
|
36
|
-
variable?:
|
|
37
|
-
bodyReferences: MemberExpression[];
|
|
38
|
-
headersReferences: MemberExpression[];
|
|
39
|
-
statusReferences: MemberExpression[];
|
|
40
|
-
destructuringBodyVariable?:
|
|
41
|
-
destructuringHeadersVariable?:
|
|
42
|
-
destructuringHeadersReferences?: MemberExpression[] | undefined;
|
|
37
|
+
variable?: Variable;
|
|
38
|
+
bodyReferences: TSESTree.MemberExpression[];
|
|
39
|
+
headersReferences: TSESTree.MemberExpression[];
|
|
40
|
+
statusReferences: TSESTree.MemberExpression[];
|
|
41
|
+
destructuringBodyVariable?: Variable | TSESTree.ObjectPattern;
|
|
42
|
+
destructuringHeadersVariable?: Variable | TSESTree.ObjectPattern;
|
|
43
|
+
destructuringHeadersReferences?: TSESTree.MemberExpression[] | undefined;
|
|
43
44
|
} = {
|
|
44
45
|
bodyReferences: [],
|
|
45
46
|
headersReferences: [],
|
|
@@ -55,7 +56,7 @@ export function analyzeResponseReferences(
|
|
|
55
56
|
assert.ok(identifier);
|
|
56
57
|
const identifierParent = getParent(identifier);
|
|
57
58
|
assert.ok(identifierParent);
|
|
58
|
-
if (identifierParent.type ===
|
|
59
|
+
if (identifierParent.type === AST_NODE_TYPES.VariableDeclarator) {
|
|
59
60
|
// e.g. const response = ...
|
|
60
61
|
results.variable = responseVariable;
|
|
61
62
|
const responseReferences = responseVariable.references.map((responseReference) =>
|
|
@@ -63,34 +64,36 @@ export function analyzeResponseReferences(
|
|
|
63
64
|
);
|
|
64
65
|
// e.g. response.body
|
|
65
66
|
results.bodyReferences = responseReferences.filter(
|
|
66
|
-
(node): node is MemberExpression =>
|
|
67
|
-
node?.type ===
|
|
67
|
+
(node): node is TSESTree.MemberExpression =>
|
|
68
|
+
node?.type === AST_NODE_TYPES.MemberExpression &&
|
|
69
|
+
node.property.type === AST_NODE_TYPES.Identifier &&
|
|
70
|
+
node.property.name === 'body',
|
|
68
71
|
);
|
|
69
72
|
// e.g. response.headers / response.header / response.get()
|
|
70
73
|
results.headersReferences = responseReferences.filter(
|
|
71
|
-
(node): node is MemberExpression =>
|
|
72
|
-
node?.type ===
|
|
73
|
-
node.property.type ===
|
|
74
|
+
(node): node is TSESTree.MemberExpression =>
|
|
75
|
+
node?.type === AST_NODE_TYPES.MemberExpression &&
|
|
76
|
+
node.property.type === AST_NODE_TYPES.Identifier &&
|
|
74
77
|
(node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
|
|
75
78
|
);
|
|
76
79
|
// e.g. response.status / response.statusCode
|
|
77
80
|
results.statusReferences = responseReferences.filter(
|
|
78
|
-
(node): node is MemberExpression =>
|
|
79
|
-
node?.type ===
|
|
80
|
-
node.property.type ===
|
|
81
|
+
(node): node is TSESTree.MemberExpression =>
|
|
82
|
+
node?.type === AST_NODE_TYPES.MemberExpression &&
|
|
83
|
+
node.property.type === AST_NODE_TYPES.Identifier &&
|
|
81
84
|
(node.property.name === 'status' || node.property.name === 'statusCode'),
|
|
82
85
|
);
|
|
83
86
|
} else if (
|
|
84
87
|
// body reference through destruction/renaming, e.g. "const { body } = ..."
|
|
85
|
-
identifierParent.type ===
|
|
86
|
-
identifierParent.key.type ===
|
|
88
|
+
identifierParent.type === AST_NODE_TYPES.Property &&
|
|
89
|
+
identifierParent.key.type === AST_NODE_TYPES.Identifier &&
|
|
87
90
|
identifierParent.key.name === 'body'
|
|
88
91
|
) {
|
|
89
92
|
results.destructuringBodyVariable = responseVariable;
|
|
90
93
|
} else if (
|
|
91
94
|
// header reference through destruction/renaming, e.g. "const { headers } = ..."
|
|
92
|
-
identifierParent.type ===
|
|
93
|
-
identifierParent.key.type ===
|
|
95
|
+
identifierParent.type === AST_NODE_TYPES.Property &&
|
|
96
|
+
identifierParent.key.type === AST_NODE_TYPES.Identifier &&
|
|
94
97
|
identifierParent.key.name === 'headers'
|
|
95
98
|
) {
|
|
96
99
|
results.destructuringHeadersVariable = responseVariable;
|
|
@@ -98,23 +101,27 @@ export function analyzeResponseReferences(
|
|
|
98
101
|
.map((reference) => reference.identifier)
|
|
99
102
|
.map(getParent)
|
|
100
103
|
.filter(
|
|
101
|
-
(parent): parent is MemberExpression =>
|
|
102
|
-
parent?.type ===
|
|
103
|
-
parent.property.type ===
|
|
104
|
+
(parent): parent is TSESTree.MemberExpression =>
|
|
105
|
+
parent?.type === AST_NODE_TYPES.MemberExpression &&
|
|
106
|
+
parent.property.type === AST_NODE_TYPES.Identifier &&
|
|
104
107
|
parent.property.name !== 'get' &&
|
|
105
|
-
getParent(parent)?.type !==
|
|
108
|
+
getParent(parent)?.type !== AST_NODE_TYPES.CallExpression,
|
|
106
109
|
);
|
|
107
|
-
} else if (identifierParent.type ===
|
|
110
|
+
} else if (identifierParent.type === AST_NODE_TYPES.Property) {
|
|
108
111
|
const parent = getParent(identifierParent);
|
|
109
|
-
if (parent?.type ===
|
|
112
|
+
if (parent?.type === AST_NODE_TYPES.ObjectPattern) {
|
|
110
113
|
// body reference through nested destruction, e.g. "const { body: {bodyPropertyName: renamedBodyPropertyName}, headers: {headerPropertyName: renamedHeaderPropertyName} } = ..."
|
|
111
114
|
const parent2 = getParent(parent);
|
|
112
|
-
if (
|
|
115
|
+
if (
|
|
116
|
+
parent2?.type === AST_NODE_TYPES.Property &&
|
|
117
|
+
parent2.key.type === AST_NODE_TYPES.Identifier &&
|
|
118
|
+
parent2.key.name === 'body'
|
|
119
|
+
) {
|
|
113
120
|
results.destructuringBodyVariable = parent;
|
|
114
121
|
}
|
|
115
122
|
if (
|
|
116
|
-
parent2?.type ===
|
|
117
|
-
parent2.key.type ===
|
|
123
|
+
parent2?.type === AST_NODE_TYPES.Property &&
|
|
124
|
+
parent2.key.type === AST_NODE_TYPES.Identifier &&
|
|
118
125
|
(parent2.key.name === 'header' || parent2.key.name === 'headers')
|
|
119
126
|
) {
|
|
120
127
|
results.destructuringHeadersVariable = parent;
|