@checkdigit/eslint-plugin 7.6.0-PR.75-1b09 → 7.6.0-PR.75-c06b

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.
@@ -8,20 +8,9 @@
8
8
 
9
9
  import { strict as assert } from 'node:assert';
10
10
 
11
- import type {
12
- AwaitExpression,
13
- CallExpression,
14
- Expression,
15
- ExpressionStatement,
16
- MemberExpression,
17
- Node,
18
- ObjectExpression,
19
- ObjectPattern,
20
- ReturnStatement,
21
- SimpleCallExpression,
22
- VariableDeclaration,
23
- } from 'estree';
24
- import { type Rule, type Scope, SourceCode } from 'eslint';
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';
25
14
 
26
15
  import {
27
16
  getEnclosingFunction,
@@ -29,7 +18,7 @@ import {
29
18
  getEnclosingStatement,
30
19
  getParent,
31
20
  isUsedInArrayOrAsArgument,
32
- } from '../library/tree';
21
+ } from '../library/ts-tree';
33
22
  import getDocumentationUrl from '../get-documentation-url';
34
23
  import { getIndentation } from '../library/format';
35
24
  import { analyzeResponseReferences } from './response-reference';
@@ -38,86 +27,91 @@ import { getResponseBodyRetrievalText, getResponseHeadersRetrievalText } from '.
38
27
  export const ruleId = 'no-supertest';
39
28
 
40
29
  interface FixtureCallInformation {
41
- rootNode: AwaitExpression | ReturnStatement | VariableDeclaration | SimpleCallExpression | ExpressionStatement;
42
- fixtureNode: AwaitExpression | SimpleCallExpression;
43
- variableDeclaration?: VariableDeclaration;
44
- variableAssignment?: ExpressionStatement;
45
- requestBody?: Expression;
46
- requestHeaders?: { name: Expression; value: Expression }[];
47
- requestHeadersObjectLiteral?: ObjectExpression;
48
- assertions?: Expression[][];
49
- inlineStatementNode?: Node;
50
- inlineBodyReference?: MemberExpression;
51
- inlineHeadersReference?: MemberExpression;
30
+ rootNode:
31
+ | TSESTree.AwaitExpression
32
+ | TSESTree.ReturnStatement
33
+ | TSESTree.VariableDeclaration
34
+ | TSESTree.CallExpression
35
+ | TSESTree.ExpressionStatement;
36
+ fixtureNode: TSESTree.AwaitExpression | TSESTree.CallExpression;
37
+ variableDeclaration?: TSESTree.VariableDeclaration;
38
+ variableAssignment?: TSESTree.ExpressionStatement;
39
+ requestBody?: TSESTree.Expression;
40
+ requestHeaders?: { name: TSESTree.Expression; value: TSESTree.Expression }[];
41
+ requestHeadersObjectLiteral?: TSESTree.ObjectExpression;
42
+ assertions?: TSESTree.Expression[][];
43
+ inlineStatementNode?: TSESTree.Node;
44
+ inlineBodyReference?: TSESTree.MemberExpression;
45
+ inlineHeadersReference?: TSESTree.MemberExpression;
52
46
  }
53
47
 
54
48
  // recursively analyze the fixture/supertest call chain to collect information of request/response
55
49
  // eslint-disable-next-line sonarjs/cognitive-complexity
