@checkdigit/eslint-plugin 7.3.0-PR.75-f04b → 7.3.0-PR.89-2e8d

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