@checkdigit/eslint-plugin 6.6.0-PR.75-24d6 → 6.6.0-PR.75-3e31

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.
package/src/no-fixture.ts CHANGED
@@ -10,55 +10,65 @@
10
10
 
11
11
  import type {
12
12
  AwaitExpression,
13
+ CallExpression,
13
14
  Expression,
14
15
  MemberExpression,
15
- Node,
16
16
  ReturnStatement,
17
17
  SimpleCallExpression,
18
+ VariableDeclaration,
18
19
  } from 'estree';
19
20
  import type { Rule, Scope, SourceCode } from 'eslint';
21
+ import { getAncestor, getParent } from './ast/tree';
20
22
  import { strict as assert } from 'node:assert';
21
23
  import getDocumentationUrl from './get-documentation-url';
24
+ import { getIndentation } from './ast/format';
22
25
 
23
26
  export const ruleId = 'no-fixture';
24
27
 
25
- type NodeParent = Node | undefined | null;
26
-
27
- interface NodeParentExtension {
28
- parent: NodeParent;
29
- }
30
-
31
28
  interface FixtureCallInformation {
32
- root: AwaitExpression | ReturnStatement;
29
+ rootNode: AwaitExpression | ReturnStatement | VariableDeclaration;
30
+ fixtureNode: AwaitExpression | SimpleCallExpression;
31
+ variableDeclaration?: VariableDeclaration;
33
32
  requestBody?: Expression;
34
33
  requestHeaders?: { name: Expression; value: Expression }[];
35
34
  assertions?: Expression[][];
36
35
  }
37
36
 
