@checkdigit/eslint-plugin 7.6.0-PR.75-4751 → 7.6.0-PR.97-b19c

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 (77) hide show
  1. package/dist-mjs/index.mjs +8 -156
  2. package/dist-mjs/no-status-code-assert.mjs +63 -0
  3. package/dist-mjs/require-resolve-full-response.mjs +5 -5
  4. package/dist-types/no-status-code-assert.d.ts +5 -0
  5. package/package.json +1 -1
  6. package/src/index.ts +4 -152
  7. package/src/no-status-code-assert.ts +85 -0
  8. package/src/require-resolve-full-response.ts +4 -4
  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 -76
  17. package/dist-mjs/agent/fetch-then.mjs +0 -205
  18. package/dist-mjs/agent/fetch.mjs +0 -48
  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 -383
  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-supertest.mjs +0 -332
  26. package/dist-mjs/agent/no-unused-function-argument.mjs +0 -79
  27. package/dist-mjs/agent/no-unused-imports.mjs +0 -81
  28. package/dist-mjs/agent/no-unused-service-variable.mjs +0 -74
  29. package/dist-mjs/agent/response-reference.mjs +0 -75
  30. package/dist-mjs/agent/supertest-then.mjs +0 -170
  31. package/dist-mjs/agent/url.mjs +0 -32
  32. package/dist-types/agent/add-assert-import.d.ts +0 -4
  33. package/dist-types/agent/add-base-path-const.d.ts +0 -4
  34. package/dist-types/agent/add-base-path-import.d.ts +0 -4
  35. package/dist-types/agent/add-url-domain.d.ts +0 -4
  36. package/dist-types/agent/agent-test-wiring.d.ts +0 -4
  37. package/dist-types/agent/fetch-response-body-json.d.ts +0 -4
  38. package/dist-types/agent/fetch-response-header-getter.d.ts +0 -4
  39. package/dist-types/agent/fetch-response-status.d.ts +0 -4
  40. package/dist-types/agent/fetch-then.d.ts +0 -4
  41. package/dist-types/agent/fetch.d.ts +0 -8
  42. package/dist-types/agent/file.d.ts +0 -7
  43. package/dist-types/agent/fix-function-call-arguments.d.ts +0 -9
  44. package/dist-types/agent/no-fixture.d.ts +0 -4
  45. package/dist-types/agent/no-mapped-response.d.ts +0 -4
  46. package/dist-types/agent/no-service-wrapper.d.ts +0 -4
  47. package/dist-types/agent/no-status-code.d.ts +0 -4
  48. package/dist-types/agent/no-supertest.d.ts +0 -4
  49. package/dist-types/agent/no-unused-function-argument.d.ts +0 -4
  50. package/dist-types/agent/no-unused-imports.d.ts +0 -4
  51. package/dist-types/agent/no-unused-service-variable.d.ts +0 -4
  52. package/dist-types/agent/response-reference.d.ts +0 -16
  53. package/dist-types/agent/supertest-then.d.ts +0 -4
  54. package/dist-types/agent/url.d.ts +0 -4
  55. package/src/agent/add-assert-import.ts +0 -74
  56. package/src/agent/add-base-path-const.ts +0 -81
  57. package/src/agent/add-base-path-import.ts +0 -69
  58. package/src/agent/add-url-domain.ts +0 -76
  59. package/src/agent/agent-test-wiring.ts +0 -273
  60. package/src/agent/fetch-response-body-json.ts +0 -194
  61. package/src/agent/fetch-response-header-getter.ts +0 -148
  62. package/src/agent/fetch-response-status.ts +0 -100
  63. package/src/agent/fetch-then.ts +0 -358
  64. package/src/agent/fetch.ts +0 -69
  65. package/src/agent/file.ts +0 -42
  66. package/src/agent/fix-function-call-arguments.ts +0 -200
  67. package/src/agent/no-fixture.ts +0 -581
  68. package/src/agent/no-mapped-response.ts +0 -84
  69. package/src/agent/no-service-wrapper.ts +0 -241
  70. package/src/agent/no-status-code.ts +0 -69
  71. package/src/agent/no-supertest.ts +0 -517
  72. package/src/agent/no-unused-function-argument.ts +0 -98
  73. package/src/agent/no-unused-imports.ts +0 -103
  74. package/src/agent/no-unused-service-variable.ts +0 -93
  75. package/src/agent/response-reference.ts +0 -153
  76. package/src/agent/supertest-then.ts +0 -230
  77. package/src/agent/url.ts +0 -32