56
- function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInformation, sourceCode: SourceCode) {
50
+ function analyzeFixtureCall(call: TSESTree.CallExpression, results: FixtureCallInformation, sourceCode: SourceCode) {
57
51
  const parent = getParent(call);
58
52
  assert.ok(parent, 'parent should exist for fixture/supertest call node');
59
53
 
60
54
  let nextCall;
61
- if (parent.type === 'ReturnStatement') {
55
+ if (parent.type === AST_NODE_TYPES.ReturnStatement) {
62
56
  // direct return, no variable declaration or await
63
57
  results.fixtureNode = call;
64
58
  results.rootNode = parent;
65
59
  } else if (
66
- parent.type === 'ArrayExpression' ||
67
- parent.type === 'CallExpression' ||
68
- parent.type === 'ArrowFunctionExpression'
60
+ parent.type === AST_NODE_TYPES.ArrayExpression ||
61
+ parent.type === AST_NODE_TYPES.CallExpression ||
62
+ parent.type === AST_NODE_TYPES.ArrowFunctionExpression
69
63
  ) {
70
64
  // direct return, no variable declaration or await
71
65
  results.fixtureNode = call;
72
66
  results.rootNode = call;
73
- } else if (parent.type === 'AwaitExpression') {
67
+ } else if (parent.type === AST_NODE_TYPES.AwaitExpression) {
74
68
  results.fixtureNode = call;
75
69
  const enclosingStatement = getEnclosingStatement(parent);
76
70
  assert.ok(enclosingStatement);
77
71
  const awaitParent = getParent(parent);
78
- if (awaitParent?.type === 'MemberExpression') {
72
+ if (awaitParent?.type === AST_NODE_TYPES.MemberExpression) {
79
73
  results.rootNode = parent;
80
74
  results.inlineStatementNode = enclosingStatement;
81
- if (awaitParent.property.type === 'Identifier' && awaitParent.property.name === 'body') {
75
+ if (awaitParent.property.type === AST_NODE_TYPES.Identifier && awaitParent.property.name === 'body') {
82
76
  results.inlineBodyReference = awaitParent;
83
77
  }
84
78
  if (
85
- awaitParent.property.type === 'Identifier' &&
79
+ awaitParent.property.type === AST_NODE_TYPES.Identifier &&
86
80
  (awaitParent.property.name === 'header' || awaitParent.property.name === 'headers')
87
81
  ) {
88
82
  results.inlineHeadersReference = awaitParent;
89
83
  }
90
- } else if (enclosingStatement.type === 'VariableDeclaration') {
84
+ } else if (enclosingStatement.type === AST_NODE_TYPES.VariableDeclaration) {
91
85
  results.variableDeclaration = enclosingStatement;
92
86
  results.rootNode = enclosingStatement;
93
87
  } else if (
94
- enclosingStatement.type === 'ExpressionStatement' &&
95
- enclosingStatement.expression.type === 'AssignmentExpression'
88
+ enclosingStatement.type === AST_NODE_TYPES.ExpressionStatement &&
89
+ enclosingStatement.expression.type === AST_NODE_TYPES.AssignmentExpression
96
90
  ) {
97
91
  results.variableAssignment = enclosingStatement;
98
92
  results.rootNode = enclosingStatement;
99
93
  } else {
100
94
  results.rootNode = parent;
101
95
  }
102
- } else if (parent.type === 'MemberExpression' && parent.property.type === 'Identifier') {
96
+ } else if (parent.type === AST_NODE_TYPES.MemberExpression && parent.property.type === AST_NODE_TYPES.Identifier) {
103
97
  if (parent.property.name === 'expect') {
104
98
  // supertest assertions
105
99
  const assertionCall = getParent(parent);
106
- assert.ok(assertionCall && assertionCall.type === 'CallExpression');
107
- results.assertions = [...(results.assertions ?? []), assertionCall.arguments as Expression[]];
100
+ assert.ok(assertionCall && assertionCall.type === AST_NODE_TYPES.CallExpression);
101
+ results.assertions = [...(results.assertions ?? []), assertionCall.arguments as TSESTree.Expression[]];
108
102
  nextCall = assertionCall;
109
103
  } else if (parent.property.name === 'send') {
110
104
  // request body
111
105
  const sendRequestBodyCall = getParent(parent);
112
- assert.ok(sendRequestBodyCall && sendRequestBodyCall.type === 'CallExpression');
113
- results.requestBody = sendRequestBodyCall.arguments[0] as Expression;
106
+ assert.ok(sendRequestBodyCall && sendRequestBodyCall.type === AST_NODE_TYPES.CallExpression);
107
+ results.requestBody = sendRequestBodyCall.arguments[0] as TSESTree.Expression;
114
108
  nextCall = sendRequestBodyCall;
115
109
  } else if (parent.property.name === 'set') {
116
110
  // request headers
117
111
  const setRequestHeaderCall = getParent(parent);
118
- assert.ok(setRequestHeaderCall && setRequestHeaderCall.type === 'CallExpression');
119
- const [arg1, arg2] = setRequestHeaderCall.arguments as [Expression, Expression];
120
- if (arg1.type === 'ObjectExpression') {
112
+ assert.ok(setRequestHeaderCall && setRequestHeaderCall.type === AST_NODE_TYPES.CallExpression);
113
+ const [arg1, arg2] = setRequestHeaderCall.arguments as [TSESTree.Expression, TSESTree.Expression];
114
+ if (arg1.type === AST_NODE_TYPES.ObjectExpression) {
121
115
  results.requestHeadersObjectLiteral = arg1;
122
116
  } else {
123
117
  results.requestHeaders = [...(results.requestHeaders ?? []), { name: arg1, value: arg2 }];
@@ -137,7 +131,7 @@ function createResponseAssertions(
137
131
  fixtureCallInformation: FixtureCallInformation,
138
132
  sourceCode: SourceCode,
139
133
  responseVariableName: string,
140
- destructuringResponseHeadersVariable: Scope.Variable | undefined,
134
+ destructuringResponseHeadersVariable: Variable | undefined,
141
135
  ) {
142
136
  let statusAssertion: string | undefined;
143
137
  const nonStatusAssertions: string[] = [];
@@ -146,20 +140,20 @@ function createResponseAssertions(
146
140
  const [assertionArgument] = expectArguments;
147
141
  assert.ok(assertionArgument);
148
142
  if (
149
- (assertionArgument.type === 'MemberExpression' &&
150
- assertionArgument.object.type === 'Identifier' &&
143
+ (assertionArgument.type === AST_NODE_TYPES.MemberExpression &&
144
+ assertionArgument.object.type === AST_NODE_TYPES.Identifier &&
151
145
  assertionArgument.object.name === 'StatusCodes') ||
152
- assertionArgument.type === 'Literal' ||
146
+ assertionArgument.type === AST_NODE_TYPES.Literal ||
153
147
  sourceCode.getText(assertionArgument).includes('StatusCodes.')
154
148
  ) {
155
149
  // status code assertion
156
150
  statusAssertion = `assert.equal(${responseVariableName}.status, ${sourceCode.getText(assertionArgument)})`;
157
- } else if (assertionArgument.type === 'ArrowFunctionExpression') {
151
+ } else if (assertionArgument.type === AST_NODE_TYPES.ArrowFunctionExpression) {
158
152
  // callback assertion using arrow function
159
153
  let functionBody = sourceCode.getText(assertionArgument.body);
160
154
 
161
155
  const [originalResponseArgument] = assertionArgument.params;
162
- assert.ok(originalResponseArgument?.type === 'Identifier');
156
+ assert.ok(originalResponseArgument?.type === AST_NODE_TYPES.Identifier);
163
157
  const originalResponseArgumentName = originalResponseArgument.name;
164
158
  if (originalResponseArgumentName !== responseVariableName) {
165
159
  functionBody = functionBody.replace(
@@ -168,12 +162,15 @@ function createResponseAssertions(
168
162
  );
169
163
  }
170
164
  nonStatusAssertions.push(`assert.doesNotThrow(()=>${functionBody})`);
171
- } else if (assertionArgument.type === 'Identifier') {
165
+ } else if (assertionArgument.type === AST_NODE_TYPES.Identifier) {
172
166
  // callback assertion using function reference
173
167
  nonStatusAssertions.push(
174
168
  `assert.doesNotThrow(()=>${sourceCode.getText(assertionArgument)}(${responseVariableName}))`,
175
169
  );
176
- } else if (assertionArgument.type === 'ObjectExpression' || assertionArgument.type === 'CallExpression') {
170
+ } else if (
171
+ assertionArgument.type === AST_NODE_TYPES.ObjectExpression ||
172
+ assertionArgument.type === AST_NODE_TYPES.CallExpression
173
+ ) {
177
174
  // body deep equal assertion
178
175
  nonStatusAssertions.push(
179
176
  `assert.deepEqual(await ${responseVariableName}.json(), ${sourceCode.getText(assertionArgument)})`,
@@ -189,7 +186,7 @@ function createResponseAssertions(
189
186
  destructuringResponseHeadersVariable !== undefined
190
187
  ? destructuringResponseHeadersVariable.name
191
188
  : `${responseVariableName}.headers`;
192
- if (headerValue.type === 'Literal' && headerValue.value instanceof RegExp) {
189
+ if (headerValue.type === AST_NODE_TYPES.Literal && headerValue.value instanceof RegExp) {
193
190
  nonStatusAssertions.push(
194
191
  `assert.ok(${headersReference}.get(${sourceCode.getText(headerName)}).match(${sourceCode.getText(headerValue)}))`,
195
192
  );
@@ -207,21 +204,22 @@ function createResponseAssertions(
207
204
  }
208
205
 
209
206
  function getResponseVariableNameToUse(
210
- scopeManager: Scope.ScopeManager,
207
+ scopeManager: ScopeManager,
211
208
  fixtureCallInformation: FixtureCallInformation,
212
- scopeVariablesMap: Map<Scope.Scope, string[]>,
209
+ scopeVariablesMap: Map<Scope, string[]>,
213
210
  ) {
214
211
  if (fixtureCallInformation.variableAssignment) {
215
212
  assert.ok(
216
- fixtureCallInformation.variableAssignment.expression.type === 'AssignmentExpression' &&
217
- fixtureCallInformation.variableAssignment.expression.left.type === 'Identifier',
213
+ fixtureCallInformation.variableAssignment.expression.type === AST_NODE_TYPES.AssignmentExpression &&
214
+ fixtureCallInformation.variableAssignment.expression.left.type === AST_NODE_TYPES.Identifier,
218
215
  );
219
216
  return fixtureCallInformation.variableAssignment.expression.left.name;
220
217
  }
221
218
 
222
219
  if (fixtureCallInformation.variableDeclaration) {
223
220
  const firstDeclaration = fixtureCallInformation.variableDeclaration.declarations[0];
224
- if (firstDeclaration && firstDeclaration.id.type === 'Identifier') {
221
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
222
+ if (firstDeclaration !== undefined && firstDeclaration.id.type === AST_NODE_TYPES.Identifier) {
225
223
  return firstDeclaration.id.name;
226
224
  }
227
225
  }
@@ -250,12 +248,15 @@ function getResponseVariableNameToUse(
250
248
  return responseVariableNameToUse;
251
249
  }
252
250
 
253
- function isResponseBodyRedefinition(responseBodyReference: MemberExpression): boolean {
251
+ function isResponseBodyRedefinition(responseBodyReference: TSESTree.MemberExpression): boolean {
254
252
  const parent = getParent(responseBodyReference);
255
- return parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier';
253
+ return parent?.type === AST_NODE_TYPES.VariableDeclarator && parent.id.type === AST_NODE_TYPES.Identifier;
256
254
  }
257
255
 
258
- const rule: Rule.RuleModule = {
256
+ const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
257
+
258
+ const rule: ESLintUtils.RuleModule<'unknownError' | 'preferNativeFetch'> = createRule({
259
+ name: ruleId,
259
260
  meta: {
260
261
  type: 'suggestion',
261
262
  docs: {
@@ -269,46 +270,42 @@ const rule: Rule.RuleModule = {
269
270
  fixable: 'code',
270
271
  schema: [],
271
272
  },
273
+ defaultOptions: [],
272
274
  // eslint-disable-next-line max-lines-per-function
273
275
  create(context) {
274
276
  const sourceCode = context.sourceCode;
275
277
  const scopeManager = sourceCode.scopeManager;
276
- const scopeVariablesMap = new Map<Scope.Scope, string[]>();
278
+ assert.ok(scopeManager !== null);
279
+ const scopeVariablesMap = new Map<Scope, string[]>();
277
280
 
278
281
  return {
279
282
  // eslint-disable-next-line max-lines-per-function
280
283
  'CallExpression[callee.property.name="expect"]': (
281
- supertestCall: CallExpression,
284
+ supertestCall: TSESTree.CallExpression,
282
285
  // eslint-disable-next-line sonarjs/cognitive-complexity
283
286
  ) => {
284
- assert.ok(supertestCall.callee.type === 'MemberExpression');
285
- if (
286
- supertestCall.callee.object.type === 'CallExpression' &&
287
- supertestCall.callee.object.callee.type === 'MemberExpression' &&
288
- supertestCall.callee.object.callee.property.type === 'Identifier' &&
289
- supertestCall.callee.object.callee.property.name === 'expect'
290
- ) {
291
- // skip nested expect calls, only focus on the top level
292
- return;
293
- }
294
287
  try {
295
- if (isUsedInArrayOrAsArgument(supertestCall) || getEnclosingFunction(supertestCall)?.async === false) {
296
- // skip and leave it to "fetch-then" rule to handle it because no "await" can be used here
288
+ assert.ok(supertestCall.callee.type === AST_NODE_TYPES.MemberExpression);
289
+ if (
290
+ supertestCall.callee.object.type !== AST_NODE_TYPES.CallExpression ||
291
+ (supertestCall.callee.object.callee.type === AST_NODE_TYPES.MemberExpression &&
292
+ supertestCall.callee.object.callee.property.type === AST_NODE_TYPES.Identifier &&
293
+ supertestCall.callee.object.callee.property.name === 'expect')
294
+ ) {
295
+ // skip nested expect calls, only focus on the top level
297
296
  return;
298
297
  }
299
298
 
300
- assert.ok(supertestCall.type === 'CallExpression');
301
- const fixtureFunction = supertestCall.callee.object;
302
- if (fixtureFunction.type !== 'CallExpression') {
299
+ if (isUsedInArrayOrAsArgument(supertestCall) || getEnclosingFunction(supertestCall)?.async === false) {
300
+ // skip and leave it to "fetch-then" rule to handle it because no "await" can be used here
303
301
  return;
304
302
  }
305
303
 
306
304
  const indentation = getIndentation(supertestCall, sourceCode);
307
305
 
308
306
  const fixtureCallInformation = {} as FixtureCallInformation;
307
+ const fixtureFunction = supertestCall.callee.object;
309
308
  analyzeFixtureCall(fixtureFunction, fixtureCallInformation, sourceCode);
310
- sourceCode.getText(fixtureCallInformation.fixtureNode);
311
- sourceCode.getText(fixtureCallInformation.rootNode);
312
309
  fixtureCallInformation.assertions?.flat().map((ass) => sourceCode.getText(ass));
313
310
 
314
311
  const {
@@ -335,7 +332,7 @@ const rule: Rule.RuleModule = {
335
332
  const isResponseHeadersVariableRedefinitionNeeded =
336
333
  (destructuringResponseHeadersVariable !== undefined &&
337
334
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
338
- (destructuringResponseHeadersVariable as ObjectPattern).type === 'ObjectPattern') ||
335
+ (destructuringResponseHeadersVariable as TSESTree.ObjectPattern).type === AST_NODE_TYPES.ObjectPattern) ||
339
336
  fixtureCallInformation.inlineHeadersReference !== undefined;
340
337
  const redefineResponseHeadersVariableName = `${responseVariableNameToUse}Headers`;
341
338
 
@@ -352,7 +349,7 @@ const rule: Rule.RuleModule = {
352
349
  ...(destructuringResponseBodyVariable
353
350
  ? [
354
351
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
355
- `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseBodyVariable as ObjectPattern).type === 'ObjectPattern' ? sourceCode.getText(destructuringResponseBodyVariable as ObjectPattern) : (destructuringResponseBodyVariable as Scope.Variable).name} = ${getResponseBodyRetrievalText(responseVariableNameToUse)}`,
352
+ `${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)}`,
356
353
  ]
357
354
  : isResponseBodyVariableRedefinitionNeeded
358
355
  ? [
@@ -362,15 +359,16 @@ const rule: Rule.RuleModule = {
362
359
  // eslint-disable-next-line no-nested-ternary
363
360
  ...(destructuringResponseHeadersVariable
364
361
  ? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
365
- (destructuringResponseHeadersVariable as ObjectPattern).type === 'ObjectPattern'
366
- ? (destructuringResponseHeadersVariable as ObjectPattern).properties.map((property) => {
367
- assert.ok(property.type === 'Property');
368
- assert.equal(property.value.type, 'Identifier');
362
+ (destructuringResponseHeadersVariable as TSESTree.ObjectPattern).type ===
363
+ AST_NODE_TYPES.ObjectPattern
364
+ ? (destructuringResponseHeadersVariable as TSESTree.ObjectPattern).properties.map((property) => {
365
+ assert.ok(property.type === AST_NODE_TYPES.Property);
366
+ assert.ok(property.value.type === AST_NODE_TYPES.Identifier);
369
367
  // eslint-disable-next-line sonarjs/no-nested-template-literals
370
- return `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${property.value.name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}.get(${property.key.type === 'Literal' ? sourceCode.getText(property.key) : `'${sourceCode.getText(property.key)}'`})`;
368
+ 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)}'`})`;
371
369
  })
372
370
  : [
373
- `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseHeadersVariable as Scope.Variable).name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}`,
371
+ `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseHeadersVariable as Variable).name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}`,
374
372
  ]
375
373
  : isResponseHeadersVariableRedefinitionNeeded
376
374
  ? [
@@ -384,7 +382,7 @@ const rule: Rule.RuleModule = {
384
382
  fixtureCallInformation,
385
383
  sourceCode,
386
384
  responseVariableNameToUse,
387
- destructuringResponseHeadersVariable as Scope.Variable | undefined,
385
+ destructuringResponseHeadersVariable as Variable | undefined,
388
386
  );
389
387
 
390
388
  // add variable declaration if needed
@@ -442,12 +440,12 @@ const rule: Rule.RuleModule = {
442
440
  const parent = getParent(responseHeadersReference);
443
441
  assert.ok(parent);
444
442
  let headerName;
445
- if (parent.type === 'MemberExpression') {
443
+ if (parent.type === AST_NODE_TYPES.MemberExpression) {
446
444
  const headerNameNode = parent.property;
447
445
  headerName = parent.computed
448
446
  ? sourceCode.getText(headerNameNode)
449
447
  : `'${sourceCode.getText(headerNameNode)}'`;
450
- } else if (parent.type === 'CallExpression') {
448
+ } else if (parent.type === AST_NODE_TYPES.CallExpression) {
451
449
  const headerNameNode = parent.arguments[0];
452
450
  headerName = sourceCode.getText(headerNameNode);
453
451
  }
@@ -458,7 +456,7 @@ const rule: Rule.RuleModule = {
458
456
  // convert response.statusCode to response.status
459
457
  for (const responseStatusReference of responseStatusReferences) {
460
458
  if (
461
- responseStatusReference.property.type === 'Identifier' &&
459
+ responseStatusReference.property.type === AST_NODE_TYPES.Identifier &&
462
460
  responseStatusReference.property.name === 'statusCode'
463
461
  ) {
464
462
  yield fixer.replaceText(responseStatusReference.property, `status`);
@@ -467,7 +465,7 @@ const rule: Rule.RuleModule = {
467
465
 
468
466
  // handle direct return statement without await, e.g. "return fixture.api.get(...);"
469
467
  if (
470
- fixtureCallInformation.rootNode.type === 'ReturnStatement' &&
468
+ fixtureCallInformation.rootNode.type === AST_NODE_TYPES.ReturnStatement &&
471
469
  fixtureCallInformation.assertions !== undefined
472
470
  ) {
473
471
  yield fixer.insertTextAfter(
@@ -492,6 +490,6 @@ const rule: Rule.RuleModule = {
492
490
  },
493
491
  };
494
492
  },
495
- };
493
+ });
496
494
 
497
495
  export default rule;
@@ -7,11 +7,12 @@
7
7
  */
8
8
 
9
9
  import { strict as assert } from 'node:assert';
10
- import type { MemberExpression, ObjectPattern, VariableDeclaration } from 'estree';
11
- import { type Scope } from 'eslint';
10
+
12
11
  import debug from 'debug';
13
12
 
14
- import { getParent } from '../library/tree';
13
+ import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
14
+ import type { ScopeManager, Variable } from '@typescript-eslint/scope-manager';
15
+ import { getParent } from '../library/ts-tree';
15
16
 
16
17
  const log = debug('eslint-plugin:response-reference');
17
18
 
@@ -21,25 +22,25 @@ const log = debug('eslint-plugin:response-reference');
21
22
  * @param variableDeclaration - variable declaration node
22
23
  */
23
24
  export function analyzeResponseReferences(
24
- variableDeclaration: VariableDeclaration | undefined,
25
- scopeManager: Scope.ScopeManager,
25
+ variableDeclaration: TSESTree.VariableDeclaration | undefined,
26
+ scopeManager: ScopeManager,
26
27
  ): {
27
- variable?: Scope.Variable;
28
- bodyReferences: MemberExpression[];
29
- headersReferences: MemberExpression[];
30
- statusReferences: MemberExpression[];
31
- destructuringBodyVariable?: Scope.Variable | ObjectPattern;
32
- destructuringHeadersVariable?: Scope.Variable | ObjectPattern;
33
- destructuringHeadersReferences?: MemberExpression[] | undefined;
28
+ variable?: Variable;
29
+ bodyReferences: TSESTree.MemberExpression[];
30
+ headersReferences: TSESTree.MemberExpression[];
31
+ statusReferences: TSESTree.MemberExpression[];
32
+ destructuringBodyVariable?: Variable | TSESTree.ObjectPattern;
33
+ destructuringHeadersVariable?: Variable | TSESTree.ObjectPattern;
34
+ destructuringHeadersReferences?: TSESTree.MemberExpression[] | undefined;
34
35
  } {
35
36
  const results: {
36
- variable?: Scope.Variable;
37
- bodyReferences: MemberExpression[];
38
- headersReferences: MemberExpression[];
39
- statusReferences: MemberExpression[];
40
- destructuringBodyVariable?: Scope.Variable | ObjectPattern;
41
- destructuringHeadersVariable?: Scope.Variable | ObjectPattern;
42
- destructuringHeadersReferences?: MemberExpression[] | undefined;
37
+ variable?: Variable;
38
+ bodyReferences: TSESTree.MemberExpression[];
39
+ headersReferences: TSESTree.MemberExpression[];
40
+ statusReferences: TSESTree.MemberExpression[];
41
+ destructuringBodyVariable?: Variable | TSESTree.ObjectPattern;
42
+ destructuringHeadersVariable?: Variable | TSESTree.ObjectPattern;
43
+ destructuringHeadersReferences?: TSESTree.MemberExpression[] | undefined;
43
44
  } = {
44
45
  bodyReferences: [],
45
46
  headersReferences: [],
@@ -55,7 +56,7 @@ export function analyzeResponseReferences(
55
56
  assert.ok(identifier);
56
57
  const identifierParent = getParent(identifier);
57
58
  assert.ok(identifierParent);
58
- if (identifierParent.type === 'VariableDeclarator') {
59
+ if (identifierParent.type === AST_NODE_TYPES.VariableDeclarator) {
59
60
  // e.g. const response = ...
60
61
  results.variable = responseVariable;
61
62
  const responseReferences = responseVariable.references.map((responseReference) =>
@@ -63,34 +64,36 @@ export function analyzeResponseReferences(
63
64
  );
64
65
  // e.g. response.body
65
66
  results.bodyReferences = responseReferences.filter(
66
- (node): node is MemberExpression =>
67
- node?.type === 'MemberExpression' && node.property.type === 'Identifier' && node.property.name === 'body',
67
+ (node): node is TSESTree.MemberExpression =>
68
+ node?.type === AST_NODE_TYPES.MemberExpression &&
69
+ node.property.type === AST_NODE_TYPES.Identifier &&
70
+ node.property.name === 'body',
68
71
  );
69
72
  // e.g. response.headers / response.header / response.get()
70
73
  results.headersReferences = responseReferences.filter(
71
- (node): node is MemberExpression =>
72
- node?.type === 'MemberExpression' &&
73
- node.property.type === 'Identifier' &&
74
+ (node): node is TSESTree.MemberExpression =>
75
+ node?.type === AST_NODE_TYPES.MemberExpression &&
76
+ node.property.type === AST_NODE_TYPES.Identifier &&
74
77
  (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
75
78
  );
76
79
  // e.g. response.status / response.statusCode
77
80
  results.statusReferences = responseReferences.filter(
78
- (node): node is MemberExpression =>
79
- node?.type === 'MemberExpression' &&
80
- node.property.type === 'Identifier' &&
81
+ (node): node is TSESTree.MemberExpression =>
82
+ node?.type === AST_NODE_TYPES.MemberExpression &&
83
+ node.property.type === AST_NODE_TYPES.Identifier &&
81
84
  (node.property.name === 'status' || node.property.name === 'statusCode'),
82
85
  );
83
86
  } else if (
84
87
  // body reference through destruction/renaming, e.g. "const { body } = ..."
85
- identifierParent.type === 'Property' &&
86
- identifierParent.key.type === 'Identifier' &&
88
+ identifierParent.type === AST_NODE_TYPES.Property &&
89
+ identifierParent.key.type === AST_NODE_TYPES.Identifier &&
87
90
  identifierParent.key.name === 'body'
88
91
  ) {
89
92
  results.destructuringBodyVariable = responseVariable;
90
93
  } else if (
91
94
  // header reference through destruction/renaming, e.g. "const { headers } = ..."
92
- identifierParent.type === 'Property' &&
93
- identifierParent.key.type === 'Identifier' &&
95
+ identifierParent.type === AST_NODE_TYPES.Property &&
96
+ identifierParent.key.type === AST_NODE_TYPES.Identifier &&
94
97
  identifierParent.key.name === 'headers'
95
98
  ) {
96
99
  results.destructuringHeadersVariable = responseVariable;
@@ -98,23 +101,27 @@ export function analyzeResponseReferences(
98
101
  .map((reference) => reference.identifier)
99
102
  .map(getParent)
100
103
  .filter(
101
- (parent): parent is MemberExpression =>
102
- parent?.type === 'MemberExpression' &&
103
- parent.property.type === 'Identifier' &&
104
+ (parent): parent is TSESTree.MemberExpression =>
105
+ parent?.type === AST_NODE_TYPES.MemberExpression &&
106
+ parent.property.type === AST_NODE_TYPES.Identifier &&
104
107
  parent.property.name !== 'get' &&
105
- getParent(parent)?.type !== 'CallExpression',
108
+ getParent(parent)?.type !== AST_NODE_TYPES.CallExpression,
106
109
  );
107
- } else if (identifierParent.type === 'Property') {
110
+ } else if (identifierParent.type === AST_NODE_TYPES.Property) {
108
111
  const parent = getParent(identifierParent);
109
- if (parent?.type === 'ObjectPattern') {
112
+ if (parent?.type === AST_NODE_TYPES.ObjectPattern) {
110
113
  // body reference through nested destruction, e.g. "const { body: {bodyPropertyName: renamedBodyPropertyName}, headers: {headerPropertyName: renamedHeaderPropertyName} } = ..."
111
114
  const parent2 = getParent(parent);
112
- if (parent2?.type === 'Property' && parent2.key.type === 'Identifier' && parent2.key.name === 'body') {
115
+ if (
116
+ parent2?.type === AST_NODE_TYPES.Property &&
117
+ parent2.key.type === AST_NODE_TYPES.Identifier &&
118
+ parent2.key.name === 'body'
119
+ ) {
113
120
  results.destructuringBodyVariable = parent;
114
121
  }
115
122
  if (
116
- parent2?.type === 'Property' &&
117
- parent2.key.type === 'Identifier' &&
123
+ parent2?.type === AST_NODE_TYPES.Property &&
124
+ parent2.key.type === AST_NODE_TYPES.Identifier &&
118
125
  (parent2.key.name === 'header' || parent2.key.name === 'headers')
119
126
  ) {
120
127
  results.destructuringHeadersVariable = parent;