@checkdigit/eslint-plugin 6.6.0-PR.75-b2d2 → 6.6.0-PR.75-1cd1

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
@@ -6,8 +6,6 @@
6
6
  * This code is licensed under the MIT license (see LICENSE.txt for details).
7
7
  */
8
8
 
9
- /* eslint-disable no-console */
10
-
11
9
  import type {
12
10
  AwaitExpression,
13
11
  CallExpression,
@@ -18,8 +16,8 @@ import type {
18
16
  SimpleCallExpression,
19
17
  VariableDeclaration,
20
18
  } from 'estree';
21
- import type { Rule, Scope, SourceCode } from 'eslint';
22
- import { getAncestor, getParent } from './ast/tree';
19
+ import { type Rule, type Scope, SourceCode } from 'eslint';
20
+ import { getEnclosingScopeNode, getEnclosingStatement, getParent } from './ast/tree';
23
21
  import { strict as assert } from 'node:assert';
24
22
  import getDocumentationUrl from './get-documentation-url';
25
23
  import { getIndentation } from './ast/format';
@@ -33,10 +31,12 @@ interface FixtureCallInformation {
33
31
  requestBody?: Expression;
34
32
  requestHeaders?: { name: Expression; value: Expression }[];
35
33
  assertions?: Expression[][];
34
+ inlineStatementNode?: Node;
35
+ inlineBodyReference?: MemberExpression;
36
36
  }
37
37
 
38
38
  // recursively analyze the fixture/supertest call chain to collect information of request/response
39
- function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInformation) {
39
+ function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInformation, sourceCode: SourceCode) {
40
40
  const parent = getParent(call);
41
41
  assert.ok(parent, 'parent should exist for fixture/supertest call node');
42
42
 
@@ -47,11 +47,18 @@ function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInfo
47
47
  results.rootNode = parent;
48
48
  } else if (parent.type === 'AwaitExpression') {
49
49
  results.fixtureNode = call;
50
- // [TODO:] should we consider variable declaration without await??
51
- const variableDeclaration = getAncestor(parent, 'VariableDeclaration', 'FunctionDeclaration');
52
- if (variableDeclaration?.type === 'VariableDeclaration') {
53
- results.variableDeclaration = variableDeclaration;
54
- results.rootNode = variableDeclaration;
50
+ const enclosingStatement = getEnclosingStatement(parent);
51
+ assert.ok(enclosingStatement);
52
+ const awaitParent = getParent(parent);
53
+ if (awaitParent?.type === 'MemberExpression') {
54
+ results.rootNode = parent;
55
+ results.inlineStatementNode = enclosingStatement;
56
+ if (awaitParent.property.type === 'Identifier' && awaitParent.property.name === 'body') {
57
+ results.inlineBodyReference = awaitParent;
58
+ }
59
+ } else if (enclosingStatement.type === 'VariableDeclaration') {
60
+ results.variableDeclaration = enclosingStatement;
61
+ results.rootNode = enclosingStatement;
55
62
  } else {
56
63
  results.rootNode = parent;
57
64
  }
@@ -77,10 +84,10 @@ function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInfo
77
84
  nextCall = setRequestHeaderCall;
78
85
  }
79
86
  } else {
80
- throw new Error(`Unexpected expression in fixture/supertest call ${String(parent)}`);
87
+ throw new Error(`Unexpected expression in fixture/supertest call ${sourceCode.getText(parent)}`);
81
88
  }
82
89
  if (nextCall) {
83
- analyzeFixtureCall(nextCall, results);
90
+ analyzeFixtureCall(nextCall, results, sourceCode);
84
91
  }
85
92
  }
86
93
 
