@checkdigit/eslint-plugin 7.6.0-PR.75-4751 → 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.filter((part) => part !== 'tenant'), 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) {
@@ -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
package/src/index.ts CHANGED
@@ -15,15 +15,15 @@ import fetchResponseHeaderGetter, {
15
15
  ruleId as fetchResponseHeaderGetterRuleId,
16
16
  } from './agent/fetch-response-header-getter';
17
17
  import fetchResponseStatus, { ruleId as fetchResponseStatusRuleId } from './agent/fetch-response-status';
18
- import fetchThen, { ruleId as fetchThenRuleId } from './agent/fetch-then';
18
+ // import fetchThen, { ruleId as fetchThenRuleId } from './agent/fetch-then';
19
19
  import fixFunctionCallArguments, {
20
20
  ruleId as fixFunctionCallArgumentsRuleId,
21
21
  } from './agent/fix-function-call-arguments';
22
22
  import invalidJsonStringify, { ruleId as invalidJsonStringifyRuleId } from './invalid-json-stringify';
23
23
  import noDuplicatedImports, { ruleId as noDuplicatedImportsRuleId } from './no-duplicated-imports';
24
24
  import noFixture, { ruleId as noFixtureRuleId } from './agent/no-fixture';
25
- import noSupertest, { ruleId as noSupertestRuleId } from './agent/no-supertest';
26
- import supertestThen, { ruleId as supertestThenRuleId } from './agent/supertest-then';
25
+ import noExpectAssertion, { ruleId as noExpectAssertionRuleId } from './agent/no-expect-assertion';
26
+ // import supertestThen, { ruleId as supertestThenRuleId } from './agent/supertest-then';
27
27
  import noLegacyServiceTyping, { ruleId as noLegacyServiceTypingRuleId } from './no-legacy-service-typing';
28
28
  import noMappedResponse, { ruleId as noMappedResponseRuleId } from './agent/no-mapped-response';
29
29
  import noPromiseInstanceMethod, { ruleId as noPromiseInstanceMethodRuleId } from './no-promise-instance-method';
@@ -74,9 +74,9 @@ const rules: Record<string, TSESLint.LooseRuleDefinition> = {
74
74
  [invalidJsonStringifyRuleId]: invalidJsonStringify,
75
75
  [noPromiseInstanceMethodRuleId]: noPromiseInstanceMethod,
76
76
  [noFixtureRuleId]: noFixture,
77
- [noSupertestRuleId]: noSupertest,
78
- [supertestThenRuleId]: supertestThen,
79
- [fetchThenRuleId]: fetchThen,
77
+ [noExpectAssertionRuleId]: noExpectAssertion,
78
+ // [supertestThenRuleId]: supertestThen,
79
+ // [fetchThenRuleId]: fetchThen,
80
80
  [noServiceWrapperRuleId]: noServiceWrapper,
81
81
  [noStatusCodeRuleId]: noStatusCode,
82
82
  [fetchResponseBodyJsonRuleId]: fetchResponseBodyJson,
@@ -135,14 +135,14 @@ const configs: Record<string, TSESLint.FlatConfig.Config[]> = {
135
135
  [`@checkdigit/${noMappedResponseRuleId}`]: 'off',
136
136
  [`@checkdigit/${addUrlDomainRuleId}`]: 'off',
137
137
  [`@checkdigit/${noFixtureRuleId}`]: 'off',
138
- [`@checkdigit/${noSupertestRuleId}`]: 'off',
139
- [`@checkdigit/${supertestThenRuleId}`]: 'off',
138
+ [`@checkdigit/${noExpectAssertionRuleId}`]: 'off',
139
+ // [`@checkdigit/${supertestThenRuleId}`]: 'off',
140
140
  [`@checkdigit/${noServiceWrapperRuleId}`]: 'off',
141
141
  [`@checkdigit/${noStatusCodeRuleId}`]: 'off',
142
142
  [`@checkdigit/${fetchResponseBodyJsonRuleId}`]: 'off',
143
143
  [`@checkdigit/${fetchResponseHeaderGetterRuleId}`]: 'off',
144
144
  [`@checkdigit/${fetchResponseStatusRuleId}`]: 'off',
145
- [`@checkdigit/${fetchThenRuleId}`]: 'off',
145
+ // [`@checkdigit/${fetchThenRuleId}`]: 'off',
146
146
  [`@checkdigit/${noUnusedFunctionArgumentsRuleId}`]: 'off',
147
147
  [`@checkdigit/${noUnusedServiceVariablesRuleId}`]: 'off',
148
148
  [`@checkdigit/${noUnusedImportsRuleId}`]: 'off',
@@ -185,14 +185,14 @@ const configs: Record<string, TSESLint.FlatConfig.Config[]> = {
185
185
  [`@checkdigit/${noMappedResponseRuleId}`]: 'off',
186
186
  [`@checkdigit/${addUrlDomainRuleId}`]: 'off',
187
187
  [`@checkdigit/${noFixtureRuleId}`]: 'off',
188
- [`@checkdigit/${noSupertestRuleId}`]: 'off',
189
- [`@checkdigit/${supertestThenRuleId}`]: 'off',
188
+ [`@checkdigit/${noExpectAssertionRuleId}`]: 'off',
189
+ // [`@checkdigit/${supertestThenRuleId}`]: 'off',
190
190
  [`@checkdigit/${noServiceWrapperRuleId}`]: 'off',
191
191
  [`@checkdigit/${noStatusCodeRuleId}`]: 'off',
192
192
  [`@checkdigit/${fetchResponseBodyJsonRuleId}`]: 'off',
193
193
  [`@checkdigit/${fetchResponseHeaderGetterRuleId}`]: 'off',
194
194
  [`@checkdigit/${fetchResponseStatusRuleId}`]: 'off',
195
- [`@checkdigit/${fetchThenRuleId}`]: 'off',
195
+ // [`@checkdigit/${fetchThenRuleId}`]: 'off',
196
196
  [`@checkdigit/${noUnusedFunctionArgumentsRuleId}`]: 'off',
197
197
  [`@checkdigit/${noUnusedServiceVariablesRuleId}`]: 'off',
198
198
  [`@checkdigit/${noUnusedImportsRuleId}`]: 'off',
@@ -221,7 +221,7 @@ const configs: Record<string, TSESLint.FlatConfig.Config[]> = {
221
221
  [`@checkdigit/${fetchResponseBodyJsonRuleId}`]: 'error',
222
222
  [`@checkdigit/${fetchResponseHeaderGetterRuleId}`]: 'error',
223
223
  [`@checkdigit/${fetchResponseStatusRuleId}`]: 'error',
224
- [`@checkdigit/${fetchThenRuleId}`]: 'error',
224
+ // [`@checkdigit/${fetchThenRuleId}`]: 'error',
225
225
  [`@checkdigit/${noUnusedFunctionArgumentsRuleId}`]: 'error',
226
226
  [`@checkdigit/${noUnusedServiceVariablesRuleId}`]: 'error',
227
227
  [`@checkdigit/${noUnusedImportsRuleId}`]: 'error',
@@ -230,8 +230,8 @@ const configs: Record<string, TSESLint.FlatConfig.Config[]> = {
230
230
  [`@checkdigit/${addBasePathImportRuleId}`]: 'error',
231
231
  [`@checkdigit/${addAssertImportRuleId}`]: 'error',
232
232
  [`@checkdigit/${noFixtureRuleId}`]: 'error',
233
- [`@checkdigit/${noSupertestRuleId}`]: 'error',
234
- [`@checkdigit/${supertestThenRuleId}`]: 'error',
233
+ [`@checkdigit/${noExpectAssertionRuleId}`]: 'error',
234
+ // [`@checkdigit/${supertestThenRuleId}`]: 'error',
235
235
  },
236
236
  },
237
237
  {
@@ -260,7 +260,7 @@ const configs: Record<string, TSESLint.FlatConfig.Config[]> = {
260
260
  [`@checkdigit/${fetchResponseBodyJsonRuleId}`]: 'error',
261
261
  [`@checkdigit/${fetchResponseHeaderGetterRuleId}`]: 'error',
262
262
  [`@checkdigit/${fetchResponseStatusRuleId}`]: 'error',
263
- [`@checkdigit/${fetchThenRuleId}`]: 'error',
263
+ // [`@checkdigit/${fetchThenRuleId}`]: 'error',
264
264
  [`@checkdigit/${noUnusedFunctionArgumentsRuleId}`]: 'error',
265
265
  [`@checkdigit/${noUnusedServiceVariablesRuleId}`]: 'error',
266
266
  [`@checkdigit/${noUnusedImportsRuleId}`]: 'error',