@checkdigit/eslint-plugin 6.6.0-PR.75-20dc → 6.6.0-PR.75-66d8

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.
@@ -1,4 +1,4 @@
1
- // no-fixture.ts
1
+ // fixture/no-fixture.ts
2
2
 
3
3
  /*
4
4
  * Copyright (c) 2021-2024 Check Digit, LLC
@@ -6,12 +6,13 @@
6
6
  * This code is licensed under the MIT license (see LICENSE.txt for details).
7
7
  */
8
8
 
9
- import type { Identifier, MemberExpression, VariableDeclarator } from 'estree';
9
+ import type { Identifier, VariableDeclarator } from 'estree';
10
10
  import type { Rule } from 'eslint';
11
11
  import { analyzeResponseReferences } from './response-reference';
12
12
  import { strict as assert } from 'node:assert';
13
13
  import getDocumentationUrl from '../get-documentation-url';
14
14
  import { getParent } from '../ast/tree';
15
+ import { isInvalidResponseHeadersAccess } from './fetch';
15
16
 
16
17
  export const ruleId = 'fetch-header-getter';
17
18
 
@@ -42,12 +43,13 @@ const rule: Rule.RuleModule = {
42
43
  );
43
44
  assert.ok(responseVariable);
44
45
 
45
- const directHeaderReferences = responseHeadersReferences
46
- .map(getParent)
47
- .filter((parent): parent is MemberExpression => parent?.type === 'MemberExpression');
46
+ const directHeadersReferences = responseHeadersReferences.filter((headersReference) => {
47
+ const headersAccess = getParent(headersReference);
48
+ return headersAccess?.type !== 'VariableDeclarator';
49
+ });
48
50
 
49
- const indirectHeaderReferences = responseHeadersReferences
50
- .map((reference) => getParent(reference))
51
+ const indirectHeadersReferences = responseHeadersReferences
52
+ .map(getParent)
51
53
  .filter((parent): parent is VariableDeclarator => parent?.type === 'VariableDeclarator')
52
54
  .map((declarator) => (declarator.id as Identifier).name)
53
55
  .map((redefinedHeadersVariableName) => {
@@ -55,32 +57,31 @@ const rule: Rule.RuleModule = {
55
57
  const identifier = variable.identifiers[0];
56
58
  return identifier?.type === 'Identifier' && identifier.name === redefinedHeadersVariableName;
57
59
  });
58
- return (
59
- headersVariable?.references
60
- .map((reference) => getParent(reference.identifier))
61
- .filter((parent): parent is MemberExpression => parent?.type === 'MemberExpression') ?? []
62
- );
60
+ return headersVariable?.references.map((reference) => reference.identifier) ?? [];
63
61
  })
64
62
  .flat();
65
63
 