@@ -91,8 +98,9 @@ function analyzeResponseReferences(fixtureInformation: FixtureCallInformation, s
91
98
  bodyReferences: MemberExpression[];
92
99
  headersReferences: MemberExpression[];
93
100
  statusReferences: MemberExpression[];
94
- spreadBodyVariable?: Scope.Variable;
95
- spreadHeadersVariable?: Scope.Variable;
101
+ destructuringBodyVariable?: Scope.Variable;
102
+ destructuringHeadersVariable?: Scope.Variable;
103
+ destructuringHeadersReferences?: MemberExpression[] | undefined;
96
104
  } = {
97
105
  bodyReferences: [],
98
106
  headersReferences: [],
@@ -102,6 +110,7 @@ function analyzeResponseReferences(fixtureInformation: FixtureCallInformation, s
102
110
  if (fixtureInformation.variableDeclaration) {
103
111
  const responseVariables = scopeManager.getDeclaredVariables(fixtureInformation.variableDeclaration);
104
112
  for (const responseVariable of responseVariables) {
113
+ const scope = responseVariable.scope;
105
114
  const identifier = responseVariable.identifiers[0];
106
115
  assert.ok(identifier);
107
116
  const identifierParent = getParent(identifier);
@@ -109,53 +118,61 @@ function analyzeResponseReferences(fixtureInformation: FixtureCallInformation, s
109
118
  if (identifierParent.type === 'VariableDeclarator') {
110
119
  // e.g. const response = ...
111
120
  results.variable = responseVariable;
121
+ const responseReferences = responseVariable.references.map((responseReference) =>
122
+ getParent(responseReference.identifier),
123
+ );
112
124
  // e.g. response.body
113
- results.bodyReferences = responseVariable.references
114
- .map((responseBodyReference) => getParent(responseBodyReference.identifier))
115
- .filter(
116
- (node): node is MemberExpression =>
117
- node !== null &&
118
- node !== undefined &&
119
- node.type === 'MemberExpression' &&
120
- node.property.type === 'Identifier' &&
121
- node.property.name === 'body',
122
- );
125
+ results.bodyReferences = responseReferences.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 === 'body',
132
+ );
123
133
  // e.g. response.headers / response.header / response.get()
124
- results.headersReferences = responseVariable.references
125
- .map((responseHeadersReference) => getParent(responseHeadersReference.identifier))
126
- .filter(
127
- (node): node is MemberExpression =>
128
- node !== null &&
129
- node !== undefined &&
130
- node.type === 'MemberExpression' &&
131
- node.property.type === 'Identifier' &&
132
- (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
133
- );
134
+ results.headersReferences = responseReferences.filter(
135
+ (node): node is MemberExpression =>
136
+ node !== null &&
137
+ node !== undefined &&
138
+ node.type === 'MemberExpression' &&
139
+ node.property.type === 'Identifier' &&
140
+ (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
141
+ );
134
142
  // e.g. response.status / response.statusCode
135
- results.statusReferences = responseVariable.references
136
- .map((responseHeadersReference) => getParent(responseHeadersReference.identifier))
137
- .filter(
138
- (node): node is MemberExpression =>
139
- node !== null &&
140
- node !== undefined &&
141
- node.type === 'MemberExpression' &&
142
- node.property.type === 'Identifier' &&
143
- (node.property.name === 'status' || node.property.name === 'statusCode'),
144
- );
143
+ results.statusReferences = responseReferences.filter(
144
+ (node): node is MemberExpression =>
145
+ node !== null &&
146
+ node !== undefined &&
147
+ node.type === 'MemberExpression' &&
148
+ node.property.type === 'Identifier' &&
149
+ (node.property.name === 'status' || node.property.name === 'statusCode'),
150
+ );
145
151
  } else if (
146
152
  // body reference through destruction/renaming, e.g. "const { body } = ..."
147
153
  identifierParent.type === 'Property' &&
148
154
  identifierParent.key.type === 'Identifier' &&
149
155
  identifierParent.key.name === 'body'
150
156
  ) {
151
- results.spreadBodyVariable = responseVariable;
157
+ results.destructuringBodyVariable = responseVariable;
152
158
  } else if (
153
159
  // header reference through destruction/renaming, e.g. "const { headers } = ..."
154
160
  identifierParent.type === 'Property' &&
155
161
  identifierParent.key.type === 'Identifier' &&
156
162
  identifierParent.key.name === 'headers'
157
163
  ) {
158
- results.spreadHeadersVariable = responseVariable;
164
+ results.destructuringHeadersVariable = responseVariable;
165
+ results.destructuringHeadersReferences = scope.set
166
+ .get(responseVariable.name)
167
+ ?.references.map((reference) => reference.identifier)
168
+ .map(getParent)
169
+ .filter(
170
+ (parent): parent is MemberExpression =>
171
+ parent?.type === 'MemberExpression' &&
172
+ parent.property.type === 'Identifier' &&
173
+ parent.property.name !== 'get' &&
174
+ getParent(parent)?.type !== 'CallExpression',
175
+ );
159
176
  } else {
160
177
  throw new Error(`Unknown response variable reference: ${responseVariable.name}`);
161
178
  }
@@ -174,12 +191,13 @@ function isValidPropertyName(name: unknown) {
174
191
  return typeof name === 'string' && /^[a-zA-Z_$][a-zA-Z_$0-9]*$/u.test(name);
175
192
  }
176
193
 
194
+ // eslint-disable-next-line sonarjs/cognitive-complexity
177
195
  function createResponseAssertions(
178
196
  fixtureCallInformation: FixtureCallInformation,
179
197
  sourceCode: SourceCode,
180
- variableName: string,
198
+ responseVariableName: string,
199
+ destructuringResponseHeadersVariable: Scope.Variable | undefined,
181
200
  ) {
182
- // [TODO:] make sure status assertion is ordered as the first
183
201
  let statusAssertion: string | undefined;
184
202
  const nonStatusAssertions: string[] = [];
185
203
  for (const expectArguments of fixtureCallInformation.assertions ?? []) {
@@ -190,20 +208,32 @@ function createResponseAssertions(
190
208
  (assertionArgument.type === 'MemberExpression' &&
191
209
  assertionArgument.object.type === 'Identifier' &&
192
210
  assertionArgument.object.name === 'StatusCodes') ||
193
- assertionArgument.type === 'Literal'
211
+ assertionArgument.type === 'Literal' ||
212
+ sourceCode.getText(assertionArgument).includes('StatusCodes.')
194
213
  ) {
195
214
  // status code assertion
196
- statusAssertion = `assert.equal(${variableName}.status, ${sourceCode.getText(assertionArgument)})`;
215
+ statusAssertion = `assert.equal(${responseVariableName}.status, ${sourceCode.getText(assertionArgument)})`;
197
216
  } else if (assertionArgument.type === 'ArrowFunctionExpression') {
198
- // callback assertion
199
- nonStatusAssertions.push(`assert.ok(${sourceCode.getText(assertionArgument)})`);
217
+ // callback assertion using arrow function
218
+ let functionBody = sourceCode.getText(assertionArgument.body);
219
+
220
+ const [originalResponseArgument] = assertionArgument.params;
221
+ assert.ok(originalResponseArgument?.type === 'Identifier');
222
+ const originalResponseArgumentName = originalResponseArgument.name;
223
+ if (originalResponseArgumentName !== responseVariableName) {
224
+ functionBody = functionBody.replace(
225
+ new RegExp(`\\b${originalResponseArgumentName}\\b`, 'ug'),
226
+ responseVariableName,
227
+ );
228
+ }
229
+ nonStatusAssertions.push(`assert.ok(${functionBody})`);
200
230
  } else if (assertionArgument.type === 'Identifier') {
201
- // callback assertion
202
- nonStatusAssertions.push(`assert.ok(${sourceCode.getText(assertionArgument)}(${variableName}))`);
231
+ // callback assertion using function reference
232
+ nonStatusAssertions.push(`assert.ok(${sourceCode.getText(assertionArgument)}(${responseVariableName}))`);
203
233
  } else if (assertionArgument.type === 'ObjectExpression' || assertionArgument.type === 'CallExpression') {
204
234
  // body deep equal assertion
205
235
  nonStatusAssertions.push(
206
- `assert.deepEqual(await ${variableName}.json(), ${sourceCode.getText(assertionArgument)})`,
236
+ `assert.deepEqual(await ${responseVariableName}.json(), ${sourceCode.getText(assertionArgument)})`,
207
237
  );
208
238
  } else {
209
239
  throw new Error(`Unexpected Supertest assertion argument: ".expect(${sourceCode.getText(assertionArgument)})`);
@@ -212,13 +242,17 @@ function createResponseAssertions(
212
242
  // header assertion
213
243
  const [headerName, headerValue] = expectArguments;
214
244
  assert.ok(headerName && headerValue);
245
+ const headersReference =
246
+ destructuringResponseHeadersVariable !== undefined
247
+ ? destructuringResponseHeadersVariable.name
248
+ : `${responseVariableName}.headers`;
215
249
  if (headerValue.type === 'Literal' && headerValue.value instanceof RegExp) {
216
250
  nonStatusAssertions.push(
217
- `assert.ok(${variableName}.headers.get(${sourceCode.getText(headerName)}).match(${sourceCode.getText(headerValue)}))`,
251
+ `assert.ok(${headersReference}.get(${sourceCode.getText(headerName)}).match(${sourceCode.getText(headerValue)}))`,
218
252
  );
219
253
  } else {
220
254
  nonStatusAssertions.push(
221
- `assert.equal(${variableName}.headers.get(${sourceCode.getText(headerName)}), ${sourceCode.getText(headerValue)})`,
255
+ `assert.equal(${headersReference}.get(${sourceCode.getText(headerName)}), ${sourceCode.getText(headerValue)})`,
222
256
  );
223
257
  }
224
258
  }
@@ -236,18 +270,15 @@ function getResponseVariableNameToUse(
236
270
  ) {
237
271
  if (fixtureCallInformation.variableDeclaration) {
238
272
  const firstDeclaration = fixtureCallInformation.variableDeclaration.declarations[0];
239
- // [TODO:] double check if it works for destruction/rename declaration
240
273
  if (firstDeclaration && firstDeclaration.id.type === 'Identifier') {
241
274
  return firstDeclaration.id.name;
242
275
  }
243
276
  }
244
277
 
245
- const closestFunctionExpression = getAncestor(fixtureCallInformation.rootNode, (node: Node) =>
246
- ['FunctionExpression', 'ArrowFunctionExpression'].includes(node.type),
247
- );
248
- scopeManager.getDeclaredVariables(fixtureCallInformation.rootNode); /*?*/
249
- assert.ok(closestFunctionExpression);
250
- const scope = scopeManager.acquire(closestFunctionExpression);
278
+ const enclosingScopeNode = getEnclosingScopeNode(fixtureCallInformation.rootNode);
279
+ scopeManager.getDeclaredVariables(fixtureCallInformation.rootNode);
280
+ assert.ok(enclosingScopeNode);
281
+ const scope = scopeManager.acquire(enclosingScopeNode);
251
282
  assert.ok(scope !== null);
252
283
  let scopeVariables = scopeVariablesMap.get(scope);
253
284
  if (!scopeVariables) {
@@ -268,6 +299,15 @@ function getResponseVariableNameToUse(
268
299
  return responseVariableNameToUse;
269
300
  }
270
301
 
302
+ function isResponseBodyRedefinition(responseBodyReference: MemberExpression): boolean {
303
+ const parent = getParent(responseBodyReference);
304
+ return parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier';
305
+ }
306
+
307
+ function getResponseBodyRetrievalText(responseVariableName: string) {
308
+ return `await ${responseVariableName}.json()`;
309
+ }
310
+
271
311
  const rule: Rule.RuleModule = {
272
312
  meta: {
273
313
  type: 'suggestion',
@@ -278,17 +318,19 @@ const rule: Rule.RuleModule = {
278
318
  messages: {
279
319
  preferNativeFetch: 'Prefer native fetch API over customized fixture API.',
280
320
  unknownError:
281
- 'Unknown error occurred: {{ error }}. Please manually convert the fixture API call to fetch API call.',
321
+ 'Unknown error occurred in file "{{fileName}}": {{ error }}. Please manually convert the fixture API call to fetch API call.',
282
322
  },
283
323
  fixable: 'code',
284
324
  schema: [],
285
325
  },
326
+ // eslint-disable-next-line max-lines-per-function
286
327
  create(context) {
287
328
  const sourceCode = context.sourceCode;
288
329
  const scopeManager = sourceCode.scopeManager;
289
330
  const scopeVariablesMap = new Map<Scope.Scope, string[]>();
290
331
 
291
332
  return {
333
+ // eslint-disable-next-line max-lines-per-function
292
334
  'CallExpression[callee.object.object.name="fixture"][callee.object.property.name="api"]': (
293
335
  fixtureCall: CallExpression,
294
336
  ) => {
@@ -302,15 +344,16 @@ const rule: Rule.RuleModule = {
302
344
  assert.ok(urlArgumentNode !== undefined);
303
345
 
304
346
  const fixtureCallInformation = {} as FixtureCallInformation;
305
- analyzeFixtureCall(fixtureCall, fixtureCallInformation);
347
+ analyzeFixtureCall(fixtureCall, fixtureCallInformation, sourceCode);
306
348
 
307
349
  const {
308
350
  variable: responseVariable,
309
351
  bodyReferences: responseBodyReferences,
310
352
  headersReferences: responseHeadersReferences,
311
353
  statusReferences: responseStatusReferences,
312
- spreadBodyVariable: spreadResponseBodyVariable,
313
- spreadHeadersVariable: spreadResponseHeadersVariable,
354
+ destructuringBodyVariable: destructuringResponseBodyVariable,
355
+ destructuringHeadersVariable: destructuringResponseHeadersVariable,
356
+ // destructuringHeadersReferences: destructuringResponseHeadersReferences,
314
357
  } = analyzeResponseReferences(fixtureCallInformation, scopeManager);
315
358
 
316
359
  // convert url from `/sample-service/v1/ping` to `${BASE_PATH}/ping`
@@ -346,17 +389,30 @@ const rule: Rule.RuleModule = {
346
389
  scopeVariablesMap,
347
390
  );
348
391
 
349
- const needResponseVariableRedefine =
350
- spreadResponseBodyVariable !== undefined ||
351
- (responseVariable === undefined && fixtureCallInformation.assertions !== undefined);
392
+ const isResponseBodyVariableRedefinitionNeeded =
393
+ destructuringResponseBodyVariable !== undefined ||
394
+ fixtureCallInformation.inlineBodyReference !== undefined ||
395
+ (responseBodyReferences.length > 0 && !responseBodyReferences.some(isResponseBodyRedefinition));
396
+ const redefineResponseBodyVariableName = `${responseVariableNameToUse}Body`;
397
+
398
+ const isResponseVariableRedefinitionNeeded =
399
+ (responseVariable === undefined && fixtureCallInformation.assertions !== undefined) ||
400
+ isResponseBodyVariableRedefinitionNeeded;
352
401
 
353
- const responseBodyHeadersVariableRedefineLines = needResponseVariableRedefine
402
+ const responseBodyHeadersVariableRedefineLines = isResponseVariableRedefinitionNeeded
354
403
  ? [
355
- ...(spreadResponseBodyVariable
356
- ? [`const ${spreadResponseBodyVariable.name} = await ${responseVariableNameToUse}.json()`]
357
- : []),
358
- ...(spreadResponseHeadersVariable
359
- ? [`const ${spreadResponseHeadersVariable.name} = ${responseVariableNameToUse}.headers`]
404
+ // eslint-disable-next-line no-nested-ternary
405
+ ...(destructuringResponseBodyVariable
406
+ ? [
407
+ `const ${destructuringResponseBodyVariable.name} = ${getResponseBodyRetrievalText(responseVariableNameToUse)}`,
408
+ ]
409
+ : isResponseBodyVariableRedefinitionNeeded
410
+ ? [
411
+ `const ${redefineResponseBodyVariableName} = ${getResponseBodyRetrievalText(responseVariableNameToUse)}`,
412
+ ]
413
+ : []),
414
+ ...(destructuringResponseHeadersVariable
415
+ ? [`const ${destructuringResponseHeadersVariable.name} = ${responseVariableNameToUse}.headers`]
360
416
  : []),
361
417
  ]
362
418
  : [];
@@ -365,15 +421,16 @@ const rule: Rule.RuleModule = {
365
421
  fixtureCallInformation,
366
422
  sourceCode,
367
423
  responseVariableNameToUse,
424
+ destructuringResponseHeadersVariable,
368
425
  );
369
426
 
370
427
  // add variable declaration if needed
371
428
  const fetchCallText = `fetch(${fetchUrlArgumentText}, ${fetchRequestArgumentLines})`;
372
- const fetchStatementText = !needResponseVariableRedefine
429
+ const fetchStatementText = !isResponseVariableRedefinitionNeeded
373
430
  ? fetchCallText
374
431
  : `const ${responseVariableNameToUse} = await ${fetchCallText}`;
375
432
 
376
- const nodeToReplace = needResponseVariableRedefine
433
+ const nodeToReplace = isResponseVariableRedefinitionNeeded
377
434
  ? fixtureCallInformation.rootNode
378
435
  : fixtureCallInformation.fixtureNode;
379
436
  const appendingAssignmentAndAssertionText = [
@@ -386,18 +443,35 @@ const rule: Rule.RuleModule = {
386
443
  context.report({
387
444
  node: fixtureCall,
388
445
  messageId: 'preferNativeFetch',
446
+ // eslint-disable-next-line sonarjs/cognitive-complexity
389
447
  *fix(fixer) {
390
- yield fixer.replaceText(nodeToReplace, fetchStatementText);
391
-
392
- const needEndingSemiColon = sourceCode.getText(nodeToReplace).endsWith(';');
393
- yield fixer.insertTextAfter(
394
- nodeToReplace,
395
- needEndingSemiColon ? `${appendingAssignmentAndAssertionText};` : appendingAssignmentAndAssertionText,
396
- );
448
+ if (fixtureCallInformation.inlineStatementNode) {
449
+ const preInlineDeclaration = [
450
+ fetchStatementText,
451
+ `${appendingAssignmentAndAssertionText};\n${indentation}`,
452
+ ].join(``);
453
+ yield fixer.insertTextBefore(fixtureCallInformation.inlineStatementNode, preInlineDeclaration);
454
+ } else {
455
+ yield fixer.replaceText(nodeToReplace, fetchStatementText);
456
+
457
+ const needEndingSemiColon = sourceCode.getText(nodeToReplace).endsWith(';');
458
+ yield fixer.insertTextAfter(
459
+ nodeToReplace,
460
+ needEndingSemiColon ? `${appendingAssignmentAndAssertionText};` : appendingAssignmentAndAssertionText,
461
+ );
462
+ }
397
463
 
398
464
  // handle response body references
399
465
  for (const responseBodyReference of responseBodyReferences) {
400
- yield fixer.replaceText(responseBodyReference, `await ${responseVariableNameToUse}.json()`);
466
+ yield fixer.replaceText(
467
+ responseBodyReference,
468
+ isResponseBodyVariableRedefinitionNeeded || !isResponseBodyRedefinition(responseBodyReference)
469
+ ? redefineResponseBodyVariableName
470
+ : getResponseBodyRetrievalText(responseVariableNameToUse),
471
+ );
472
+ }
473
+ if (fixtureCallInformation.inlineBodyReference) {
474
+ yield fixer.replaceText(fixtureCallInformation.inlineBodyReference, redefineResponseBodyVariableName);
401
475
  }
402
476
 
403
477
  // handle response headers references
@@ -414,9 +488,22 @@ const rule: Rule.RuleModule = {
414
488
  const headerNameNode = parent.arguments[0];
415
489
  headerName = sourceCode.getText(headerNameNode);
416
490
  }
417
- assert.ok(headerName);
491
+ assert.ok(headerName !== undefined);
418
492
  yield fixer.replaceText(parent, `${responseVariableNameToUse}.headers.get(${headerName})`);
419
493
  }
494
+ // if (destructuringResponseHeadersVariable !== undefined) {
495
+ // for (const destructuringResponseHeadersReference of destructuringResponseHeadersReferences ?? []) {
496
+ // const headerNameNode = destructuringResponseHeadersReference.property;
497
+ // const headerName = destructuringResponseHeadersReference.computed
498
+ // ? sourceCode.getText(headerNameNode)
499
+ // : `'${sourceCode.getText(headerNameNode)}'`;
500
+
501
+ // yield fixer.replaceText(
502
+ // destructuringResponseHeadersReference,
503
+ // `${destructuringResponseHeadersVariable.name}.get(${headerName})`,
504
+ // );
505
+ // }
506
+ // }
420
507
 
421
508
  // convert response.statusCode to response.status
422
509
  for (const responseStatusReference of responseStatusReferences) {
@@ -428,7 +515,7 @@ const rule: Rule.RuleModule = {
428
515
  }
429
516
  }
430
517
 
431
- // handle direct return without await
518
+ // handle direct return statement without await, e.g. "return fixture.api.get(...);"
432
519
  if (
433
520
  fixtureCallInformation.rootNode.type === 'ReturnStatement' &&
434
521
  fixtureCallInformation.assertions !== undefined
@@ -441,12 +528,14 @@ const rule: Rule.RuleModule = {
441
528
  },
442
529
  });
443
530
  } catch (error) {
444
- console.error(`Failed to apply ${ruleId} rule. Error:`, error);
531
+ // eslint-disable-next-line no-console
532
+ console.error(`Failed to apply ${ruleId} rule for file "${context.filename}":`, error);
445
533
  context.report({
446
534
  node: fixtureCall,
447
535
  messageId: 'unknownError',
448
536
  data: {
449
- error: String(error),
537
+ fileName: context.filename,
538
+ error: error instanceof Error ? error.toString() : JSON.stringify(error),
450
539
  },
451
540
  });
452
541
  }