@checkdigit/eslint-plugin 7.5.0-PR.75-0252 → 7.5.0-PR.93-6f1c

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.
Files changed (71) hide show
  1. package/dist-mjs/index.mjs +4 -146
  2. package/dist-mjs/require-fixed-services-import.mjs +1 -1
  3. package/dist-mjs/require-resolve-full-response.mjs +1 -1
  4. package/dist-types/index.d.ts +1 -1
  5. package/package.json +1 -1
  6. package/src/index.ts +2 -144
  7. package/src/require-fixed-services-import.ts +1 -1
  8. package/src/require-resolve-full-response.ts +0 -2
  9. package/dist-mjs/agent/add-assert-import.mjs +0 -58
  10. package/dist-mjs/agent/add-base-path-const.mjs +0 -65
  11. package/dist-mjs/agent/add-base-path-import.mjs +0 -60
  12. package/dist-mjs/agent/add-url-domain.mjs +0 -61
  13. package/dist-mjs/agent/agent-test-wiring.mjs +0 -221
  14. package/dist-mjs/agent/fetch-response-body-json.mjs +0 -146
  15. package/dist-mjs/agent/fetch-response-header-getter.mjs +0 -117
  16. package/dist-mjs/agent/fetch-response-status.mjs +0 -66
  17. package/dist-mjs/agent/fetch-then.mjs +0 -269
  18. package/dist-mjs/agent/fetch.mjs +0 -38
  19. package/dist-mjs/agent/file.mjs +0 -43
  20. package/dist-mjs/agent/fix-function-call-arguments.mjs +0 -153
  21. package/dist-mjs/agent/no-fixture.mjs +0 -361
  22. package/dist-mjs/agent/no-mapped-response.mjs +0 -75
  23. package/dist-mjs/agent/no-service-wrapper.mjs +0 -185
  24. package/dist-mjs/agent/no-status-code.mjs +0 -59
  25. package/dist-mjs/agent/no-unused-function-argument.mjs +0 -79
  26. package/dist-mjs/agent/no-unused-imports.mjs +0 -81
  27. package/dist-mjs/agent/no-unused-service-variable.mjs +0 -74
  28. package/dist-mjs/agent/response-reference.mjs +0 -70
  29. package/dist-mjs/agent/url.mjs +0 -32
  30. package/dist-types/agent/add-assert-import.d.ts +0 -4
  31. package/dist-types/agent/add-base-path-const.d.ts +0 -4
  32. package/dist-types/agent/add-base-path-import.d.ts +0 -4
  33. package/dist-types/agent/add-url-domain.d.ts +0 -4
  34. package/dist-types/agent/agent-test-wiring.d.ts +0 -4
  35. package/dist-types/agent/fetch-response-body-json.d.ts +0 -4
  36. package/dist-types/agent/fetch-response-header-getter.d.ts +0 -4
  37. package/dist-types/agent/fetch-response-status.d.ts +0 -4
  38. package/dist-types/agent/fetch-then.d.ts +0 -4
  39. package/dist-types/agent/fetch.d.ts +0 -5
  40. package/dist-types/agent/file.d.ts +0 -7
  41. package/dist-types/agent/fix-function-call-arguments.d.ts +0 -9
  42. package/dist-types/agent/no-fixture.d.ts +0 -4
  43. package/dist-types/agent/no-mapped-response.d.ts +0 -4
  44. package/dist-types/agent/no-service-wrapper.d.ts +0 -4
  45. package/dist-types/agent/no-status-code.d.ts +0 -4
  46. package/dist-types/agent/no-unused-function-argument.d.ts +0 -4
  47. package/dist-types/agent/no-unused-imports.d.ts +0 -4
  48. package/dist-types/agent/no-unused-service-variable.d.ts +0 -4
  49. package/dist-types/agent/response-reference.d.ts +0 -16
  50. package/dist-types/agent/url.d.ts +0 -4
  51. package/src/agent/add-assert-import.ts +0 -74
  52. package/src/agent/add-base-path-const.ts +0 -81
  53. package/src/agent/add-base-path-import.ts +0 -69
  54. package/src/agent/add-url-domain.ts +0 -76
  55. package/src/agent/agent-test-wiring.ts +0 -273
  56. package/src/agent/fetch-response-body-json.ts +0 -197
  57. package/src/agent/fetch-response-header-getter.ts +0 -148
  58. package/src/agent/fetch-response-status.ts +0 -87
  59. package/src/agent/fetch-then.ts +0 -357
  60. package/src/agent/fetch.ts +0 -57
  61. package/src/agent/file.ts +0 -42
  62. package/src/agent/fix-function-call-arguments.ts +0 -200
  63. package/src/agent/no-fixture.ts +0 -521
  64. package/src/agent/no-mapped-response.ts +0 -84
  65. package/src/agent/no-service-wrapper.ts +0 -241
  66. package/src/agent/no-status-code.ts +0 -72
  67. package/src/agent/no-unused-function-argument.ts +0 -98
  68. package/src/agent/no-unused-imports.ts +0 -103
  69. package/src/agent/no-unused-service-variable.ts +0 -93
  70. package/src/agent/response-reference.ts +0 -129
  71. package/src/agent/url.ts +0 -32
