@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,10 +8,11 @@
8
8
 
9
9
  import { strict as assert } from 'node:assert';
10
10
 
11
- import type { CallExpression, Expression, MemberExpression, SimpleCallExpression } from 'estree';
12
- import { type Rule, type Scope, SourceCode } from 'eslint';
11
+ import { ScopeManager, Variable } from '@typescript-eslint/scope-manager';
12
+ import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils';
13
+ import type { SourceCode } from '@typescript-eslint/utils/ts-eslint';
13
14
 
14
- import { getEnclosingFunction, getEnclosingStatement, getParent, isUsedInArrayOrAsArgument } from '../library/tree';
15
+ import { getEnclosingFunction, getEnclosingStatement, getParent, isUsedInArrayOrAsArgument } from '../library/ts-tree';
15
16
  import getDocumentationUrl from '../get-documentation-url';
16
17
  import { getIndentation } from '../library/format';
17
18
  import { isValidPropertyName } from '../library/variable';
@@ -21,48 +22,48 @@ import { replaceEndpointUrlPrefixWithBasePath } from './url';
21
22
  export const ruleId = 'fetch-then';
22
23
 
23
24
  interface FixtureCallInformation {
24
- fixtureNode: SimpleCallExpression;
25
- requestBody?: Expression;
26
- requestHeaders?: { name: Expression; value: Expression }[];
27
- assertions?: Expression[][];
25
+ fixtureNode: TSESTree.CallExpression;
26
+ requestBody?: TSESTree.Expression;
27
+ requestHeaders?: { name: TSESTree.Expression; value: TSESTree.Expression }[];
28
+ assertions?: TSESTree.Expression[][];
28
29
  }
29
30
 
30
31
  // recursively analyze the fixture/supertest call chain to collect information of request/response