66
- const invalidHeaderReferences = [...directHeaderReferences, ...indirectHeaderReferences].filter(
67
- (reference) => !(reference.property.type === 'Identifier' && reference.property.name === 'get'),
64
+ const invalidHeadersReferences = [...directHeadersReferences, ...indirectHeadersReferences].filter(
65
+ isInvalidResponseHeadersAccess,
68
66
  );
69
67
 
70
- invalidHeaderReferences.forEach((reference) => {
71
- const headerNameNode = reference.property;
72
- const headerName = reference.computed
73
- ? sourceCode.getText(headerNameNode)
74
- : `'${sourceCode.getText(headerNameNode)}'`;
75
- const replacementText = `${sourceCode.getText(reference.object)}.get(${headerName})`;
68
+ invalidHeadersReferences.forEach((headersReference) => {
69
+ const headerAccess = getParent(headersReference);
70
+ if (headerAccess?.type === 'MemberExpression') {
71
+ const headerNameNode = headerAccess.property;
72
+ const headerName = headerAccess.computed
73
+ ? sourceCode.getText(headerNameNode)
74
+ : `'${sourceCode.getText(headerNameNode)}'`;
75
+ const replacementText = `${sourceCode.getText(headerAccess.object)}.get(${headerName})`;
76
76
 
77
- context.report({
78
- node: reference,
79
- messageId: 'shouldUseHeaderGetter',
80
- fix(fixer) {
81
- return fixer.replaceText(reference, replacementText);
82
- },
83
- });
77
+ context.report({
78
+ node: headerAccess,
79
+ messageId: 'shouldUseHeaderGetter',
80
+ fix(fixer) {
81
+ return fixer.replaceText(headerAccess, replacementText);
82
+ },
83
+ });
84
+ }
84
85
  });
85
86
  },
86
87
  };
@@ -0,0 +1,30 @@
1
+ // fixture/fetch.ts
2
+
3
+ import type { Node } from 'estree';
4
+ import { getParent } from '../ast/tree';
5
+
6
+ export function getResponseBodyRetrievalText(responseVariableName: string) {
7
+ return `await ${responseVariableName}.json()`;
8
+ }
9
+
10
+ export function isInvalidResponseHeadersAccess(responseHeadersAccess: Node) {
11
+ const responseHeaderAccessParent = getParent(responseHeadersAccess);
12
+ if (responseHeaderAccessParent?.type === 'VariableDeclarator') {
13
+ return false;
14
+ }
15
+
16
+ if (
17
+ responseHeaderAccessParent?.type === 'CallExpression' &&
18
+ responseHeaderAccessParent.callee.type === 'MemberExpression' &&
19
+ responseHeaderAccessParent.callee.property.type === 'Identifier' &&
20
+ responseHeaderAccessParent.callee.property.name === 'get'
21
+ ) {
22
+ return true;
23
+ }
24
+
25
+ return !(
26
+ responseHeaderAccessParent?.type === 'MemberExpression' &&
27
+ responseHeaderAccessParent.property.type === 'Identifier' &&
28
+ responseHeaderAccessParent.property.name === 'get'
29
+ );
30
+ }
@@ -1,4 +1,4 @@
1
- // no-fixture.ts
1
+ // fixture/no-fixture.ts
2
2
 
3
3
  /*
4
4
  * Copyright (c) 2021-2024 Check Digit, LLC
@@ -22,6 +22,9 @@ import { analyzeResponseReferences } from './response-reference';
22
22
  import { strict as assert } from 'node:assert';
23
23
  import getDocumentationUrl from '../get-documentation-url';
24
24
  import { getIndentation } from '../ast/format';
25
+ import { getResponseBodyRetrievalText } from './fetch';
26
+ import { isValidPropertyName } from './variable';
27
+ import { replaceEndpointUrlPrefixWithBasePath } from './url';
25
28
 
26
29
  export const ruleId = 'no-fixture';
27
30
 
@@ -34,6 +37,7 @@ interface FixtureCallInformation {
34
37
  assertions?: Expression[][];
35
38
  inlineStatementNode?: Node;
36
39
  inlineBodyReference?: MemberExpression;
40
+ isConcurrent?: boolean;
37
41
  }
38
42
 
39
43
  // recursively analyze the fixture/supertest call chain to collect information of request/response
@@ -46,6 +50,8 @@ function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInfo
46
50
  // direct return, no variable declaration or await
47
51
  results.fixtureNode = call;
48
52
  results.rootNode = parent;
53
+ } else if (parent.type === 'ArrayExpression') {
54
+ results.isConcurrent = true;
49
55
  } else if (parent.type === 'AwaitExpression') {
50
56
  results.fixtureNode = call;
51
57
  const enclosingStatement = getEnclosingStatement(parent);
@@ -92,16 +98,6 @@ function analyzeFixtureCall(call: SimpleCallExpression, results: FixtureCallInfo
92
98
  }
93
99
  }
94
100
 
95
- // `/sample-service/v1/ping` -> `${BASE_PATH}/ping`
96
- function replaceEndpointUrlPrefixWithBasePath(url: string) {
97
- // eslint-disable-next-line no-template-curly-in-string
98
- return url.replace(/`\/\w+(?<parts>-\w+)*\/v\d+\//u, '`${BASE_PATH}/');
99
- }
100
-
101
- function isValidPropertyName(name: unknown) {
102
- return typeof name === 'string' && /^[a-zA-Z_$][a-zA-Z_$0-9]*$/u.test(name);
103
- }
104
-
105
101
  // eslint-disable-next-line sonarjs/cognitive-complexity
106
102
  function createResponseAssertions(
107
103
  fixtureCallInformation: FixtureCallInformation,
@@ -215,10 +211,6 @@ function isResponseBodyRedefinition(responseBodyReference: MemberExpression): bo
215
211
  return parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier';
216
212
  }
217
213
 
218
- function getResponseBodyRetrievalText(responseVariableName: string) {
219
- return `await ${responseVariableName}.json()`;
220
- }
221
-
222
214
  const rule: Rule.RuleModule = {
223
215
  meta: {
224
216
  type: 'suggestion',
@@ -256,6 +248,9 @@ const rule: Rule.RuleModule = {
256
248
 
257
249
  const fixtureCallInformation = {} as FixtureCallInformation;
258
250
  analyzeFixtureCall(fixtureCall, fixtureCallInformation, sourceCode);
251
+ if (fixtureCallInformation.isConcurrent === true) {
252
+ return;
253
+ }
259
254
 
260
255
  const {
261
256
  variable: responseVariable,
@@ -1,4 +1,4 @@
1
- // no-fixture.ts
1
+ // fixture/response-reference.ts
2
2
 
3
3
  /*
4
4
  * Copyright (c) 2021-2024 Check Digit, LLC
@@ -52,27 +52,19 @@ export function analyzeResponseReferences(
52
52
  // e.g. response.body
53
53
  results.bodyReferences = responseReferences.filter(
54
54
  (node): node is MemberExpression =>
55
- node !== null &&
56
- node !== undefined &&
57
- node.type === 'MemberExpression' &&
58
- node.property.type === 'Identifier' &&
59
- node.property.name === 'body',
55
+ node?.type === 'MemberExpression' && node.property.type === 'Identifier' && node.property.name === 'body',
60
56
  );
61
57
  // e.g. response.headers / response.header / response.get()
62
58
  results.headersReferences = responseReferences.filter(
63
59
  (node): node is MemberExpression =>
64
- node !== null &&
65
- node !== undefined &&
66
- node.type === 'MemberExpression' &&
60
+ node?.type === 'MemberExpression' &&
67
61
  node.property.type === 'Identifier' &&
68
62
  (node.property.name === 'header' || node.property.name === 'headers' || node.property.name === 'get'),
69
63
  );
70
64
  // e.g. response.status / response.statusCode
71
65
  results.statusReferences = responseReferences.filter(
72
66
  (node): node is MemberExpression =>
73
- node !== null &&
74
- node !== undefined &&
75
- node.type === 'MemberExpression' &&
67
+ node?.type === 'MemberExpression' &&
76
68
  node.property.type === 'Identifier' &&
77
69
  (node.property.name === 'status' || node.property.name === 'statusCode'),
78
70
  );
@@ -0,0 +1,6 @@
1
+ // fixture/url.ts
2
+
3
+ export function replaceEndpointUrlPrefixWithBasePath(url: string) {
4
+ // eslint-disable-next-line no-template-curly-in-string
5
+ return url.replace(/`\/\w+(?<parts>-\w+)*\/v\d+\//u, '`${BASE_PATH}/');
6
+ }
@@ -0,0 +1,5 @@
1
+ // fixture/variable.ts
2
+
3
+ export function isValidPropertyName(name: unknown) {
4
+ return typeof name === 'string' && /^[a-zA-Z_$][a-zA-Z_$0-9]*$/u.test(name);
5
+ }
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * This code is licensed under the MIT license (see LICENSE.txt for details).
7
7
  */
8
8
 
9
+ import concurrentPromises, { ruleId as concurrentPromisesRuleId } from './fixture/concurrent-promises';
9
10
  import fetchHeaderGetter, { ruleId as fetchHeaderGetterRuleId } from './fixture/fetch-header-getter';
10
11
  import invalidJsonStringify, { ruleId as invalidJsonStringifyRuleId } from './invalid-json-stringify';
11
12
  import noFixture, { ruleId as noFixtureRuleId } from './fixture/no-fixture';
@@ -35,6 +36,7 @@ export default {
35
36
  [noPromiseInstanceMethodRuleId]: noPromiseInstanceMethod,
36
37
  [noFixtureRuleId]: noFixture,
37
38
  [fetchHeaderGetterRuleId]: fetchHeaderGetter,
39
+ [concurrentPromisesRuleId]: concurrentPromises,
38
40
  },
39
41
  configs: {
40
42
  all: {
@@ -52,6 +54,7 @@ export default {
52
54
  [`@checkdigit/${noPromiseInstanceMethodRuleId}`]: 'error',
53
55
  [`@checkdigit/${noFixtureRuleId}`]: 'error',
54
56
  [`@checkdigit/${fetchHeaderGetterRuleId}`]: 'error',
57
+ [`@checkdigit/${concurrentPromisesRuleId}`]: 'error',
55
58
  },
56
59
  },
57
60
  recommended: {