@checkdigit/eslint-plugin 7.6.0-PR.75-1b09 → 7.6.0-PR.75-c06b
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 +45 -47
- package/dist-mjs/agent/response-reference.mjs +14 -14
- package/dist-mjs/agent/supertest-then.mjs +170 -0
- package/dist-mjs/index.mjs +7 -2
- 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/dist-types/agent/supertest-then.d.ts +4 -0
- 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 +94 -96
- package/src/agent/response-reference.ts +48 -41
- package/src/agent/supertest-then.ts +322 -0
- package/src/index.ts +5 -0
|
@@ -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,46 +270,42 @@ 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 === 'MemberExpression');
|
|
285
|
-
if (
|
|
286
|
-
supertestCall.callee.object.type === 'CallExpression' &&
|
|
287
|
-
supertestCall.callee.object.callee.type === 'MemberExpression' &&
|
|
288
|
-
supertestCall.callee.object.callee.property.type === 'Identifier' &&
|
|
289
|
-
supertestCall.callee.object.callee.property.name === 'expect'
|
|
290
|
-
) {
|
|
291
|
-
// skip nested expect calls, only focus on the top level
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
287
|
try {
|
|
295
|
-
|
|
296
|
-
|
|
288
|
+
assert.ok(supertestCall.callee.type === AST_NODE_TYPES.MemberExpression);
|
|
289
|
+
if (
|
|
290
|
+
supertestCall.callee.object.type !== AST_NODE_TYPES.CallExpression ||
|
|
291
|
+
(supertestCall.callee.object.callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
292
|
+
supertestCall.callee.object.callee.property.type === AST_NODE_TYPES.Identifier &&
|
|
293
|
+
supertestCall.callee.object.callee.property.name === 'expect')
|
|
294
|
+
) {
|
|
295
|
+
// skip nested expect calls, only focus on the top level
|
|
297
296
|
return;
|
|
298
297
|
}
|
|
299
298
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (fixtureFunction.type !== 'CallExpression') {
|
|
299
|
+
if (isUsedInArrayOrAsArgument(supertestCall) || getEnclosingFunction(supertestCall)?.async === false) {
|
|
300
|
+
// skip and leave it to "fetch-then" rule to handle it because no "await" can be used here
|
|
303
301
|
return;
|
|
304
302
|
}
|
|
305
303
|
|
|
306
304
|
const indentation = getIndentation(supertestCall, sourceCode);
|
|
307
305
|
|
|
308
306
|
const fixtureCallInformation = {} as FixtureCallInformation;
|
|
307
|
+
const fixtureFunction = supertestCall.callee.object;
|
|
309
308
|
analyzeFixtureCall(fixtureFunction, fixtureCallInformation, sourceCode);
|
|
310
|
-
sourceCode.getText(fixtureCallInformation.fixtureNode);
|
|
311
|
-
sourceCode.getText(fixtureCallInformation.rootNode);
|
|
312
309
|
fixtureCallInformation.assertions?.flat().map((ass) => sourceCode.getText(ass));
|
|
313
310
|
|
|
314
311
|
const {
|
|
@@ -335,7 +332,7 @@ const rule: Rule.RuleModule = {
|
|
|
335
332
|
const isResponseHeadersVariableRedefinitionNeeded =
|
|
336
333
|
(destructuringResponseHeadersVariable !== undefined &&
|
|
337
334
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
338
|
-
(destructuringResponseHeadersVariable as ObjectPattern).type ===
|
|
335
|
+
(destructuringResponseHeadersVariable as TSESTree.ObjectPattern).type === AST_NODE_TYPES.ObjectPattern) ||
|
|
339
336
|
fixtureCallInformation.inlineHeadersReference !== undefined;
|
|
340
337
|
const redefineResponseHeadersVariableName = `${responseVariableNameToUse}Headers`;
|
|
341
338
|
|
|
@@ -352,7 +349,7 @@ const rule: Rule.RuleModule = {
|
|
|
352
349
|
...(destructuringResponseBodyVariable
|
|
353
350
|
? [
|
|
354
351
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
355
|
-
`${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseBodyVariable as ObjectPattern).type ===
|
|
352
|
+
`${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
353
|
]
|
|
357
354
|
: isResponseBodyVariableRedefinitionNeeded
|
|
358
355
|
? [
|
|
@@ -362,15 +359,16 @@ const rule: Rule.RuleModule = {
|
|
|
362
359
|
// eslint-disable-next-line no-nested-ternary
|
|
363
360
|
...(destructuringResponseHeadersVariable
|
|
364
361
|
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
365
|
-
(destructuringResponseHeadersVariable as ObjectPattern).type ===
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
assert.
|
|
362
|
+
(destructuringResponseHeadersVariable as TSESTree.ObjectPattern).type ===
|
|
363
|
+
AST_NODE_TYPES.ObjectPattern
|
|
364
|
+
? (destructuringResponseHeadersVariable as TSESTree.ObjectPattern).properties.map((property) => {
|
|
365
|
+
assert.ok(property.type === AST_NODE_TYPES.Property);
|
|
366
|
+
assert.ok(property.value.type === AST_NODE_TYPES.Identifier);
|
|
369
367
|
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
370
|
-
return `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${property.value.name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}.get(${property.key.type ===
|
|
368
|
+
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
369
|
})
|
|
372
370
|
: [
|
|
373
|
-
`${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseHeadersVariable as
|
|
371
|
+
`${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseHeadersVariable as Variable).name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}`,
|
|
374
372
|
]
|
|
375
373
|
: isResponseHeadersVariableRedefinitionNeeded
|
|
376
374
|
? [
|
|
@@ -384,7 +382,7 @@ const rule: Rule.RuleModule = {
|
|
|
384
382
|
fixtureCallInformation,
|
|
385
383
|
sourceCode,
|
|
386
384
|
responseVariableNameToUse,
|
|
387
|
-
destructuringResponseHeadersVariable as
|
|
385
|
+
destructuringResponseHeadersVariable as Variable | undefined,
|
|
388
386
|
);
|
|
389
387
|
|
|
390
388
|
// add variable declaration if needed
|
|
@@ -442,12 +440,12 @@ const rule: Rule.RuleModule = {
|
|
|
442
440
|
const parent = getParent(responseHeadersReference);
|
|
443
441
|
assert.ok(parent);
|
|
444
442
|
let headerName;
|
|
445
|
-
if (parent.type ===
|
|
443
|
+
if (parent.type === AST_NODE_TYPES.MemberExpression) {
|
|
446
444
|
const headerNameNode = parent.property;
|
|
447
445
|
headerName = parent.computed
|
|
448
446
|
? sourceCode.getText(headerNameNode)
|
|
449
447
|
: `'${sourceCode.getText(headerNameNode)}'`;
|
|
450
|
-
} else if (parent.type ===
|
|
448
|
+
} else if (parent.type === AST_NODE_TYPES.CallExpression) {
|
|
451
449
|
const headerNameNode = parent.arguments[0];
|
|
452
450
|
headerName = sourceCode.getText(headerNameNode);
|
|
453
451
|
}
|
|
@@ -458,7 +456,7 @@ const rule: Rule.RuleModule = {
|
|
|
458
456
|
// convert response.statusCode to response.status
|
|
459
457
|
for (const responseStatusReference of responseStatusReferences) {
|
|
460
458
|
if (
|
|
461
|
-
responseStatusReference.property.type ===
|
|
459
|
+
responseStatusReference.property.type === AST_NODE_TYPES.Identifier &&
|
|
462
460
|
responseStatusReference.property.name === 'statusCode'
|
|
463
461
|
) {
|
|
464
462
|
yield fixer.replaceText(responseStatusReference.property, `status`);
|
|
@@ -467,7 +465,7 @@ const rule: Rule.RuleModule = {
|
|
|
467
465
|
|
|
468
466
|
// handle direct return statement without await, e.g. "return fixture.api.get(...);"
|
|
469
467
|
if (
|
|
470
|
-
fixtureCallInformation.rootNode.type ===
|
|
468
|
+
fixtureCallInformation.rootNode.type === AST_NODE_TYPES.ReturnStatement &&
|
|
471
469
|
fixtureCallInformation.assertions !== undefined
|
|
472
470
|
) {
|
|
473
471
|
yield fixer.insertTextAfter(
|
|
@@ -492,6 +490,6 @@ const rule: Rule.RuleModule = {
|
|
|
492
490
|
},
|
|
493
491
|
};
|
|
494
492
|
},
|
|
495
|
-
};
|
|
493
|
+
});
|
|
496
494
|
|
|
497
495
|
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;
|