@@ -1,521 +0,0 @@
1
- // agent/no-fixture.ts
2
-
3
- /*
4
- * Copyright (c) 2021-2024 Check Digit, LLC
5
- *
6
- * This code is licensed under the MIT license (see LICENSE.txt for details).
7
- */
8
-
9
- import { strict as assert } from 'node:assert';
10
-
11
- import type {
12
- AwaitExpression,
13
- CallExpression,
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';
25
-
26
- import {
27
- getEnclosingFunction,
28
- getEnclosingScopeNode,
29
- getEnclosingStatement,
30
- getParent,
31
- isUsedInArrayOrAsArgument,
32
- } from '../library/tree';
33
- import getDocumentationUrl from '../get-documentation-url';
34
- import { getIndentation } from '../library/format';
35
- import { isValidPropertyName } from '../library/variable';
36
- import { analyzeResponseReferences } from './response-reference';
37
- import { getResponseBodyRetrievalText, getResponseHeadersRetrievalText, hasAssertions } from './fetch';
38
- import { replaceEndpointUrlPrefixWithBasePath } from './url';
39
-
40
- export const ruleId = 'no-fixture';
41
-
42
- interface FixtureCallInformation {
43
- rootNode: AwaitExpression | ReturnStatement | VariableDeclaration | SimpleCallExpression | ExpressionStatement;
44
- fixtureNode: AwaitExpression | SimpleCallExpression;
45
- variableDeclaration?: VariableDeclaration;
46
- variableAssignment?: ExpressionStatement;
47
- requestBody?: Expression;
48
- requestHeaders?: { name: Expression; value: Expression }[];
49
- requestHeadersObjectLiteral?: ObjectExpression;
50
- assertions?: Expression[][];
51
- inlineStatementNode?: Node;
52
- inlineBodyReference?: MemberExpression;
53
- inlineHeadersReference?: MemberExpression;
54
- }
55
-
56
- // recursively analyze the fixture/supertest call chain to collect information of request/response
57
- // eslint-disable-next-line sonarjs/cognitive-complexity
58
- function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInformation, sourceCode: SourceCode) {
59
- const parent = getParent(call);
60
- assert.ok(parent, 'parent should exist for fixture/supertest call node');
61
-
62
- let nextCall;
63
- if (parent.type === 'ReturnStatement') {
64
- // direct return, no variable declaration or await
65
- results.fixtureNode = call;
66
- results.rootNode = parent;
67
- } else if (
68
- parent.type === 'ArrayExpression' ||
69
- parent.type === 'CallExpression' ||
70
- parent.type === 'ArrowFunctionExpression'
71
- ) {
72
- // direct return, no variable declaration or await
73
- results.fixtureNode = call;
74
- results.rootNode = call;
75
- } else if (parent.type === 'AwaitExpression') {
76
- results.fixtureNode = call;
77
- const enclosingStatement = getEnclosingStatement(parent);
78
- assert.ok(enclosingStatement);
79
- const awaitParent = getParent(parent);
80
- if (awaitParent?.type === 'MemberExpression') {
81
- results.rootNode = parent;
82
- results.inlineStatementNode = enclosingStatement;
83
- if (awaitParent.property.type === 'Identifier' && awaitParent.property.name === 'body') {
84
- results.inlineBodyReference = awaitParent;
85
- }
86
- if (
87
- awaitParent.property.type === 'Identifier' &&
88
- (awaitParent.property.name === 'header' || awaitParent.property.name === 'headers')
89
- ) {
90
- results.inlineHeadersReference = awaitParent;
91
- }
92
- } else if (enclosingStatement.type === 'VariableDeclaration') {
93
- results.variableDeclaration = enclosingStatement;
94
- results.rootNode = enclosingStatement;
95
- } else if (
96
- enclosingStatement.type === 'ExpressionStatement' &&
97
- enclosingStatement.expression.type === 'AssignmentExpression'
98
- ) {
99
- results.variableAssignment = enclosingStatement;
100
- results.rootNode = enclosingStatement;
101
- } else {
102
- results.rootNode = parent;
103
- }
104
- } else if (parent.type === 'MemberExpression' && parent.property.type === 'Identifier') {
105
- if (parent.property.name === 'expect') {
106
- // supertest assertions
107
- const assertionCall = getParent(parent);
108
- assert.ok(assertionCall && assertionCall.type === 'CallExpression');
109
- results.assertions = [...(results.assertions ?? []), assertionCall.arguments as Expression[]];
110
- nextCall = assertionCall;
111
- } else if (parent.property.name === 'send') {
112
- // request body
113
- const sendRequestBodyCall = getParent(parent);
114
- assert.ok(sendRequestBodyCall && sendRequestBodyCall.type === 'CallExpression');
115
- results.requestBody = sendRequestBodyCall.arguments[0] as Expression;
116
- nextCall = sendRequestBodyCall;
117
- } else if (parent.property.name === 'set') {
118
- // request headers
119
- const setRequestHeaderCall = getParent(parent);
120
- assert.ok(setRequestHeaderCall && setRequestHeaderCall.type === 'CallExpression');
121
- const [arg1, arg2] = setRequestHeaderCall.arguments as [Expression, Expression];
122
- if (arg1.type === 'ObjectExpression') {
123
- results.requestHeadersObjectLiteral = arg1;
124
- } else {
125
- results.requestHeaders = [...(results.requestHeaders ?? []), { name: arg1, value: arg2 }];
126
- }
127
- nextCall = setRequestHeaderCall;
128
- }
129
- } else {
130
- throw new Error(`Unexpected expression in fixture/supertest call ${sourceCode.getText(parent)}.`);
131
- }
132
- if (nextCall) {
133
- analyzeFixtureCall(nextCall, results, sourceCode);
134
- }
135
- }
136
-
137
- // eslint-disable-next-line sonarjs/cognitive-complexity
138
- function createResponseAssertions(
139
- fixtureCallInformation: FixtureCallInformation,
140
- sourceCode: SourceCode,
141
- responseVariableName: string,
142
- destructuringResponseHeadersVariable: Scope.Variable | undefined,
143
- ) {
144
- let statusAssertion: string | undefined;
145
- const nonStatusAssertions: string[] = [];
146
- for (const expectArguments of fixtureCallInformation.assertions ?? []) {
147
- if (expectArguments.length === 1) {
148
- const [assertionArgument] = expectArguments;
149
- assert.ok(assertionArgument);
150
- if (
151
- (assertionArgument.type === 'MemberExpression' &&
152
- assertionArgument.object.type === 'Identifier' &&
153
- assertionArgument.object.name === 'StatusCodes') ||
154
- assertionArgument.type === 'Literal' ||
155
- sourceCode.getText(assertionArgument).includes('StatusCodes.')
156
- ) {
157
- // status code assertion
158
- statusAssertion = `assert.equal(${responseVariableName}.status, ${sourceCode.getText(assertionArgument)})`;
159
- } else if (assertionArgument.type === 'ArrowFunctionExpression') {
160
- // callback assertion using arrow function
161
- let functionBody = sourceCode.getText(assertionArgument.body);
162
-
163
- const [originalResponseArgument] = assertionArgument.params;
164
- assert.ok(originalResponseArgument?.type === 'Identifier');
165
- const originalResponseArgumentName = originalResponseArgument.name;
166
- if (originalResponseArgumentName !== responseVariableName) {
167
- functionBody = functionBody.replace(
168
- new RegExp(`\\b${originalResponseArgumentName}\\b`, 'ug'),
169
- responseVariableName,
170
- );
171
- }
172
- nonStatusAssertions.push(`assert.doesNotThrow(()=>${functionBody})`);
173
- } else if (assertionArgument.type === 'Identifier') {
174
- // callback assertion using function reference
175
- nonStatusAssertions.push(
176
- `assert.doesNotThrow(()=>${sourceCode.getText(assertionArgument)}(${responseVariableName}))`,
177
- );
178
- } else if (assertionArgument.type === 'ObjectExpression' || assertionArgument.type === 'CallExpression') {
179
- // body deep equal assertion
180
- nonStatusAssertions.push(
181
- `assert.deepEqual(await ${responseVariableName}.json(), ${sourceCode.getText(assertionArgument)})`,
182
- );
183
- } else {
184
- throw new Error(`Unexpected Supertest assertion argument: ".expect(${sourceCode.getText(assertionArgument)})`);
185
- }
186
- } else if (expectArguments.length === 2) {
187
- // header assertion
188
- const [headerName, headerValue] = expectArguments;
189
- assert.ok(headerName && headerValue);
190
- const headersReference =
191
- destructuringResponseHeadersVariable !== undefined
192
- ? destructuringResponseHeadersVariable.name
193
- : `${responseVariableName}.headers`;
194
- if (headerValue.type === 'Literal' && headerValue.value instanceof RegExp) {
195
- nonStatusAssertions.push(
196
- `assert.ok(${headersReference}.get(${sourceCode.getText(headerName)}).match(${sourceCode.getText(headerValue)}))`,
197
- );
198
- } else {
199
- nonStatusAssertions.push(
200
- `assert.equal(${headersReference}.get(${sourceCode.getText(headerName)}), ${sourceCode.getText(headerValue)})`,
201
- );
202
- }
203
- }
204
- }
205
- return {
206
- statusAssertion,
207
- nonStatusAssertions,
208
- };
209
- }
210
-
211
- function getResponseVariableNameToUse(
212
- scopeManager: Scope.ScopeManager,
213
- fixtureCallInformation: FixtureCallInformation,
214
- scopeVariablesMap: Map<Scope.Scope, string[]>,
215
- ) {
216
- if (fixtureCallInformation.variableAssignment) {
217
- assert.ok(
218
- fixtureCallInformation.variableAssignment.expression.type === 'AssignmentExpression' &&
219
- fixtureCallInformation.variableAssignment.expression.left.type === 'Identifier',
220
- );
221
- return fixtureCallInformation.variableAssignment.expression.left.name;
222
- }
223
-
224
- if (fixtureCallInformation.variableDeclaration) {
225
- const firstDeclaration = fixtureCallInformation.variableDeclaration.declarations[0];
226
- if (firstDeclaration && firstDeclaration.id.type === 'Identifier') {
227
- return firstDeclaration.id.name;
228
- }
229
- }
230
-
231
- const enclosingScopeNode = getEnclosingScopeNode(fixtureCallInformation.rootNode);
232
- scopeManager.getDeclaredVariables(fixtureCallInformation.rootNode);
233
- assert.ok(enclosingScopeNode);
234
- const scope = scopeManager.acquire(enclosingScopeNode);
235
- assert.ok(scope !== null);
236
- let scopeVariables = scopeVariablesMap.get(scope);
237
- if (!scopeVariables) {
238
- scopeVariables = [...scope.set.keys()];
239
- scopeVariablesMap.set(scope, scopeVariables);
240
- }
241
-
242
- let responseVariableCounter = 0;
243
- let responseVariableNameToUse;
244
- while (responseVariableNameToUse === undefined) {
245
- responseVariableCounter++;
246
- responseVariableNameToUse = `response${responseVariableCounter === 1 ? '' : responseVariableCounter.toString()}`;
247
- if (scopeVariables.includes(responseVariableNameToUse)) {
248
- responseVariableNameToUse = undefined;
249
- }
250
- }
251
- scopeVariables.push(responseVariableNameToUse);
252
- return responseVariableNameToUse;
253
- }
254
-
255
- function isResponseBodyRedefinition(responseBodyReference: MemberExpression): boolean {
256
- const parent = getParent(responseBodyReference);
257
- return parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier';
258
- }
259
-
260
- const rule: Rule.RuleModule = {
261
- meta: {
262
- type: 'suggestion',
263
- docs: {
264
- description: 'Prefer native fetch API over customized fixture API.',
265
- url: getDocumentationUrl(ruleId),
266
- },
267
- messages: {
268
- preferNativeFetch: 'Prefer native fetch API over customized fixture API.',
269
- unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
270
- },
271
- fixable: 'code',
272
- schema: [],
273
- },
274
- // eslint-disable-next-line max-lines-per-function
275
- create(context) {
276
- const sourceCode = context.sourceCode;
277
- const scopeManager = sourceCode.scopeManager;
278
- const scopeVariablesMap = new Map<Scope.Scope, string[]>();
279
-
280
- return {
281
- // eslint-disable-next-line max-lines-per-function
282
- 'CallExpression[callee.object.object.name="fixture"][callee.object.property.name="api"]': (
283
- fixtureCall: CallExpression,
284
- // eslint-disable-next-line sonarjs/cognitive-complexity
285
- ) => {
286
- try {
287
- if (
288
- hasAssertions(fixtureCall) &&
289
- (isUsedInArrayOrAsArgument(fixtureCall) || getEnclosingFunction(fixtureCall)?.async === false)
290
- ) {
291
- // skip and leave it to "fetch-then" rule to handle it because no "await" can be used here
292
- return;
293
- }
294
-
295
- assert.ok(fixtureCall.type === 'CallExpression');
296
- const fixtureFunction = fixtureCall.callee; // e.g. fixture.api.get
297
- assert.ok(fixtureFunction.type === 'MemberExpression');
298
- const indentation = getIndentation(fixtureCall, sourceCode);
299
-
300
- const [urlArgumentNode] = fixtureCall.arguments; // e.g. `/sample-service/v1/ping`
301
- assert.ok(urlArgumentNode !== undefined);
302
-
303
- const fixtureCallInformation = {} as FixtureCallInformation;
304
- analyzeFixtureCall(fixtureCall, fixtureCallInformation, sourceCode);
305
-
306
- const {
307
- variable: responseVariable,
308
- bodyReferences: responseBodyReferences,
309
- headersReferences: responseHeadersReferences,
310
- statusReferences: responseStatusReferences,
311
- destructuringBodyVariable: destructuringResponseBodyVariable,
312
- destructuringHeadersVariable: destructuringResponseHeadersVariable,
313
- } = analyzeResponseReferences(fixtureCallInformation.variableDeclaration, scopeManager);
314
-
315
- // convert url from `/sample-service/v1/ping` to `${BASE_PATH}/ping`
316
- const originalUrlArgumentText = sourceCode.getText(urlArgumentNode);
317
- const fetchUrlArgumentText = replaceEndpointUrlPrefixWithBasePath(originalUrlArgumentText);
318
-
319
- // fetch request argument
320
- const methodNode = fixtureFunction.property; // get/put/etc.
321
- assert.ok(methodNode.type === 'Identifier');
322
- const methodName = methodNode.name.toUpperCase();
323
-
324
- const fetchRequestArgumentLines = [
325
- '{',
326
- ` method: '${methodName === 'DEL' ? 'DELETE' : methodName}',`,
327
- ...(fixtureCallInformation.requestBody
328
- ? [` body: JSON.stringify(${sourceCode.getText(fixtureCallInformation.requestBody)}),`]
329
- : []),
330
- // eslint-disable-next-line no-nested-ternary
331
- ...(fixtureCallInformation.requestHeadersObjectLiteral
332
- ? [` headers: ${sourceCode.getText(fixtureCallInformation.requestHeadersObjectLiteral)},`]
333
- : fixtureCallInformation.requestHeaders
334
- ? [
335
- ` headers: {`,
336
- ...fixtureCallInformation.requestHeaders.map(
337
- ({ name, value }) =>
338
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, no-nested-ternary, sonarjs/no-nested-template-literals
339
- ` ${name.type === 'Literal' ? (isValidPropertyName(name.value) ? name.value : `'${name.value}'`) : `[${sourceCode.getText(name)}]`}: ${sourceCode.getText(value)},`,
340
- ),
341
- ` },`,
342
- ]
343
- : []),
344
- '}',
345
- ].join(`\n${indentation}`);
346
-
347
- const responseVariableNameToUse = getResponseVariableNameToUse(
348
- scopeManager,
349
- fixtureCallInformation,
350
- scopeVariablesMap,
351
- );
352
-
353
- const isResponseBodyVariableRedefinitionNeeded =
354
- destructuringResponseBodyVariable !== undefined ||
355
- fixtureCallInformation.inlineBodyReference !== undefined ||
356
- (responseBodyReferences.length > 0 && !responseBodyReferences.some(isResponseBodyRedefinition));
357
- const redefineResponseBodyVariableName = `${responseVariableNameToUse}Body`;
358
-
359
- const isResponseHeadersVariableRedefinitionNeeded =
360
- (destructuringResponseHeadersVariable !== undefined &&
361
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
362
- (destructuringResponseHeadersVariable as ObjectPattern).type === 'ObjectPattern') ||
363
- fixtureCallInformation.inlineHeadersReference !== undefined;
364
- const redefineResponseHeadersVariableName = `${responseVariableNameToUse}Headers`;
365
-
366
- const isResponseVariableRedefinitionNeeded =
367
- (fixtureCallInformation.variableAssignment === undefined &&
368
- responseVariable === undefined &&
369
- fixtureCallInformation.assertions !== undefined) ||
370
- isResponseBodyVariableRedefinitionNeeded ||
371
- isResponseHeadersVariableRedefinitionNeeded;
372
-
373
- const responseBodyHeadersVariableRedefineLines = isResponseVariableRedefinitionNeeded
374
- ? [
375
- // eslint-disable-next-line no-nested-ternary
376
- ...(destructuringResponseBodyVariable
377
- ? [
378
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
379
- `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseBodyVariable as ObjectPattern).type === 'ObjectPattern' ? sourceCode.getText(destructuringResponseBodyVariable as ObjectPattern) : (destructuringResponseBodyVariable as Scope.Variable).name} = ${getResponseBodyRetrievalText(responseVariableNameToUse)}`,
380
- ]
381
- : isResponseBodyVariableRedefinitionNeeded
382
- ? [
383
- `const ${redefineResponseBodyVariableName} = ${getResponseBodyRetrievalText(responseVariableNameToUse)}`,
384
- ]
385
- : []),
386
- // eslint-disable-next-line no-nested-ternary
387
- ...(destructuringResponseHeadersVariable
388
- ? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
389
- (destructuringResponseHeadersVariable as ObjectPattern).type === 'ObjectPattern'
390
- ? (destructuringResponseHeadersVariable as ObjectPattern).properties.map((property) => {
391
- assert.ok(property.type === 'Property');
392
- assert.equal(property.value.type, 'Identifier');
393
- // eslint-disable-next-line sonarjs/no-nested-template-literals
394
- return `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${property.value.name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}.get(${property.key.type === 'Literal' ? sourceCode.getText(property.key) : `'${sourceCode.getText(property.key)}'`})`;
395
- })
396
- : [
397
- `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseHeadersVariable as Scope.Variable).name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}`,
398
- ]
399
- : isResponseHeadersVariableRedefinitionNeeded
400
- ? [
401
- `const ${redefineResponseHeadersVariableName} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}`,
402
- ]
403
- : []),
404
- ]
405
- : [];
406
-
407
- const { statusAssertion, nonStatusAssertions } = createResponseAssertions(
408
- fixtureCallInformation,
409
- sourceCode,
410
- responseVariableNameToUse,
411
- destructuringResponseHeadersVariable as Scope.Variable | undefined,
412
- );
413
-
414
- // add variable declaration if needed
415
- const fetchCallText = `fetch(${fetchUrlArgumentText}, ${fetchRequestArgumentLines})`;
416
- const fetchStatementText = !isResponseVariableRedefinitionNeeded
417
- ? fetchCallText
418
- : `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${responseVariableNameToUse} = await ${fetchCallText}`;
419
-
420
- const nodeToReplace = isResponseVariableRedefinitionNeeded
421
- ? fixtureCallInformation.rootNode
422
- : fixtureCallInformation.fixtureNode;
423
- const appendingAssignmentAndAssertionText = [
424
- '',
425
- ...(statusAssertion !== undefined ? [statusAssertion] : []),
426
- ...responseBodyHeadersVariableRedefineLines,
427
- ...nonStatusAssertions,
428
- ].join(`;\n${indentation}`);
429
-
430
- context.report({
431
- node: fixtureCall,
432
- messageId: 'preferNativeFetch',
433
-
434
- *fix(fixer) {
435
- if (fixtureCallInformation.inlineStatementNode) {
436
- const preInlineDeclaration = [
437
- fetchStatementText,
438
- `${appendingAssignmentAndAssertionText};\n${indentation}`,
439
- ].join(``);
440
- yield fixer.insertTextBefore(fixtureCallInformation.inlineStatementNode, preInlineDeclaration);
441
- } else {
442
- yield fixer.replaceText(nodeToReplace, fetchStatementText);
443
-
444
- const needEndingSemiColon = sourceCode.getText(nodeToReplace).endsWith(';');
445
- yield fixer.insertTextAfter(
446
- nodeToReplace,
447
- needEndingSemiColon ? `${appendingAssignmentAndAssertionText};` : appendingAssignmentAndAssertionText,
448
- );
449
- }
450
-
451
- // handle response body references
452
- for (const responseBodyReference of responseBodyReferences) {
453
- yield fixer.replaceText(
454
- responseBodyReference,
455
- isResponseBodyVariableRedefinitionNeeded || !isResponseBodyRedefinition(responseBodyReference)
456
- ? redefineResponseBodyVariableName
457
- : getResponseBodyRetrievalText(responseVariableNameToUse),
458
- );
459
- }
460
- if (fixtureCallInformation.inlineBodyReference) {
461
- yield fixer.replaceText(fixtureCallInformation.inlineBodyReference, redefineResponseBodyVariableName);
462
- }
463
-
464
- // handle response headers references
465
- for (const responseHeadersReference of responseHeadersReferences) {
466
- const parent = getParent(responseHeadersReference);
467
- assert.ok(parent);
468
- let headerName;
469
- if (parent.type === 'MemberExpression') {
470
- const headerNameNode = parent.property;
471
- headerName = parent.computed
472
- ? sourceCode.getText(headerNameNode)
473
- : `'${sourceCode.getText(headerNameNode)}'`;
474
- } else if (parent.type === 'CallExpression') {
475
- const headerNameNode = parent.arguments[0];
476
- headerName = sourceCode.getText(headerNameNode);
477
- }
478
- assert.ok(headerName !== undefined);
479
- yield fixer.replaceText(parent, `${responseVariableNameToUse}.headers.get(${headerName})`);
480
- }
481
-
482
- // convert response.statusCode to response.status
483
- for (const responseStatusReference of responseStatusReferences) {
484
- if (
485
- responseStatusReference.property.type === 'Identifier' &&
486
- responseStatusReference.property.name === 'statusCode'
487
- ) {
488
- yield fixer.replaceText(responseStatusReference.property, `status`);
489
- }
490
- }
491
-
492
- // handle direct return statement without await, e.g. "return fixture.api.get(...);"
493
- if (
494
- fixtureCallInformation.rootNode.type === 'ReturnStatement' &&
495
- fixtureCallInformation.assertions !== undefined
496
- ) {
497
- yield fixer.insertTextAfter(
498
- fixtureCallInformation.rootNode,
499
- `\n${indentation}return ${responseVariableNameToUse};`,
500
- );
501
- }
502
- },
503
- });
504
- } catch (error) {
505
- // eslint-disable-next-line no-console
506
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
507
- context.report({
508
- node: fixtureCall,
509
- messageId: 'unknownError',
510
- data: {
511
- fileName: context.filename,
512
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
513
- },
514
- });
515
- }
516
- },
517
- };
518
- },
519
- };
520
-
521
- export default rule;
@@ -1,84 +0,0 @@
1
- // agent/no-mapped-response-type.ts
2
-
3
- /*
4
- * Copyright (c) 2021-2024 Check Digit, LLC
5
- *
6
- * This code is licensed under the MIT license (see LICENSE.txt for details).
7
- */
8
-
9
- import { ESLintUtils, TSESTree } from '@typescript-eslint/utils';
10
- import getDocumentationUrl from '../get-documentation-url';
11
-
12
- export const ruleId = 'no-mapped-response';
13
-
14
- const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
15
-
16
- const rule: ESLintUtils.RuleModule<'unknownError' | 'replaceFullResponseWithFetchResponse'> = createRule({
17
- name: ruleId,
18
- meta: {
19
- type: 'suggestion',
20
- docs: {
21
- description: 'Replace the usage of MappedResponse type with FetchResponse.',
22
- },
23
- messages: {
24
- replaceFullResponseWithFetchResponse: 'Replace the usage of FullResponse type with FetchResponse.',
25
- unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
26
- },
27
- fixable: 'code',
28
- schema: [],
29
- },
30
- defaultOptions: [],
31
- create(context) {
32
- const sourceCode = context.sourceCode;
33
-
34
- return {
35
- 'TSTypeReference[typeName.name="MappedResponse"]': (typeReference: TSESTree.TSTypeReference) => {
36
- try {
37
- context.report({
38
- messageId: 'replaceFullResponseWithFetchResponse',
39
- node: typeReference,
40
- fix(fixer) {
41
- const typeParams = sourceCode.getText(typeReference.typeArguments);
42
- return fixer.replaceText(typeReference, `FetchResponse${typeParams || ''}`);
43
- },
44
- });
45
- } catch (error) {
46
- // eslint-disable-next-line no-console
47
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
48
- context.report({
49
- node: typeReference,
50
- messageId: 'unknownError',
51
- data: {
52
- fileName: context.filename,
53
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
54
- },
55
- });
56
- }
57
- },
58
- 'ImportSpecifier[imported.name="MappedResponse"]': (importSpecifier: TSESTree.ImportSpecifier) => {
59
- try {
60
- context.report({
61
- messageId: 'replaceFullResponseWithFetchResponse',
62
- node: importSpecifier.imported,
63
- fix(fixer) {
64
- return fixer.replaceText(importSpecifier.imported, 'FetchResponse');
65
- },
66
- });
67
- } catch (error) {
68
- // eslint-disable-next-line no-console
69
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
70
- context.report({
71
- node: importSpecifier.imported,
72
- messageId: 'unknownError',
73
- data: {
74
- fileName: context.filename,
75
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
76
- },
77
- });
78
- }
79
- },
80
- };
81
- },
82
- });
83
-
84
- export default rule;