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

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,23 +270,25 @@ 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');
287
+ assert.ok(supertestCall.callee.type === AST_NODE_TYPES.MemberExpression);
285
288
  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.type === AST_NODE_TYPES.CallExpression &&
290
+ supertestCall.callee.object.callee.type === AST_NODE_TYPES.MemberExpression &&
291
+ supertestCall.callee.object.callee.property.type === AST_NODE_TYPES.Identifier &&
289
292
  supertestCall.callee.object.callee.property.name === 'expect'
290
293
  ) {
291
294
  // skip nested expect calls, only focus on the top level
@@ -297,9 +300,8 @@ const rule: Rule.RuleModule = {
297
300
  return;
298
301
  }
299
302
 
300
- assert.ok(supertestCall.type === 'CallExpression');
301
303
  const fixtureFunction = supertestCall.callee.object;
302
- if (fixtureFunction.type !== 'CallExpression') {
304
+ if (fixtureFunction.type !== AST_NODE_TYPES.CallExpression) {
303
305
  return;
304
306
  }
305
307
 
@@ -335,7 +337,7 @@ const rule: Rule.RuleModule = {
335
337
  const isResponseHeadersVariableRedefinitionNeeded =
336
338
  (destructuringResponseHeadersVariable !== undefined &&
337
339
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
338
- (destructuringResponseHeadersVariable as ObjectPattern).type === 'ObjectPattern') ||
340
+ (destructuringResponseHeadersVariable as TSESTree.ObjectPattern).type === AST_NODE_TYPES.ObjectPattern) ||
339
341
  fixtureCallInformation.inlineHeadersReference !== undefined;
340
342
  const redefineResponseHeadersVariableName = `${responseVariableNameToUse}Headers`;
341
343
 
@@ -352,7 +354,7 @@ const rule: Rule.RuleModule = {
352
354
  ...(destructuringResponseBodyVariable
353
355
  ? [
354
356
  // 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)}`,
357
+ `${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
358
  ]
357
359
  : isResponseBodyVariableRedefinitionNeeded
358
360
  ? [
@@ -362,15 +364,16 @@ const rule: Rule.RuleModule = {
362
364
  // eslint-disable-next-line no-nested-ternary
363
365
  ...(destructuringResponseHeadersVariable
364
366
  ? // 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');
367
+ (destructuringResponseHeadersVariable as TSESTree.ObjectPattern).type ===
368
+ AST_NODE_TYPES.ObjectPattern
369
+ ? (destructuringResponseHeadersVariable as TSESTree.ObjectPattern).properties.map((property) => {
370
+ assert.ok(property.type === AST_NODE_TYPES.Property);
371
+ assert.ok(property.value.type === AST_NODE_TYPES.Identifier);
369
372
  // 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)}'`})`;
373
+ 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
374
  })
372
375
  : [
373
- `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseHeadersVariable as Scope.Variable).name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}`,
376
+ `${fixtureCallInformation.variableDeclaration?.kind ?? 'const'} ${(destructuringResponseHeadersVariable as Variable).name} = ${getResponseHeadersRetrievalText(responseVariableNameToUse)}`,
374
377
  ]
375
378
  : isResponseHeadersVariableRedefinitionNeeded
376
379
  ? [
@@ -384,7 +387,7 @@ const rule: Rule.RuleModule = {
384
387
  fixtureCallInformation,
385
388
  sourceCode,
386
389
  responseVariableNameToUse,
387
- destructuringResponseHeadersVariable as Scope.Variable | undefined,
390
+ destructuringResponseHeadersVariable as Variable | undefined,
388
391
  );
389
392
 
390
393
  // add variable declaration if needed
@@ -442,12 +445,12 @@ const rule: Rule.RuleModule = {
442
445
  const parent = getParent(responseHeadersReference);
443
446
  assert.ok(parent);
444
447
  let headerName;
445
- if (parent.type === 'MemberExpression') {
448
+ if (parent.type === AST_NODE_TYPES.MemberExpression) {
446
449
  const headerNameNode = parent.property;
447
450
  headerName = parent.computed
448
451
  ? sourceCode.getText(headerNameNode)
449
452
  : `'${sourceCode.getText(headerNameNode)}'`;
450
- } else if (parent.type === 'CallExpression') {
453
+ } else if (parent.type === AST_NODE_TYPES.CallExpression) {
451
454
  const headerNameNode = parent.arguments[0];
452
455
  headerName = sourceCode.getText(headerNameNode);
453
456
  }
@@ -458,7 +461,7 @@ const rule: Rule.RuleModule = {
458
461
  // convert response.statusCode to response.status
459
462
  for (const responseStatusReference of responseStatusReferences) {
460
463
  if (
461
- responseStatusReference.property.type === 'Identifier' &&
464
+ responseStatusReference.property.type === AST_NODE_TYPES.Identifier &&
462
465
  responseStatusReference.property.name === 'statusCode'
463
466
  ) {
464
467
  yield fixer.replaceText(responseStatusReference.property, `status`);
@@ -467,7 +470,7 @@ const rule: Rule.RuleModule = {
467
470
 
468
471
  // handle direct return statement without await, e.g. "return fixture.api.get(...);"
469
472
  if (
470
- fixtureCallInformation.rootNode.type === 'ReturnStatement' &&
473
+ fixtureCallInformation.rootNode.type === AST_NODE_TYPES.ReturnStatement &&
471
474
  fixtureCallInformation.assertions !== undefined
472
475
  ) {
473
476
  yield fixer.insertTextAfter(
@@ -492,6 +495,6 @@ const rule: Rule.RuleModule = {
492
495
  },
493
496
  };
494
497
  },
495
- };
498
+ });
496
499
 
497
500
  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;