@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.
@@ -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
- rootNode:
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
- assertions?: TSESTree.Expression[][];
50
- inlineStatementNode?: TSESTree.Node;
51
- inlineBodyReference?: TSESTree.MemberExpression;
52
- inlineStatusReference?: TSESTree.MemberExpression;
53
- inlineHeadersReference?: TSESTree.MemberExpression;
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
- let nextCall;
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
- results.assertions = [...(results.assertions ?? []), assertionCall.arguments as TSESTree.Expression[]];
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
- // eslint-disable-next-line sonarjs/cognitive-complexity
144
- function createResponseAssertions(
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 = !isResponseVariableRedefinitionNeeded
477
- ? fetchCallText
478
- : `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${responseVariableNameToUse} = await ${fetchCallText}`;
479
-
480
- const nodeToReplace = isResponseVariableRedefinitionNeeded
481
- ? fixtureCallInformation.rootNode
482
- : fixtureCallInformation.fixtureNode;
483
- const appendingAssignmentAndAssertionText = [
484
- '',
485
- ...(statusAssertion !== undefined ? [statusAssertion] : []),
486
- ...responseBodyHeadersVariableRedefineLines,
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: fixtureCall,
180
+ node: fixtureCallInformation.fixtureNode,
492
181
  messageId: 'preferNativeFetch',
493
-
494
- *fix(fixer) {
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
- (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
- );
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