38
- function getParent(node: Node): Node | undefined | null {
39
- return (node as unknown as NodeParentExtension).parent;
40
- }
41
-
42
- function analyze(call: SimpleCallExpression, results: FixtureCallInformation) {
37
+ // recursively analyze the fixture/supertest call chain to collect information of request/response
38
+ function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInformation) {
43
39
  const parent = getParent(call);
44
40
  assert.ok(parent, 'parent should exist for fixture/supertest call node');
45
41
 
46
42
  let nextCall;
47
- if (parent.type === 'AwaitExpression' || parent.type === 'ReturnStatement') {
48
- // no more assertions, return the await expression of the fixture call
49
- results.root = parent;
43
+ if (parent.type === 'ReturnStatement') {
44
+ // direct return, no variable declaration / await
45
+ results.fixtureNode = call;
46
+ results.rootNode = parent;
47
+ } else if (parent.type === 'AwaitExpression') {
48
+ results.fixtureNode = call;
49
+ // [TODO:] should we consider variable declaration without await??
50
+ const variableDeclaration = getAncestor(parent, 'VariableDeclaration', 'FunctionDeclaration');
51
+ if (variableDeclaration?.type === 'VariableDeclaration') {
52
+ results.variableDeclaration = variableDeclaration;
53
+ results.rootNode = variableDeclaration;
54
+ } else {
55
+ results.rootNode = parent;
56
+ }
50
57
  } else if (parent.type === 'MemberExpression' && parent.property.type === 'Identifier') {
51
58
  if (parent.property.name === 'expect') {
59
+ // supertest assertions
52
60
  const assertionCall = getParent(parent);
53
61
  assert.ok(assertionCall && assertionCall.type === 'CallExpression');
54
62
  results.assertions = [...(results.assertions ?? []), assertionCall.arguments as Expression[]];
55
63
  nextCall = assertionCall;
56
64
  } else if (parent.property.name === 'send') {
65
+ // request body
57
66
  const sendRequestBodyCall = getParent(parent);
58
67
  assert.ok(sendRequestBodyCall && sendRequestBodyCall.type === 'CallExpression');
59
68
  results.requestBody = sendRequestBodyCall.arguments[0] as Expression;
60
69
  nextCall = sendRequestBodyCall;
61
70
  } else if (parent.property.name === 'set') {
71
+ // request headers
62
72
  const setRequestHeaderCall = getParent(parent);
63
73
  assert.ok(setRequestHeaderCall && setRequestHeaderCall.type === 'CallExpression');
64
74
  const [name, value] = setRequestHeaderCall.arguments as [Expression, Expression];
@@ -69,10 +79,91 @@ function analyze(call: SimpleCallExpression, results: FixtureCallInformation) {
69
79
  throw new Error(`Unexpected expression in fixture/supertest call ${String(parent)}`);
70
80
  }
71
81
  if (nextCall) {
72
- analyze(nextCall, results);
82
+ analyzeFixtureCall(nextCall, results);
73
83
  }
74
84
  }
75
85
 
86
+ // analyze response related variables and their references0
87
+ function analyzeResponseReferences(fixtureInformation: FixtureCallInformation, scopeManager: Scope.ScopeManager) {
88
+ const results: {
89
+ variable?: Scope.Variable;
90
+ bodyReferences: MemberExpression[];
91
+ headersReferences: MemberExpression[];
92
+ statusReferences: MemberExpression[];
93
+ spreadBodyVariable?: Scope.Variable;
94
+ spreadHeadersVariable?: Scope.Variable;
95
+ } = {
96
+ bodyReferences: [],
97
+ headersReferences: [],
98
+ statusReferences: [],
99
+ };
100
+
101
+ if (fixtureInformation.variableDeclaration) {
102
+ const responseVariables = scopeManager.getDeclaredVariables(fixtureInformation.variableDeclaration);
103
+ for (const responseVariable of responseVariables) {
104
+ const identifier = responseVariable.identifiers[0];
105
+ assert.ok(identifier);
106
+ const identifierParent = getParent(identifier);
107
+ assert.ok(identifierParent);
108
+ if (identifierParent.type === 'VariableDeclarator') {
109
+ // e.g. const response = ...
110
+ results.variable = responseVariable;
111
+ // e.g. response.body
112
+ results.bodyReferences = responseVariable.references
113
+ .map((responseBodyReference) => getParent(responseBodyReference.identifier))
114
+ .filter(
115
+ (node): node is MemberExpression =>
116
+ node !== null &&
117
+ node !== undefined &&
118
+ node.type === 'MemberExpression' &&
119
+ node.property.type === 'Identifier' &&
120
+ node.property.name === 'body',
121
+ );
122
+ // e.g. response.headers / response.header / response.get()
123
+ results.headersReferences = responseVariable.references
124
+ .map((responseHeadersReference) => getParent(responseHeadersReference.identifier))
125
+ .filter(
126
+ (node): node is MemberExpression =>
127
+ node !== null &&
128
+ node !== undefined &&
129
+ node.type === 'MemberExpression' &&
130
+ node.property.type === 'Identifier' &&
131
+ (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
132
+ );
133
+ // e.g. response.status / response.statusCode
134
+ results.statusReferences = responseVariable.references
135
+ .map((responseHeadersReference) => getParent(responseHeadersReference.identifier))
136
+ .filter(
137
+ (node): node is MemberExpression =>
138
+ node !== null &&
139
+ node !== undefined &&
140
+ node.type === 'MemberExpression' &&
141
+ node.property.type === 'Identifier' &&
142
+ (node.property.name === 'status' || node.property.name === 'statusCode'),
143
+ );
144
+ } else if (
145
+ // body reference through destruction/renaming, e.g. "const { body } = ..."
146
+ identifierParent.type === 'Property' &&
147
+ identifierParent.key.type === 'Identifier' &&
148
+ identifierParent.key.name === 'body'
149
+ ) {
150
+ results.spreadBodyVariable = responseVariable;
151
+ } else if (
152
+ // header reference through destruction/renaming, e.g. "const { headers } = ..."
153
+ identifierParent.type === 'Property' &&
154
+ identifierParent.key.type === 'Identifier' &&
155
+ identifierParent.key.name === 'headers'
156
+ ) {
157
+ results.spreadHeadersVariable = responseVariable;
158
+ } else {
159
+ throw new Error(`Unknown response variable reference: ${responseVariable.name}`);
160
+ }
161
+ }
162
+ }
163
+ return results;
164
+ }
165
+
166
+ // `/sample-service/v1/ping` -> `${BASE_PATH}/ping`
76
167
  function replaceEndpointUrlPrefixWithBasePath(url: string) {
77
168
  // eslint-disable-next-line no-template-curly-in-string
78
169
  return url.replace(/`\/\w+(?<parts>-\w+)*\/v\d+\//u, '`${BASE_PATH}/');
@@ -82,9 +173,15 @@ function isValidPropertyName(name: unknown) {
82
173
  return typeof name === 'string' && /^[a-zA-Z_$][a-zA-Z_$0-9]*$/u.test(name);
83
174
  }
84
175
 
85
- function appendAssertions(expects: Expression[][], sourceCode: SourceCode, variableName: string) {
86
- const assertions: string[] = [];
87
- for (const expectArguments of expects) {
176
+ function createResponseAssertions(
177
+ fixtureCallInformation: FixtureCallInformation,
178
+ sourceCode: SourceCode,
179
+ variableName: string,
180
+ ) {
181
+ // [TODO:] make sure status assertion is ordered as the first
182
+ let statusAssertion: string | undefined;
183
+ const nonStatusAssertions: string[] = [];
184
+ for (const expectArguments of fixtureCallInformation.assertions ?? []) {
88
185
  if (expectArguments.length === 1) {
89
186
  const [assertionArgument] = expectArguments;
90
187
  assert.ok(assertionArgument);
@@ -95,16 +192,18 @@ function appendAssertions(expects: Expression[][], sourceCode: SourceCode, varia
95
192
  assertionArgument.type === 'Literal'
96
193
  ) {
97
194
  // status code assertion
98
- assertions.push(`assert.equal(${variableName}.status, ${sourceCode.getText(assertionArgument)})`);
195
+ statusAssertion = `assert.equal(${variableName}.status, ${sourceCode.getText(assertionArgument)})`;
99
196
  } else if (assertionArgument.type === 'ArrowFunctionExpression') {
100
197
  // callback assertion
101
- assertions.push(`assert.ok(${sourceCode.getText(assertionArgument)})`);
198
+ nonStatusAssertions.push(`assert.ok(${sourceCode.getText(assertionArgument)})`);
102
199
  } else if (assertionArgument.type === 'Identifier') {
103
200
  // callback assertion
104
- assertions.push(`assert.ok(${sourceCode.getText(assertionArgument)}(${variableName}))`);
201
+ nonStatusAssertions.push(`assert.ok(${sourceCode.getText(assertionArgument)}(${variableName}))`);
105
202
  } else if (assertionArgument.type === 'ObjectExpression' || assertionArgument.type === 'CallExpression') {
106
203
  // body deep equal assertion
107
- assertions.push(`assert.deepEqual(await ${variableName}.json(), ${sourceCode.getText(assertionArgument)})`);
204
+ nonStatusAssertions.push(
205
+ `assert.deepEqual(await ${variableName}.json(), ${sourceCode.getText(assertionArgument)})`,
206
+ );
108
207
  } else {
109
208
  throw new Error(`Unexpected Supertest assertion argument: ".expect(${sourceCode.getText(assertionArgument)})`);
110
209
  }
@@ -113,75 +212,20 @@ function appendAssertions(expects: Expression[][], sourceCode: SourceCode, varia
113
212
  const [headerName, headerValue] = expectArguments;
114
213
  assert.ok(headerName && headerValue);
115
214
  if (headerValue.type === 'Literal' && headerValue.value instanceof RegExp) {
116
- assertions.push(
215
+ nonStatusAssertions.push(
117
216
  `assert.ok(${variableName}.headers.get(${sourceCode.getText(headerName)}).match(${sourceCode.getText(headerValue)}))`,
118
217
  );
119
218
  } else {
120
- assertions.push(
219
+ nonStatusAssertions.push(
121
220
  `assert.equal(${variableName}.headers.get(${sourceCode.getText(headerName)}), ${sourceCode.getText(headerValue)})`,
122
221
  );
123
222
  }
124
223
  }
125
224
  }
126
- return assertions;
127
- }
128
-
129
- function getAncestor(node: Node, matchType: string, quitType: string) {
130
- const parent = getParent(node);
131
- if (!parent || parent.type === quitType) {
132
- return undefined;
133
- } else if (parent.type === matchType) {
134
- return parent;
135
- }
136
- return getAncestor(parent, matchType, quitType);
137
- }
138
-
139
- function analyzeReferences(fixtureCallAwait: AwaitExpression | ReturnStatement, scopeManager: Scope.ScopeManager) {
140
- const results: {
141
- responseVariable?: Scope.Variable;
142
- responseBodyReferences: MemberExpression[];
143
- responseHeadersReferences: MemberExpression[];
144
- } = {
145
- responseBodyReferences: [],
146
- responseHeadersReferences: [],
225
+ return {
226
+ statusAssertion,
227
+ nonStatusAssertions,
147
228
  };
148
-
149
- const variableDeclaration = getAncestor(fixtureCallAwait, 'VariableDeclaration', 'FunctionDeclaration');
150
- if (variableDeclaration && variableDeclaration.type === 'VariableDeclaration') {
151
- const [responseVariable] = scopeManager.getDeclaredVariables(variableDeclaration);
152
- assert.ok(responseVariable);
153
-
154
- results.responseVariable = responseVariable;
155
- results.responseBodyReferences = responseVariable.references
156
- .map((responseBodyReference) => getParent(responseBodyReference.identifier))
157
- .filter(
158
- (node): node is MemberExpression =>
159
- node !== null &&
160
- node !== undefined &&
161
- node.type === 'MemberExpression' &&
162
- node.property.type === 'Identifier' &&
163
- node.property.name === 'body',
164
- );
165
- results.responseHeadersReferences = responseVariable.references
166
- .map((responseHeadersReference) => getParent(responseHeadersReference.identifier))
167
- .filter(
168
- (node): node is MemberExpression =>
169
- node !== null &&
170
- node !== undefined &&
171
- node.type === 'MemberExpression' &&
172
- node.property.type === 'Identifier' &&
173
- (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
174
- );
175
- }
176
- return results;
177
- }
178
-
179
- function getIndentation(node: Node, sourceCode: SourceCode) {
180
- assert.ok(node.loc);
181
- const line = sourceCode.lines[node.loc.start.line - 1];
182
- assert.ok(line);
183
- const indentMatch = line.match(/^\s*/u);
184
- return indentMatch ? indentMatch[0] : '';
185
229
  }
186
230
 
187
231
  const rule: Rule.RuleModule = {
@@ -202,105 +246,123 @@ const rule: Rule.RuleModule = {
202
246
  create(context) {
203
247
  const sourceCode = context.sourceCode;
204
248
  const scopeManager = sourceCode.scopeManager;
205
- let variableCounter = 0;
249
+ let responseVariableCounter = 0;
206
250
 
207
251
  return {
208
- 'CallExpression[callee.object.object.name="fixture"][callee.object.property.name="api"]': (fixtureCall: Node) => {
252
+ 'CallExpression[callee.object.object.name="fixture"][callee.object.property.name="api"]': (
253
+ fixtureCall: CallExpression,
254
+ ) => {
209
255
  try {
210
256
  assert.ok(fixtureCall.type === 'CallExpression');
211
- const fixtureFunction = fixtureCall.callee; // node - fixture.api.get
257
+ const fixtureFunction = fixtureCall.callee; // e.g. fixture.api.get
212
258
  assert.ok(fixtureFunction.type === 'MemberExpression');
213
- const methodNode = fixtureFunction.property; // get/put/etc.
214
- assert.ok(methodNode.type === 'Identifier');
215
259
  const indentation = getIndentation(fixtureCall, sourceCode);
216
260
 
217
- const [urlArgumentNode] = fixtureCall.arguments; // node - `/smartdata/v1/ping`
261
+ const [urlArgumentNode] = fixtureCall.arguments; // e.g. `/sample-service/v1/ping`
218
262
  assert.ok(urlArgumentNode !== undefined);
219
263
 
220
264
  const fixtureCallInformation = {} as FixtureCallInformation;
221
- analyze(fixtureCall, fixtureCallInformation);
222
-
223
- const { responseVariable, responseBodyReferences, responseHeadersReferences } = analyzeReferences(
224
- fixtureCallInformation.root,
225
- scopeManager,
226
- );
227
- let variableNameToUse: string;
228
- let isResponseVariableDeclared = false;
265
+ analyzeFixtureCall(fixtureCall, fixtureCallInformation);
266
+
267
+ const {
268
+ variable: responseVariable,
269
+ bodyReferences: responseBodyReferences,
270
+ headersReferences: responseHeadersReferences,
271
+ statusReferences: responseStatusReferences,
272
+ spreadBodyVariable: spreadResponseBodyVariable,
273
+ spreadHeadersVariable: spreadResponseHeadersVariable,
274
+ } = analyzeResponseReferences(fixtureCallInformation, scopeManager);
275
+
276
+ // convert url from `/sample-service/v1/ping` to `${BASE_PATH}/ping`
277
+ const originalUrlArgumentText = sourceCode.getText(urlArgumentNode);
278
+ const fetchUrlArgumentText = replaceEndpointUrlPrefixWithBasePath(originalUrlArgumentText);
279
+
280
+ // fetch request argument
281
+ const methodNode = fixtureFunction.property; // get/put/etc.
282
+ assert.ok(methodNode.type === 'Identifier');
283
+ const fetchRequestArgumentLines = [
284
+ '{',
285
+ ` method: '${methodNode.name.toUpperCase()}',`,
286
+ ...(fixtureCallInformation.requestBody
287
+ ? [` body: JSON.stringify(${sourceCode.getText(fixtureCallInformation.requestBody)}),`]
288
+ : []),
289
+ ...(fixtureCallInformation.requestHeaders
290
+ ? [
291
+ ` headers: {`,
292
+ ...fixtureCallInformation.requestHeaders.map(
293
+ ({ name, value }) =>
294
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, no-nested-ternary, sonarjs/no-nested-template-literals
295
+ ` ${name.type === 'Literal' ? (isValidPropertyName(name.value) ? name.value : `'${name.value}'`) : `[${sourceCode.getText(name)}]`}: ${sourceCode.getText(value)},`,
296
+ ),
297
+ ` },`,
298
+ ]
299
+ : []),
300
+ '}',
301
+ ].join(`\n${indentation}`);
302
+
303
+ let responseVariableNameToUse: string;
229
304
  if (responseVariable === undefined) {
230
- variableNameToUse = `response${variableCounter === 0 ? '' : variableCounter.toString()}`;
231
- variableCounter++;
305
+ responseVariableNameToUse = `response${responseVariableCounter === 0 ? '' : responseVariableCounter.toString()}`;
306
+ responseVariableCounter++;
232
307
  } else {
233
- isResponseVariableDeclared = true;
234
- variableNameToUse = responseVariable.name;
308
+ responseVariableNameToUse = responseVariable.name;
235
309
  }
236
310
 
237
- // convert fixture.api.get to fetch
238
- const fixtureApiCallText = sourceCode.getText(fixtureCall); // e.g. "fixture.api.get(`/smartdata/v1/ping`)""
239
- const fixtureMethodText = sourceCode.getText(fixtureFunction); // e.g. "fixture.api.get"
240
-
241
- const fetchStatementStart =
242
- fixtureCallInformation.root.type === 'ReturnStatement' && fixtureCallInformation.assertions === undefined
243
- ? 'return'
244
- : 'await';
245
- let replacedText = fixtureApiCallText.replace(fixtureMethodText, `${fetchStatementStart} fetch`);
246
-
247
- // convert `/smartdata/v1/ping` to `${BASE_PATH}/ping`
248
- const fixtureArgumentText = sourceCode.getText(urlArgumentNode); // text - e.g. `/smartdata/v1/ping`
249
- let fetchArgumentText = replaceEndpointUrlPrefixWithBasePath(fixtureArgumentText); // test - e.g. `${BASE_PATH}/ping`
250
-
251
- // add request argument if deeded
252
- if (
253
- methodNode.name !== 'get' ||
254
- fixtureCallInformation.requestBody !== undefined ||
255
- fixtureCallInformation.requestHeaders !== undefined
256
- ) {
257
- fetchArgumentText += [
258
- ', {',
259
- ` method: '${methodNode.name.toUpperCase()}',`,
260
- ...(fixtureCallInformation.requestBody
261
- ? [` body: JSON.stringify(${sourceCode.getText(fixtureCallInformation.requestBody)}),`]
262
- : []),
263
- ...(fixtureCallInformation.requestHeaders
264
- ? [
265
- ` headers: {`,
266
- ...fixtureCallInformation.requestHeaders.map(
267
- ({ name, value }) =>
268
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, no-nested-ternary, sonarjs/no-nested-template-literals
269
- ` ${name.type === 'Literal' ? (isValidPropertyName(name.value) ? name.value : `'${name.value}'`) : `[${sourceCode.getText(name)}]`}: ${sourceCode.getText(value)},`,
270
- ),
271
- ` },`,
272
- ]
273
- : []),
274
- '}',
275
- ].join(`\n${indentation}`);
276
- }
311
+ const needResponseVariableRedefine =
312
+ spreadResponseBodyVariable !== undefined ||
313
+ (responseVariable === undefined && fixtureCallInformation.assertions !== undefined);
314
+
315
+ const responseBodyHeadersVariableRedefineLines = needResponseVariableRedefine
316
+ ? [
317
+ ...(spreadResponseBodyVariable
318
+ ? [`const ${spreadResponseBodyVariable.name} = await ${responseVariableNameToUse}.json()`]
319
+ : []),
320
+ ...(spreadResponseHeadersVariable
321
+ ? [`const ${spreadResponseHeadersVariable.name} = ${responseVariableNameToUse}.headers`]
322
+ : []),
323
+ ]
324
+ : [];
325
+
326
+ const { statusAssertion, nonStatusAssertions } = createResponseAssertions(
327
+ fixtureCallInformation,
328
+ sourceCode,
329
+ responseVariableNameToUse,
330
+ );
277
331
 
278
- replacedText = replacedText.replace(fixtureArgumentText, fetchArgumentText);
279
-
280
- if (fixtureCallInformation.assertions) {
281
- // add variable declaration if needed
282
- if (!isResponseVariableDeclared) {
283
- replacedText = `const ${variableNameToUse} = ${replacedText}`;
284
- }
285
- // externalize response assertions
286
- replacedText = [
287
- replacedText,
288
- ...appendAssertions(fixtureCallInformation.assertions, sourceCode, variableNameToUse),
289
- ].join(`;\n${indentation}`);
290
- }
332
+ // add variable declaration if needed
333
+ const fetchCallText = `fetch(${fetchUrlArgumentText}, ${fetchRequestArgumentLines})`;
334
+ const fetchStatementText = !needResponseVariableRedefine
335
+ ? fetchCallText
336
+ : `const ${responseVariableNameToUse} = await ${fetchCallText}`;
337
+
338
+ const nodeToReplace = needResponseVariableRedefine
339
+ ? fixtureCallInformation.rootNode
340
+ : fixtureCallInformation.fixtureNode;
341
+ const appendingAssignmentAndAssertionText = [
342
+ '',
343
+ ...(statusAssertion !== undefined ? [statusAssertion] : []),
344
+ ...responseBodyHeadersVariableRedefineLines,
345
+ ...nonStatusAssertions,
346
+ ].join(`;\n${indentation}`);
291
347
 
292
348
  context.report({
293
349
  node: fixtureCall,
294
350
  messageId: 'preferNativeFetch',
295
351
  *fix(fixer) {
296
- yield fixer.replaceText(fixtureCallInformation.root, replacedText);
352
+ yield fixer.replaceText(nodeToReplace, fetchStatementText);
353
+
354
+ const needEndingSemiColon = sourceCode.getText(nodeToReplace).endsWith(';');
355
+ yield fixer.insertTextAfter(
356
+ nodeToReplace,
357
+ needEndingSemiColon ? `${appendingAssignmentAndAssertionText};` : appendingAssignmentAndAssertionText,
358
+ );
297
359
 
298
- // handle response body
360
+ // handle response body references
299
361
  for (const responseBodyReference of responseBodyReferences) {
300
- yield fixer.replaceText(responseBodyReference, `await ${variableNameToUse}.json()`);
362
+ yield fixer.replaceText(responseBodyReference, `await ${responseVariableNameToUse}.json()`);
301
363
  }
302
364
 
303
- // handle response headers
365
+ // handle response headers references
304
366
  for (const responseHeadersReference of responseHeadersReferences) {
305
367
  const parent = getParent(responseHeadersReference);
306
368
  assert.ok(parent);
@@ -315,38 +377,33 @@ const rule: Rule.RuleModule = {
315
377
  headerName = sourceCode.getText(headerNameNode);
316
378
  }
317
379
  assert.ok(headerName);
318
- yield fixer.replaceText(parent, `${variableNameToUse}.headers.get(${headerName})`);
380
+ yield fixer.replaceText(parent, `${responseVariableNameToUse}.headers.get(${headerName})`);
381
+ }
382
+
383
+ // convert response.statusCode to response.status
384
+ for (const responseStatusReference of responseStatusReferences) {
385
+ if (
386
+ responseStatusReference.property.type === 'Identifier' &&
387
+ responseStatusReference.property.name === 'statusCode'
388
+ ) {
389
+ yield fixer.replaceText(responseStatusReference.property, `status`);
390
+ }
319
391
  }
320
392
 
321
393
  // handle direct return without await
322
394
  if (
323
- fixtureCallInformation.root.type === 'ReturnStatement' &&
395
+ fixtureCallInformation.rootNode.type === 'ReturnStatement' &&
324
396
  fixtureCallInformation.assertions !== undefined
325
397
  ) {
326
398
  yield fixer.insertTextAfter(
327
- fixtureCallInformation.root,
328
- `;\n${indentation}return ${variableNameToUse};`,
399
+ fixtureCallInformation.rootNode,
400
+ `\n${indentation}return ${responseVariableNameToUse};`,
329
401
  );
330
402
  }
331
-
332
- // convert statusCode to status
333
- function* statusCodeReplacer(reference: Scope.Reference) {
334
- const parent = getParent(reference.identifier);
335
- if (
336
- parent?.type === 'MemberExpression' &&
337
- parent.property.type === 'Identifier' &&
338
- parent.property.name === 'statusCode'
339
- ) {
340
- yield fixer.replaceText(parent.property, 'status');
341
- }
342
- }
343
- for (const reference of responseVariable?.references ?? []) {
344
- yield* statusCodeReplacer(reference);
345
- }
346
403
  },
347
404
  });
348
405
  } catch (error) {
349
- console.log(error);
406
+ console.error(`Failed to apply ${ruleId} rule. Error:`, error);
350
407
  context.report({
351
408
  node: fixtureCall,
352
409
  messageId: 'unknownError',
@@ -359,4 +416,5 @@ const rule: Rule.RuleModule = {
359
416
  };
360
417
  },
361
418
  };
419
+
362
420
  export default rule;