31
- function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInformation, sourceCode: SourceCode) {
32
+ function analyzeFixtureCall(call: TSESTree.CallExpression, results: FixtureCallInformation, sourceCode: SourceCode) {
32
33
  const parent = getParent(call);
33
34
  if (!parent) {
34
35
  return;
35
36
  }
36
37
 
37
38
  let nextCall;
38
- if (parent.type !== 'MemberExpression') {
39
+ if (parent.type !== AST_NODE_TYPES.MemberExpression) {
39
40
  results.fixtureNode = call;
40
41
  return;
41
42
  }
42
43
 
43
- if (parent.property.type === 'Identifier') {
44
+ if (parent.property.type === AST_NODE_TYPES.Identifier) {
44
45
  if (parent.property.name === 'expect') {
45
46
  // supertest assertions
46
47
  const assertionCall = getParent(parent);
47
- assert.ok(assertionCall && assertionCall.type === 'CallExpression');
48
- results.assertions = [...(results.assertions ?? []), assertionCall.arguments as Expression[]];
48
+ assert.ok(assertionCall && assertionCall.type === AST_NODE_TYPES.CallExpression);
49
+ results.assertions = [...(results.assertions ?? []), assertionCall.arguments as TSESTree.Expression[]];
49
50
  nextCall = assertionCall;
50
51
  } else if (parent.property.name === 'send') {
51
52
  // request body
52
53
  const sendRequestBodyCall = getParent(parent);
53
- assert.ok(sendRequestBodyCall && sendRequestBodyCall.type === 'CallExpression');
54
- results.requestBody = sendRequestBodyCall.arguments[0] as Expression;
54
+ assert.ok(sendRequestBodyCall && sendRequestBodyCall.type === AST_NODE_TYPES.CallExpression);
55
+ results.requestBody = sendRequestBodyCall.arguments[0] as TSESTree.Expression;
55
56
  nextCall = sendRequestBodyCall;
56
57
  } else if (parent.property.name === 'set') {
57
58
  // request headers
58
59
  const setRequestHeaderCall = getParent(parent);
59
- assert.ok(setRequestHeaderCall && setRequestHeaderCall.type === 'CallExpression');
60
- const [name, value] = setRequestHeaderCall.arguments as [Expression, Expression];
60
+ assert.ok(setRequestHeaderCall && setRequestHeaderCall.type === AST_NODE_TYPES.CallExpression);
61
+ const [name, value] = setRequestHeaderCall.arguments as [TSESTree.Expression, TSESTree.Expression];
61
62
  results.requestHeaders = [...(results.requestHeaders ?? []), { name, value }];
62
63
  nextCall = setRequestHeaderCall;
63
64
  }
64
65
  } else {
65
- throw new Error(`Unexpected expression in fixture/supertest call ${sourceCode.getText(parent)}.`);
66
+ throw new Error(`Unexpected TSESTree.Expression in fixture/supertest call ${sourceCode.getText(parent)}.`);
66
67
  }
67
68
  if (nextCall) {
68
69
  analyzeFixtureCall(nextCall, results, sourceCode);
@@ -82,20 +83,20 @@ function createResponseAssertions(
82
83
  const [assertionArgument] = expectArguments;
83
84
  assert.ok(assertionArgument);
84
85
  if (
85
- (assertionArgument.type === 'MemberExpression' &&
86
- assertionArgument.object.type === 'Identifier' &&
86
+ (assertionArgument.type === AST_NODE_TYPES.MemberExpression &&
87
+ assertionArgument.object.type === AST_NODE_TYPES.Identifier &&
87
88
  assertionArgument.object.name === 'StatusCodes') ||
88
- assertionArgument.type === 'Literal' ||
89
+ assertionArgument.type === AST_NODE_TYPES.Literal ||
89
90
  sourceCode.getText(assertionArgument).includes('StatusCodes.')
90
91
  ) {
91
92
  // status code assertion
92
93
  statusAssertion = `assert.equal(${responseVariableName}.status, ${sourceCode.getText(assertionArgument)})`;
93
- } else if (assertionArgument.type === 'ArrowFunctionExpression') {
94
+ } else if (assertionArgument.type === AST_NODE_TYPES.ArrowFunctionExpression) {
94
95
  // callback assertion using arrow function
95
96
  let functionBody = sourceCode.getText(assertionArgument.body);
96
97
 
97
98
  const [originalResponseArgument] = assertionArgument.params;
98
- assert.ok(originalResponseArgument?.type === 'Identifier');
99
+ assert.ok(originalResponseArgument?.type === AST_NODE_TYPES.Identifier);
99
100
  const originalResponseArgumentName = originalResponseArgument.name;
100
101
  if (originalResponseArgumentName !== responseVariableName) {
101
102
  functionBody = functionBody.replace(
@@ -104,12 +105,15 @@ function createResponseAssertions(
104
105
  );
105
106
  }
106
107
  nonStatusAssertions.push(`assert.doesNotThrow(()=>${functionBody})`);
107
- } else if (assertionArgument.type === 'Identifier') {
108
+ } else if (assertionArgument.type === AST_NODE_TYPES.Identifier) {
108
109
  // callback assertion using function reference
109
110
  nonStatusAssertions.push(
110
111
  `assert.doesNotThrow(()=>${sourceCode.getText(assertionArgument)}(${responseVariableName}))`,
111
112
  );
112
- } else if (assertionArgument.type === 'ObjectExpression' || assertionArgument.type === 'CallExpression') {
113
+ } else if (
114
+ assertionArgument.type === AST_NODE_TYPES.ObjectExpression ||
115
+ assertionArgument.type === AST_NODE_TYPES.CallExpression
116
+ ) {
113
117
  // body deep equal assertion
114
118
  nonStatusAssertions.push(
115
119
  `assert.deepEqual(await ${responseVariableName}.json(), ${sourceCode.getText(assertionArgument)})`,
@@ -122,7 +126,7 @@ function createResponseAssertions(
122
126
  const [headerName, headerValue] = expectArguments;
123
127
  assert.ok(headerName && headerValue);
124
128
  const headersReference = `${responseVariableName}.headers`;
125
- if (headerValue.type === 'Literal' && headerValue.value instanceof RegExp) {
129
+ if (headerValue.type === AST_NODE_TYPES.Literal && headerValue.value instanceof RegExp) {
126
130
  nonStatusAssertions.push(
127
131
  `assert.ok(${headersReference}.get(${sourceCode.getText(headerName)}).match(${sourceCode.getText(headerValue)}))`,
128
132
  );
@@ -139,16 +143,12 @@ function createResponseAssertions(
139
143
  };
140
144
  }
141
145
 
142
- function getResponseHeadersAccesses(
143
- responseVariables: Scope.Variable[],
144
- scopeManager: Scope.ScopeManager,
145
- sourceCode: SourceCode,
146
- ) {
147
- const responseHeadersAccesses: MemberExpression[] = [];
146
+ function getResponseHeadersAccesses(responseVariables: Variable[], scopeManager: ScopeManager, sourceCode: SourceCode) {
147
+ const responseHeadersAccesses: TSESTree.MemberExpression[] = [];
148
148
  for (const responseVariable of responseVariables) {
149
149
  for (const responseReference of responseVariable.references) {
150
150
  const responseAccess = getParent(responseReference.identifier);
151
- if (!responseAccess || responseAccess.type !== 'MemberExpression') {
151
+ if (!responseAccess || responseAccess.type !== AST_NODE_TYPES.MemberExpression) {
152
152
  continue;
153
153
  }
154
154
 
@@ -158,8 +158,8 @@ function getResponseHeadersAccesses(
158
158
  }
159
159
 
160
160
  if (
161
- responseAccessParent.type === 'CallExpression' &&
162
- responseAccessParent.arguments[0]?.type === 'ArrowFunctionExpression'
161
+ responseAccessParent.type === AST_NODE_TYPES.CallExpression &&
162
+ responseAccessParent.arguments[0]?.type === AST_NODE_TYPES.ArrowFunctionExpression
163
163
  ) {
164
164
  // map-like operation against responses, e.g. responses.map((response) => response.headers.etag)
165
165
  responseHeadersAccesses.push(
@@ -174,8 +174,8 @@ function getResponseHeadersAccesses(
174
174
 
175
175
  if (
176
176
  responseAccess.computed &&
177
- responseAccess.property.type === 'Literal' &&
178
- responseAccessParent.type === 'MemberExpression'
177
+ responseAccess.property.type === AST_NODE_TYPES.Literal &&
178
+ responseAccessParent.type === AST_NODE_TYPES.MemberExpression
179
179
  ) {
180
180
  // header access through indexed responses array, e.g. responses[0].headers, responses[1].get(...), etc.
181
181
  responseHeadersAccesses.push(responseAccessParent);
@@ -187,7 +187,9 @@ function getResponseHeadersAccesses(
187
187
  return responseHeadersAccesses;
188
188
  }
189
189
 
190
- const rule: Rule.RuleModule = {
190
+ const createRule = ESLintUtils.RuleCreator((name) => getDocumentationUrl(name));
191
+ const rule: ESLintUtils.RuleModule<'unknownError' | 'preferNativeFetch' | 'shouldUseHeaderGetter'> = createRule({
192
+ name: ruleId,
191
193
  meta: {
192
194
  type: 'suggestion',
193
195
  docs: {
@@ -202,14 +204,15 @@ const rule: Rule.RuleModule = {
202
204
  fixable: 'code',
203
205
  schema: [],
204
206
  },
205
-
207
+ defaultOptions: [],
206
208
  create(context) {
207
209
  const sourceCode = context.sourceCode;
208
210
  const scopeManager = sourceCode.scopeManager;
211
+ assert.ok(scopeManager);
209
212
 
210
213
  return {
211
214
  'CallExpression[callee.object.object.name="fixture"][callee.object.property.name="api"]': (
212
- fixtureCall: CallExpression,
215
+ fixtureCall: TSESTree.CallExpression,
213
216
  // eslint-disable-next-line sonarjs/cognitive-complexity
214
217
  ) => {
215
218
  try {
@@ -222,9 +225,8 @@ const rule: Rule.RuleModule = {
222
225
  return;
223
226
  }
224
227
 
225
- assert.ok(fixtureCall.type === 'CallExpression');
226
228
  const fixtureFunction = fixtureCall.callee; // e.g. fixture.api.get
227
- assert.ok(fixtureFunction.type === 'MemberExpression');
229
+ assert.ok(fixtureFunction.type === AST_NODE_TYPES.MemberExpression);
228
230
  const indentation = getIndentation(fixtureCall, sourceCode);
229
231
 
230
232
  const [urlArgumentNode] = fixtureCall.arguments; // e.g. `/sample-service/v1/ping`
@@ -239,7 +241,7 @@ const rule: Rule.RuleModule = {
239
241
 
240
242
  // fetch request argument
241
243
  const methodNode = fixtureFunction.property; // get/put/etc.
242
- assert.ok(methodNode.type === 'Identifier');
244
+ assert.ok(methodNode.type === AST_NODE_TYPES.Identifier);
243
245
  const fetchRequestArgumentLines = [
244
246
  '{',
245
247
  ` method: '${methodNode.name.toUpperCase()}',`,
@@ -252,7 +254,7 @@ const rule: Rule.RuleModule = {
252
254
  ...fixtureCallInformation.requestHeaders.map(
253
255
  ({ name, value }) =>
254
256
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, no-nested-ternary, sonarjs/no-nested-template-literals
255
- ` ${name.type === 'Literal' ? (isValidPropertyName(name.value) ? name.value : `'${name.value}'`) : `[${sourceCode.getText(name)}]`}: ${sourceCode.getText(value)},`,
257
+ ` ${name.type === AST_NODE_TYPES.Literal ? (isValidPropertyName(name.value) ? name.value : `'${name.value}'`) : `[${sourceCode.getText(name)}]`}: ${sourceCode.getText(value)},`,
256
258
  ),
257
259
  ` },`,
258
260
  ]
@@ -306,7 +308,7 @@ const rule: Rule.RuleModule = {
306
308
  for (const responseHeadersAccess of responseHeadersAccesses) {
307
309
  if (isInvalidResponseHeadersAccess(responseHeadersAccess)) {
308
310
  const headerAccess = getParent(responseHeadersAccess);
309
- if (headerAccess?.type === 'MemberExpression') {
311
+ if (headerAccess?.type === AST_NODE_TYPES.MemberExpression) {
310
312
  const headerNameNode = headerAccess.property;
311
313
  const headerName = headerAccess.computed
312
314
  ? sourceCode.getText(headerNameNode)
@@ -321,8 +323,8 @@ const rule: Rule.RuleModule = {
321
323
  },
322
324
  });
323
325
  } else if (
324
- headerAccess?.type === 'CallExpression' &&
325
- responseHeadersAccess.property.type === 'Identifier' &&
326
+ headerAccess?.type === AST_NODE_TYPES.CallExpression &&
327
+ responseHeadersAccess.property.type === AST_NODE_TYPES.Identifier &&
326
328
  responseHeadersAccess.property.name === 'get'
327
329
  ) {
328
330
  const headerAccessReplacementText = `${sourceCode.getText(responseHeadersAccess.object)}.headers.get(${sourceCode.getText(headerAccess.arguments[0])})`;
@@ -352,6 +354,6 @@ const rule: Rule.RuleModule = {
352
354
  },
353
355
  };
354
356
  },
355
- };
357
+ });
356
358
 
357
359
  export default rule;
@@ -1,8 +1,8 @@
1
1
  // agent/fetch.ts
2
2
 
3
- import type { Node } from 'estree';
3
+ import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
4
4
 
5
- import { getParent, isBlockStatement } from '../library/tree';
5
+ import { getParent, isBlockStatement } from '../library/ts-tree';
6
6
 
7
7
  export function getResponseBodyRetrievalText(responseVariableName: string) {
8
8
  return `await ${responseVariableName}.json()`;
@@ -12,29 +12,29 @@ export function getResponseHeadersRetrievalText(responseVariableName: string) {
12
12
  return `${responseVariableName}.headers`;
13
13
  }
14
14
 
15
- export function isInvalidResponseHeadersAccess(responseHeadersAccess: Node): boolean {
15
+ export function isInvalidResponseHeadersAccess(responseHeadersAccess: TSESTree.Node): boolean {
16
16
  const responseHeaderAccessParent = getParent(responseHeadersAccess);
17
- if (responseHeaderAccessParent?.type === 'VariableDeclarator') {
17
+ if (responseHeaderAccessParent?.type === AST_NODE_TYPES.VariableDeclarator) {
18
18
  return false;
19
19
  }
20
20
 
21
21
  if (
22
- responseHeaderAccessParent?.type === 'CallExpression' &&
23
- responseHeaderAccessParent.callee.type === 'MemberExpression' &&
24
- responseHeaderAccessParent.callee.property.type === 'Identifier' &&
22
+ responseHeaderAccessParent?.type === AST_NODE_TYPES.CallExpression &&
23
+ responseHeaderAccessParent.callee.type === AST_NODE_TYPES.MemberExpression &&
24
+ responseHeaderAccessParent.callee.property.type === AST_NODE_TYPES.Identifier &&
25
25
  responseHeaderAccessParent.callee.property.name === 'get'
26
26
  ) {
27
27
  return true;
28
28
  }
29
29
 
30
30
  return !(
31
- responseHeaderAccessParent?.type === 'MemberExpression' &&
32
- responseHeaderAccessParent.property.type === 'Identifier' &&
31
+ responseHeaderAccessParent?.type === AST_NODE_TYPES.MemberExpression &&
32
+ responseHeaderAccessParent.property.type === AST_NODE_TYPES.Identifier &&
33
33
  responseHeaderAccessParent.property.name === 'get'
34
34
  );
35
35
  }
36
36
 
37
- export function hasAssertions(fixtureCall: Node): boolean {
37
+ export function hasAssertions(fixtureCall: TSESTree.Node): boolean {
38
38
  if (isBlockStatement(fixtureCall)) {
39
39
  return false;
40
40
  }
@@ -45,10 +45,10 @@ export function hasAssertions(fixtureCall: Node): boolean {
45
45
  }
46
46
 
47
47
  if (
48
- parent.type === 'MemberExpression' &&
49
- parent.property.type === 'Identifier' &&
48
+ parent.type === AST_NODE_TYPES.MemberExpression &&
49
+ parent.property.type === AST_NODE_TYPES.Identifier &&
50
50
  parent.property.name === 'expect' &&
51
- getParent(parent)?.type === 'CallExpression'
51
+ getParent(parent)?.type === AST_NODE_TYPES.CallExpression
52
52
  ) {
53
53
  return true;
54
54
  }