@@ -1,581 +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 { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
12
- import type { Scope, ScopeManager, Variable } from '@typescript-eslint/scope-manager';
13
- import type { SourceCode } from '@typescript-eslint/utils/ts-eslint';
14
-
15
- import {
16
- getEnclosingFunction,
17
- getEnclosingScopeNode,
18
- getEnclosingStatement,
19
- getParent,
20
- isUsedInArrayOrAsArgument,
21
- } from '../library/ts-tree';
22
- import getDocumentationUrl from '../get-documentation-url';
23
- import { getIndentation } from '../library/format';
24
- 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
- import { replaceEndpointUrlPrefixWithBasePath } from './url';
33
-
34
- export const ruleId = 'no-fixture';
35
-
36
- 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;
46
- requestBody?: TSESTree.Expression;
47
- requestHeaders?: { name: TSESTree.Expression; value: TSESTree.Expression }[];
48
- requestHeadersObjectLiteral?: TSESTree.ObjectExpression;
49
- assertions?: TSESTree.Expression[][];
50
- inlineStatementNode?: TSESTree.Node;
51
- inlineBodyReference?: TSESTree.MemberExpression;
52
- inlineStatusReference?: TSESTree.MemberExpression;
53
- inlineHeadersReference?: TSESTree.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: TSESTree.CallExpression, 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 === 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) {
111
- if (parent.property.name === 'expect') {
112
- // supertest assertions
113
- const assertionCall = getParent(parent);
114
- assert.ok(assertionCall && assertionCall.type === AST_NODE_TYPES.CallExpression);
115
- results.assertions = [...(results.assertions ?? []), assertionCall.arguments as TSESTree.Expression[]];
116
- nextCall = assertionCall;
117
- } else if (parent.property.name === 'send') {
118
- // request body
119
- const sendRequestBodyCall = getParent(parent);
120
- assert.ok(sendRequestBodyCall && sendRequestBodyCall.type === AST_NODE_TYPES.CallExpression);
121
- results.requestBody = sendRequestBodyCall.arguments[0] as TSESTree.Expression;
122
- nextCall = sendRequestBodyCall;
123
- } else if (parent.property.name === 'set') {
124
- // request headers
125
- const setRequestHeaderCall = getParent(parent);
126
- assert.ok(setRequestHeaderCall && setRequestHeaderCall.type === AST_NODE_TYPES.CallExpression);
127
- const [arg1, arg2] = setRequestHeaderCall.arguments as [TSESTree.Expression, TSESTree.Expression];
128
- if (arg1.type === AST_NODE_TYPES.ObjectExpression) {
129
- results.requestHeadersObjectLiteral = arg1;
130
- } else {
131
- results.requestHeaders = [...(results.requestHeaders ?? []), { name: arg1, value: arg2 }];
132
- }
133
- nextCall = setRequestHeaderCall;
134
- }
135
- } else {
136
- throw new Error(`Unexpected expression in fixture/supertest call ${sourceCode.getText(parent)}.`);
137
- }
138
- if (nextCall) {
139
- analyzeFixtureCall(nextCall, results, sourceCode);
140
- }
141
- }
142
-
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;
291
- }
292
-
293
- const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
294
-
295
- const rule: ESLintUtils.RuleModule<'unknownError' | 'preferNativeFetch'> = createRule({
296
- name: ruleId,
297
- meta: {
298
- type: 'suggestion',
299
- docs: {
300
- description: 'Prefer native fetch API over customized fixture API.',
301
- url: getDocumentationUrl(ruleId),
302
- },
303
- messages: {
304
- preferNativeFetch: 'Prefer native fetch API over customized fixture API.',
305
- unknownError: 'Unknown error occurred in file "{{fileName}}": {{ error }}.',
306
- },
307
- fixable: 'code',
308
- schema: [],
309
- },
310
- defaultOptions: [],
311
- // eslint-disable-next-line max-lines-per-function
312
- create(context) {
313
- const sourceCode = context.sourceCode;
314
- const scopeManager = sourceCode.scopeManager;
315
- assert.ok(scopeManager !== null);
316
- const scopeVariablesMap = new Map<Scope, string[]>();
317
-
318
- return {
319
- // eslint-disable-next-line max-lines-per-function
320
- 'CallExpression[callee.object.object.name="fixture"][callee.object.property.name="api"]': (
321
- fixtureCall: TSESTree.CallExpression,
322
- // eslint-disable-next-line sonarjs/cognitive-complexity
323
- ) => {
324
- 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
- const fixtureFunction = fixtureCall.callee; // e.g. fixture.api.get
334
- assert.ok(fixtureFunction.type === AST_NODE_TYPES.MemberExpression);
335
- const indentation = getIndentation(fixtureCall, sourceCode);
336
-
337
- const [urlArgumentNode] = fixtureCall.arguments; // e.g. `/sample-service/v1/ping`
338
- assert.ok(urlArgumentNode !== undefined);
339
-
340
- const fixtureCallInformation = {} as FixtureCallInformation;
341
- analyzeFixtureCall(fixtureCall, fixtureCallInformation, sourceCode);
342
-
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
- // convert url from `/sample-service/v1/ping` to `${BASE_PATH}/ping`
354
- const originalUrlArgumentText = sourceCode.getText(urlArgumentNode);
355
- const fetchUrlArgumentText = replaceEndpointUrlPrefixWithBasePath(originalUrlArgumentText);
356
-
357
- // fetch request argument
358
- const methodNode = fixtureFunction.property; // get/put/etc.
359
- assert.ok(methodNode.type === AST_NODE_TYPES.Identifier);
360
- const methodName = methodNode.name.toUpperCase();
361
- const methodNameToUse = methodName === 'DEL' ? 'DELETE' : methodName;
362
-
363
- const fetchRequestArgumentLines = [
364
- '{',
365
- ` method: '${methodNameToUse}',`,
366
- ...(fixtureCallInformation.requestBody
367
- ? [` body: JSON.stringify(${sourceCode.getText(fixtureCallInformation.requestBody)}),`]
368
- : []),
369
- // eslint-disable-next-line no-nested-ternary
370
- ...(fixtureCallInformation.requestHeadersObjectLiteral
371
- ? [` headers: ${sourceCode.getText(fixtureCallInformation.requestHeadersObjectLiteral)},`]
372
- : fixtureCallInformation.requestHeaders
373
- ? [
374
- ` headers: {`,
375
- ...fixtureCallInformation.requestHeaders.map(
376
- ({ name, value }) =>
377
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, no-nested-ternary, sonarjs/no-nested-template-literals
378
- ` ${name.type === AST_NODE_TYPES.Literal ? (isValidPropertyName(name.value) ? name.value : `'${name.value}'`) : `[${sourceCode.getText(name)}]`}: ${sourceCode.getText(value)},`,
379
- ),
380
- ` },`,
381
- ]
382
- : []),
383
- '}',
384
- ].join(`\n${indentation}`);
385
-
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
- 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}`);
489
-
490
- context.report({
491
- node: fixtureCall,
492
- 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
- }
562
- },
563
- });
564
- } catch (error) {
565
- // eslint-disable-next-line no-console
566
- console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
567
- context.report({
568
- node: fixtureCall,
569
- messageId: 'unknownError',
570
- data: {
571
- fileName: context.filename,
572
- error: error instanceof Error ? error.toString() : JSON.stringify(error),
573
- },
574
- });
575
- }
576
- },
577
- };
578
- },
579
- });
580
-
581
- 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;