@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.
- package/dist-cjs/index.cjs +781 -501
- package/dist-cjs/metafile.json +152 -43
- package/dist-mjs/fixture/concurrent-promises.mjs +261 -0
- package/dist-mjs/fixture/fetch-header-getter.mjs +24 -17
- package/dist-mjs/fixture/fetch.mjs +20 -0
- package/dist-mjs/fixture/no-fixture.mjs +9 -10
- package/dist-mjs/fixture/response-reference.mjs +4 -4
- package/dist-mjs/fixture/url.mjs +8 -0
- package/dist-mjs/fixture/variable.mjs +8 -0
- package/dist-mjs/index.mjs +6 -3
- package/dist-types/fixture/concurrent-promises.d.ts +4 -0
- package/dist-types/fixture/fetch.d.ts +3 -0
- package/dist-types/fixture/url.d.ts +1 -0
- package/dist-types/fixture/variable.d.ts +1 -0
- package/dist-types/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/fixture/concurrent-promises.ts +341 -0
- package/src/fixture/fetch-header-getter.ts +28 -27
- package/src/fixture/fetch.ts +30 -0
- package/src/fixture/no-fixture.ts +10 -15
- package/src/fixture/response-reference.ts +4 -12
- package/src/fixture/url.ts +6 -0
- package/src/fixture/variable.ts +5 -0
- package/src/index.ts +3 -0
|
@@ -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,
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const directHeadersReferences = responseHeadersReferences.filter((headersReference) => {
|
|
47
|
+
const headersAccess = getParent(headersReference);
|
|
48
|
+
return headersAccess?.type !== 'VariableDeclarator';
|
|
49
|
+
});
|
|
48
50
|
|
|
49
|
-
const
|
|
50
|
-
.map(
|
|
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
|
|
67
|
-
|
|
64
|
+
const invalidHeadersReferences = [...directHeadersReferences, ...indirectHeadersReferences].filter(
|
|
65
|
+
isInvalidResponseHeadersAccess,
|
|
68
66
|
);
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
);
|
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: {
|