@cjser/eslint-node-test 0.2.0-cjser.2
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-cjser/index.cjs +8561 -0
- package/index.d.ts +11 -0
- package/index.js +49 -0
- package/license +9 -0
- package/package.json +141 -0
- package/readme.md +172 -0
- package/rules/assertion-arguments.js +147 -0
- package/rules/ast/call-or-new-expression.js +94 -0
- package/rules/ast/function-types.js +7 -0
- package/rules/ast/index.js +17 -0
- package/rules/ast/is-expression-statement.js +7 -0
- package/rules/ast/is-function.js +5 -0
- package/rules/ast/is-loop.js +5 -0
- package/rules/ast/is-member-expression.js +92 -0
- package/rules/ast/is-method-call.js +62 -0
- package/rules/ast/literal.js +32 -0
- package/rules/ast/loop-types.js +9 -0
- package/rules/consistent-assert-style.js +169 -0
- package/rules/consistent-assert-throws-callback-style.js +241 -0
- package/rules/consistent-modifier-style.js +95 -0
- package/rules/consistent-test-context-name.js +75 -0
- package/rules/consistent-test-filename.js +70 -0
- package/rules/consistent-test-it.js +84 -0
- package/rules/fix/index.js +5 -0
- package/rules/fix/remove-argument.js +58 -0
- package/rules/fix/replace-member-expression-property.js +25 -0
- package/rules/hooks-order.js +147 -0
- package/rules/index.js +84 -0
- package/rules/max-assertions.js +88 -0
- package/rules/max-nested-describe.js +70 -0
- package/rules/no-assert-in-describe.js +61 -0
- package/rules/no-assert-in-hook.js +61 -0
- package/rules/no-assert-match-string.js +166 -0
- package/rules/no-assert-throws-async.js +113 -0
- package/rules/no-assert-throws-call.js +106 -0
- package/rules/no-assert-throws-multiple-statements.js +194 -0
- package/rules/no-assert-throws-string.js +78 -0
- package/rules/no-async-describe.js +50 -0
- package/rules/no-async-fn-without-await.js +74 -0
- package/rules/no-callback-and-promise.js +56 -0
- package/rules/no-commented-tests.js +64 -0
- package/rules/no-compound-assertion.js +131 -0
- package/rules/no-conditional-assertion.js +108 -0
- package/rules/no-conditional-in-test.js +66 -0
- package/rules/no-conditional-tests.js +75 -0
- package/rules/no-conflicting-modifiers.js +73 -0
- package/rules/no-constant-assertion.js +218 -0
- package/rules/no-done-callback.js +58 -0
- package/rules/no-duplicate-assertions.js +172 -0
- package/rules/no-duplicate-hooks.js +75 -0
- package/rules/no-duplicate-plan.js +209 -0
- package/rules/no-export.js +45 -0
- package/rules/no-identical-assertion-arguments.js +84 -0
- package/rules/no-identical-title.js +101 -0
- package/rules/no-incorrect-deep-equal.js +113 -0
- package/rules/no-incorrect-strict-equal.js +99 -0
- package/rules/no-loop-static-title.js +93 -0
- package/rules/no-misused-concurrency.js +85 -0
- package/rules/no-mock-timers-destructured-import.js +150 -0
- package/rules/no-nested-tests.js +71 -0
- package/rules/no-only-test.js +11 -0
- package/rules/no-parent-test-context.js +244 -0
- package/rules/no-process-env-mutation.js +495 -0
- package/rules/no-process-exit-in-test.js +96 -0
- package/rules/no-skip-test.js +11 -0
- package/rules/no-skip-without-reason.js +90 -0
- package/rules/no-skip-without-return.js +123 -0
- package/rules/no-sleep-in-test.js +482 -0
- package/rules/no-snapshot-in-loop.js +132 -0
- package/rules/no-standalone-assert.js +51 -0
- package/rules/no-test-inside-hook.js +68 -0
- package/rules/no-test-return-statement.js +217 -0
- package/rules/no-todo-test.js +11 -0
- package/rules/no-unawaited-promise-assertion.js +366 -0
- package/rules/no-unawaited-rejects.js +87 -0
- package/rules/no-unawaited-subtest.js +66 -0
- package/rules/no-unknown-test-options.js +77 -0
- package/rules/no-useless-assertion.js +60 -0
- package/rules/prefer-assert-match.js +253 -0
- package/rules/prefer-assert-throws.js +97 -0
- package/rules/prefer-async-await.js +203 -0
- package/rules/prefer-context-mock.js +55 -0
- package/rules/prefer-diagnostic.js +94 -0
- package/rules/prefer-equality-assertion.js +124 -0
- package/rules/prefer-hooks-on-top.js +75 -0
- package/rules/prefer-lowercase-title.js +119 -0
- package/rules/prefer-mock-method.js +115 -0
- package/rules/prefer-strict-assert.js +82 -0
- package/rules/prefer-test-context-assert.js +162 -0
- package/rules/prefer-todo.js +98 -0
- package/rules/require-assertion.js +99 -0
- package/rules/require-await-concurrent-subtests.js +119 -0
- package/rules/require-context-assert-with-plan.js +127 -0
- package/rules/require-hook.js +108 -0
- package/rules/require-mock-timers-advance.js +313 -0
- package/rules/require-mock-timers-apis.js +383 -0
- package/rules/require-throws-expectation.js +65 -0
- package/rules/require-throws-validator-return-true.js +156 -0
- package/rules/require-top-level-describe.js +89 -0
- package/rules/rule/index.js +9 -0
- package/rules/rule/to-eslint-create.js +35 -0
- package/rules/rule/to-eslint-listener.js +52 -0
- package/rules/rule/to-eslint-problem.js +36 -0
- package/rules/rule/to-eslint-rule-fixer.js +47 -0
- package/rules/rule/to-eslint-rule.js +36 -0
- package/rules/rule/to-eslint-rules.js +8 -0
- package/rules/rule/unicorn-context.js +34 -0
- package/rules/rule/unicorn-listeners.js +56 -0
- package/rules/rule/utilities.js +26 -0
- package/rules/shared/test-modifier-rule.js +92 -0
- package/rules/test-title-format.js +86 -0
- package/rules/test-title.js +139 -0
- package/rules/utils/contains-suspension-point.js +35 -0
- package/rules/utils/get-comments.js +15 -0
- package/rules/utils/get-documentation-url.js +9 -0
- package/rules/utils/get-enclosing-function.js +18 -0
- package/rules/utils/index.js +15 -0
- package/rules/utils/is-conditional-branch.js +37 -0
- package/rules/utils/is-promise-type.js +26 -0
- package/rules/utils/is-same-reference.js +179 -0
- package/rules/utils/is-value-not-usable.js +5 -0
- package/rules/utils/node-test.js +883 -0
- package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +78 -0
- package/rules/utils/parentheses/iterate-surrounding-parentheses.js +80 -0
- package/rules/utils/parentheses/parentheses.js +69 -0
- package/rules/utils/types.js +5 -0
- package/rules/utils/unwrap-typescript-expression.js +16 -0
- package/rules/valid-describe-callback.js +63 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {findVariable} from '@eslint-community/eslint-utils';
|
|
2
|
+
import {
|
|
3
|
+
resolveImports,
|
|
4
|
+
parseTestCall,
|
|
5
|
+
getTestCallback,
|
|
6
|
+
getSubtestReceiver,
|
|
7
|
+
} from './utils/node-test.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
@import {TSESTree as ESTree} from '@typescript-eslint/types';
|
|
11
|
+
@import * as ESLint from 'eslint';
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const MESSAGE_ID_ERROR = 'no-parent-test-context/error';
|
|
15
|
+
const MESSAGE_ID_SUGGESTION = 'no-parent-test-context/suggestion';
|
|
16
|
+
const messages = {
|
|
17
|
+
[MESSAGE_ID_ERROR]: 'Do not use the parent test context `{{parent}}` inside this subtest. Use this subtest\'s own context instead.',
|
|
18
|
+
[MESSAGE_ID_SUGGESTION]: 'Replace `{{parent}}` with `{{child}}`.',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const TEST_MODULES = new Set(['node:test', 'test']);
|
|
22
|
+
|
|
23
|
+
function getCalleeRoot(node) {
|
|
24
|
+
let current = node;
|
|
25
|
+
|
|
26
|
+
while (current.type === 'MemberExpression') {
|
|
27
|
+
current = current.object;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (current.type === 'Identifier') {
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isNodeTestImport(variable) {
|
|
36
|
+
return variable?.defs.some(definition =>
|
|
37
|
+
definition.type === 'ImportBinding'
|
|
38
|
+
&& TEST_MODULES.has(definition.parent.source.value));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isInsideNode(node, container, sourceCode) {
|
|
42
|
+
const [nodeStart, nodeEnd] = sourceCode.getRange(node);
|
|
43
|
+
const [containerStart, containerEnd] = sourceCode.getRange(container);
|
|
44
|
+
return nodeStart >= containerStart && nodeEnd <= containerEnd;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getParameterIdentifier(parameter) {
|
|
48
|
+
if (parameter?.type === 'Identifier') {
|
|
49
|
+
return parameter;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (parameter?.type === 'AssignmentPattern' && parameter.left.type === 'Identifier') {
|
|
53
|
+
return parameter.left;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getParameterVariable(parameter, sourceCode) {
|
|
58
|
+
const identifier = getParameterIdentifier(parameter);
|
|
59
|
+
|
|
60
|
+
return identifier ? findVariable(sourceCode.getScope(identifier), identifier) : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getResolvedReference(node, sourceCode) {
|
|
64
|
+
return sourceCode
|
|
65
|
+
.getScope(node)
|
|
66
|
+
.references
|
|
67
|
+
.find(reference => reference.identifier === node)
|
|
68
|
+
?.resolved;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getParentContextFrame(frames, variable, currentFrameIndex) {
|
|
72
|
+
for (let index = currentFrameIndex - 1; index >= 0; index -= 1) {
|
|
73
|
+
const frame = frames[index];
|
|
74
|
+
if (frame.contextVariable === variable) {
|
|
75
|
+
return frame;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getContextReceiverFrame(node, frames, sourceCode) {
|
|
81
|
+
const receiver = getSubtestReceiver(node);
|
|
82
|
+
if (!receiver) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const variable = getResolvedReference(receiver, sourceCode);
|
|
87
|
+
if (!variable) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return frames.findLast(frame => frame.contextVariable === variable);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isShorthandPropertyValue(node) {
|
|
95
|
+
return node.parent.type === 'Property' && node.parent.shorthand;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isSubtestReceiver(node) {
|
|
99
|
+
let current = node.parent;
|
|
100
|
+
while (current?.type === 'MemberExpression') {
|
|
101
|
+
current = current.parent;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return current?.type === 'CallExpression' && getSubtestReceiver(current) === node;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function canSuggestContextReplacement(node, currentFrame, sourceCode) {
|
|
108
|
+
if (
|
|
109
|
+
!currentFrame.contextParameter
|
|
110
|
+
|| !isInsideNode(node, currentFrame.callback.body, sourceCode)
|
|
111
|
+
|| isShorthandPropertyValue(node)
|
|
112
|
+
) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return findVariable(sourceCode.getScope(node), currentFrame.contextParameter.name) === currentFrame.contextVariable;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getContainingFrameIndex(node, frames, sourceCode) {
|
|
120
|
+
for (let index = frames.length - 1; index >= 0; index -= 1) {
|
|
121
|
+
if (isInsideNode(node, frames[index].callback, sourceCode)) {
|
|
122
|
+
return index;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getParentContextProblem(node, frames, sourceCode) {
|
|
128
|
+
const currentFrameIndex = getContainingFrameIndex(node, frames, sourceCode);
|
|
129
|
+
if (currentFrameIndex === undefined) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const currentFrame = frames[currentFrameIndex];
|
|
134
|
+
if (!currentFrame.isSubtest) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const variable = getResolvedReference(node, sourceCode);
|
|
139
|
+
if (!variable) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const parentFrame = getParentContextFrame(frames, variable, currentFrameIndex);
|
|
144
|
+
if (!parentFrame) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const data = {
|
|
149
|
+
parent: parentFrame.contextParameter.name,
|
|
150
|
+
child: currentFrame.contextParameter?.name,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const problem = {
|
|
154
|
+
node,
|
|
155
|
+
messageId: MESSAGE_ID_ERROR,
|
|
156
|
+
data,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (canSuggestContextReplacement(node, currentFrame, sourceCode)) {
|
|
160
|
+
problem.suggest = [
|
|
161
|
+
{
|
|
162
|
+
messageId: MESSAGE_ID_SUGGESTION,
|
|
163
|
+
data,
|
|
164
|
+
fix: fixer => fixer.replaceText(node, currentFrame.contextParameter.name),
|
|
165
|
+
},
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return problem;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** @param {ESLint.Rule.RuleContext} context */
|
|
173
|
+
const create = context => {
|
|
174
|
+
const {sourceCode} = context;
|
|
175
|
+
const imports = resolveImports(context);
|
|
176
|
+
if (!imports.isTestFile) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const frames = [];
|
|
181
|
+
|
|
182
|
+
context.on('CallExpression', node => {
|
|
183
|
+
const receiver = getSubtestReceiver(node);
|
|
184
|
+
const receiverProblem = receiver && getParentContextProblem(receiver, frames, sourceCode);
|
|
185
|
+
const isSubtest = getContextReceiverFrame(node, frames, sourceCode) !== undefined;
|
|
186
|
+
const parsed = parseTestCall(node, imports);
|
|
187
|
+
const root = getCalleeRoot(node.callee);
|
|
188
|
+
const isTest = parsed?.kind === 'test' && root && isNodeTestImport(getResolvedReference(root, sourceCode));
|
|
189
|
+
|
|
190
|
+
if (!isTest && !isSubtest) {
|
|
191
|
+
return receiverProblem;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const callback = getTestCallback(node);
|
|
195
|
+
if (!callback) {
|
|
196
|
+
return receiverProblem;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const contextParameter = callback.params[0];
|
|
200
|
+
const contextVariable = getParameterVariable(contextParameter, sourceCode);
|
|
201
|
+
|
|
202
|
+
frames.push({
|
|
203
|
+
node,
|
|
204
|
+
callback,
|
|
205
|
+
contextParameter: getParameterIdentifier(contextParameter),
|
|
206
|
+
contextVariable,
|
|
207
|
+
isSubtest,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return receiverProblem;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
context.on('Identifier', node => {
|
|
214
|
+
if (isSubtestReceiver(node)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return getParentContextProblem(node, frames, sourceCode);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
context.onExit('CallExpression', node => {
|
|
222
|
+
if (frames.at(-1)?.node === node) {
|
|
223
|
+
frames.pop();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/** @type {ESLint.Rule.RuleModule} */
|
|
229
|
+
const config = {
|
|
230
|
+
create,
|
|
231
|
+
meta: {
|
|
232
|
+
type: 'problem',
|
|
233
|
+
docs: {
|
|
234
|
+
description: 'Disallow references to parent test contexts inside subtests.',
|
|
235
|
+
recommended: 'unopinionated',
|
|
236
|
+
},
|
|
237
|
+
hasSuggestions: true,
|
|
238
|
+
schema: [],
|
|
239
|
+
messages,
|
|
240
|
+
languages: ['js/js'],
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export default config;
|