@vitest/eslint-plugin 1.1.10 → 1.1.12
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/README.md +5 -5
- package/dist/index.cjs +60 -6
- package/dist/index.mjs +60 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|

|
|
4
4
|
[](https://github.com/veritem/eslint-plugin-vitest/actions/workflows/ci.yml)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
ESLint plugin for Vitest
|
|
7
7
|
|
|
8
8
|
### Installation
|
|
9
9
|
|
|
@@ -21,7 +21,7 @@ npm install @vitest/eslint-plugin --save-dev
|
|
|
21
21
|
|
|
22
22
|
### Usage
|
|
23
23
|
|
|
24
|
-
Make sure you're running
|
|
24
|
+
Make sure you're running ESLint `v9.0.0` or higher for the latest version of this plugin to work. The following example is how your `eslint.config.js` should be setup for this plugin to work for you.
|
|
25
25
|
|
|
26
26
|
```js
|
|
27
27
|
import vitest from "@vitest/eslint-plugin";
|
|
@@ -40,7 +40,7 @@ export default [
|
|
|
40
40
|
];
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
If you're not using the latest version of
|
|
43
|
+
If you're not using the latest version of ESLint (version `v8.57.0` or lower) you can setup this plugin using the following configuration
|
|
44
44
|
|
|
45
45
|
Add `vitest` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:
|
|
46
46
|
|
|
@@ -65,7 +65,7 @@ Then configure the rules you want to use under the rules section.
|
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
If you're using old
|
|
68
|
+
If you're using old ESLint configuration, make sure to use legacy key like the following
|
|
69
69
|
|
|
70
70
|
```js
|
|
71
71
|
{
|
|
@@ -171,7 +171,7 @@ export default [
|
|
|
171
171
|
| [require-to-throw-message](docs/rules/require-to-throw-message.md) | require toThrow() to be called with an error message | | 🌐 | | | |
|
|
172
172
|
| [require-top-level-describe](docs/rules/require-top-level-describe.md) | enforce that all tests are in a top-level describe | | 🌐 | | | |
|
|
173
173
|
| [valid-describe-callback](docs/rules/valid-describe-callback.md) | enforce valid describe callback | ✅ | | | | |
|
|
174
|
-
| [valid-expect](docs/rules/valid-expect.md) | enforce valid `expect()` usage | ✅ | |
|
|
174
|
+
| [valid-expect](docs/rules/valid-expect.md) | enforce valid `expect()` usage | ✅ | | 🔧 | | |
|
|
175
175
|
| [valid-title](docs/rules/valid-title.md) | enforce valid titles | ✅ | | 🔧 | | |
|
|
176
176
|
|
|
177
177
|
<!-- end auto-generated rules list -->
|
package/dist/index.cjs
CHANGED
|
@@ -23,7 +23,7 @@ function _interopNamespaceCompat(e) {
|
|
|
23
23
|
const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
|
|
24
24
|
const ts__default = /*#__PURE__*/_interopDefaultCompat(ts);
|
|
25
25
|
|
|
26
|
-
const version = "1.1.
|
|
26
|
+
const version = "1.1.11";
|
|
27
27
|
|
|
28
28
|
function createEslintRule(rule) {
|
|
29
29
|
const createRule = utils.ESLintUtils.RuleCreator(
|
|
@@ -105,6 +105,15 @@ var ModifierName = /* @__PURE__ */ ((ModifierName2) => {
|
|
|
105
105
|
ModifierName2["not"] = "not";
|
|
106
106
|
ModifierName2["rejects"] = "rejects";
|
|
107
107
|
ModifierName2["resolves"] = "resolves";
|
|
108
|
+
ModifierName2["returns"] = "returns";
|
|
109
|
+
ModifierName2["branded"] = "branded";
|
|
110
|
+
ModifierName2["asserts"] = "asserts";
|
|
111
|
+
ModifierName2["constructorParameters"] = "constructorParameters";
|
|
112
|
+
ModifierName2["parameters"] = "parameters";
|
|
113
|
+
ModifierName2["thisParameter"] = "thisParameter";
|
|
114
|
+
ModifierName2["guards"] = "guards";
|
|
115
|
+
ModifierName2["instance"] = "instance";
|
|
116
|
+
ModifierName2["items"] = "items";
|
|
108
117
|
return ModifierName2;
|
|
109
118
|
})(ModifierName || {});
|
|
110
119
|
var EqualityMatcher = /* @__PURE__ */ ((EqualityMatcher2) => {
|
|
@@ -2344,6 +2353,11 @@ const getPromiseCallExpressionNode = (node) => {
|
|
|
2344
2353
|
return null;
|
|
2345
2354
|
};
|
|
2346
2355
|
const promiseArrayExceptionKey = ({ start, end }) => `${start.line}:${start.column}-${end.line}:${end.column}`;
|
|
2356
|
+
const getNormalizeFunctionExpression = (functionExpression) => {
|
|
2357
|
+
if (functionExpression.parent.type === utils.AST_NODE_TYPES.Property && functionExpression.type === utils.AST_NODE_TYPES.FunctionExpression)
|
|
2358
|
+
return functionExpression.parent;
|
|
2359
|
+
return functionExpression;
|
|
2360
|
+
};
|
|
2347
2361
|
function getParentIfThenified(node) {
|
|
2348
2362
|
const grandParentNode = node.parent?.parent;
|
|
2349
2363
|
if (grandParentNode && grandParentNode.type === utils.AST_NODE_TYPES.CallExpression && grandParentNode.callee.type === utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(grandParentNode.callee.property) && ["then", "catch"].includes(getAccessorValue(grandParentNode.callee.property)) && grandParentNode.parent)
|
|
@@ -2353,6 +2367,13 @@ function getParentIfThenified(node) {
|
|
|
2353
2367
|
const findPromiseCallExpressionNode = (node) => node.parent?.parent && [utils.AST_NODE_TYPES.CallExpression, utils.AST_NODE_TYPES.ArrayExpression].includes(
|
|
2354
2368
|
node.parent.type
|
|
2355
2369
|
) ? getPromiseCallExpressionNode(node.parent) : null;
|
|
2370
|
+
const findFirstFunctionExpression = ({
|
|
2371
|
+
parent
|
|
2372
|
+
}) => {
|
|
2373
|
+
if (!parent)
|
|
2374
|
+
return null;
|
|
2375
|
+
return isFunction(parent) ? parent : findFirstFunctionExpression(parent);
|
|
2376
|
+
};
|
|
2356
2377
|
const isAcceptableReturnNode = (node, allowReturn) => {
|
|
2357
2378
|
if (allowReturn && node.type === utils.AST_NODE_TYPES.ReturnStatement)
|
|
2358
2379
|
return true;
|
|
@@ -2380,6 +2401,7 @@ const validExpect = createEslintRule({
|
|
|
2380
2401
|
promisesWithAsyncAssertionsMustBeAwaited: "Promises which return async assertions must be awaited{{orReturned}}"
|
|
2381
2402
|
},
|
|
2382
2403
|
type: "suggestion",
|
|
2404
|
+
fixable: "code",
|
|
2383
2405
|
schema: [
|
|
2384
2406
|
{
|
|
2385
2407
|
type: "object",
|
|
@@ -2413,6 +2435,7 @@ const validExpect = createEslintRule({
|
|
|
2413
2435
|
}],
|
|
2414
2436
|
create: (context, [{ alwaysAwait, asyncMatchers = defaultAsyncMatchers$1, minArgs = 1, maxArgs = 1 }]) => {
|
|
2415
2437
|
const arrayExceptions = /* @__PURE__ */ new Set();
|
|
2438
|
+
const descriptors = [];
|
|
2416
2439
|
const pushPromiseArrayException = (loc) => arrayExceptions.add(promiseArrayExceptionKey(loc));
|
|
2417
2440
|
const promiseArrayExceptionExists = (loc) => arrayExceptions.has(promiseArrayExceptionKey(loc));
|
|
2418
2441
|
const findTopMostMemberExpression = (node) => {
|
|
@@ -2429,6 +2452,7 @@ const validExpect = createEslintRule({
|
|
|
2429
2452
|
return {
|
|
2430
2453
|
CallExpression(node) {
|
|
2431
2454
|
const vitestFnCall = parseVitestFnCallWithReason(node, context);
|
|
2455
|
+
const settings = parsePluginSettings(context.settings);
|
|
2432
2456
|
if (typeof vitestFnCall === "string") {
|
|
2433
2457
|
const reportingNode = node.parent?.type === utils.AST_NODE_TYPES.MemberExpression ? findTopMostMemberExpression(node.parent).property : node;
|
|
2434
2458
|
if (vitestFnCall === "matcher-not-found") {
|
|
@@ -2452,6 +2476,8 @@ const validExpect = createEslintRule({
|
|
|
2452
2476
|
return;
|
|
2453
2477
|
}
|
|
2454
2478
|
return;
|
|
2479
|
+
} else if (vitestFnCall?.type === "expectTypeOf" && settings.typecheck) {
|
|
2480
|
+
return;
|
|
2455
2481
|
} else if (vitestFnCall?.type !== "expect") {
|
|
2456
2482
|
return;
|
|
2457
2483
|
}
|
|
@@ -2507,19 +2533,47 @@ const validExpect = createEslintRule({
|
|
|
2507
2533
|
if (!parentNode?.parent || !shouldBeAwaited)
|
|
2508
2534
|
return;
|
|
2509
2535
|
const isParentArrayExpression = parentNode.parent.type === utils.AST_NODE_TYPES.ArrayExpression;
|
|
2510
|
-
const orReturned = alwaysAwait ? "" : " or returned";
|
|
2511
2536
|
const targetNode = getParentIfThenified(parentNode);
|
|
2512
2537
|
const finalNode = findPromiseCallExpressionNode(targetNode) || targetNode;
|
|
2513
2538
|
if (finalNode.parent && !isAcceptableReturnNode(finalNode.parent, !alwaysAwait) && !promiseArrayExceptionExists(finalNode.loc)) {
|
|
2514
|
-
|
|
2515
|
-
loc: finalNode.loc,
|
|
2516
|
-
data: { orReturned },
|
|
2539
|
+
descriptors.push({
|
|
2517
2540
|
messageId: finalNode === targetNode ? "asyncMustBeAwaited" : "promisesWithAsyncAssertionsMustBeAwaited",
|
|
2518
|
-
node
|
|
2541
|
+
node: finalNode
|
|
2519
2542
|
});
|
|
2520
2543
|
if (isParentArrayExpression)
|
|
2521
2544
|
pushPromiseArrayException(finalNode.loc);
|
|
2522
2545
|
}
|
|
2546
|
+
},
|
|
2547
|
+
"Program:exit"() {
|
|
2548
|
+
const fixes = [];
|
|
2549
|
+
descriptors.forEach(({ node, messageId }, index) => {
|
|
2550
|
+
const orReturned = alwaysAwait ? "" : " or returned";
|
|
2551
|
+
context.report({
|
|
2552
|
+
loc: node.loc,
|
|
2553
|
+
data: { orReturned },
|
|
2554
|
+
messageId,
|
|
2555
|
+
node,
|
|
2556
|
+
fix(fixer) {
|
|
2557
|
+
const functionExpression = findFirstFunctionExpression(node);
|
|
2558
|
+
if (!functionExpression)
|
|
2559
|
+
return null;
|
|
2560
|
+
const foundAsyncFixer = fixes.some((fix) => fix.text === "async ");
|
|
2561
|
+
if (!functionExpression.async && !foundAsyncFixer) {
|
|
2562
|
+
const targetFunction = getNormalizeFunctionExpression(functionExpression);
|
|
2563
|
+
fixes.push(fixer.insertTextBefore(targetFunction, "async "));
|
|
2564
|
+
}
|
|
2565
|
+
const returnStatement = node.parent?.type === utils.AST_NODE_TYPES.ReturnStatement ? node.parent : null;
|
|
2566
|
+
if (alwaysAwait && returnStatement) {
|
|
2567
|
+
const sourceCodeText = context.sourceCode.getText(returnStatement);
|
|
2568
|
+
const replacedText = sourceCodeText.replace("return", "await");
|
|
2569
|
+
fixes.push(fixer.replaceText(returnStatement, replacedText));
|
|
2570
|
+
} else {
|
|
2571
|
+
fixes.push(fixer.insertTextBefore(node, "await "));
|
|
2572
|
+
}
|
|
2573
|
+
return index === descriptors.length - 1 ? fixes : null;
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
});
|
|
2523
2577
|
}
|
|
2524
2578
|
};
|
|
2525
2579
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { isAbsolute, posix } from 'node:path';
|
|
|
4
4
|
import ts from 'typescript';
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
|
|
7
|
-
const version = "1.1.
|
|
7
|
+
const version = "1.1.11";
|
|
8
8
|
|
|
9
9
|
function createEslintRule(rule) {
|
|
10
10
|
const createRule = ESLintUtils.RuleCreator(
|
|
@@ -86,6 +86,15 @@ var ModifierName = /* @__PURE__ */ ((ModifierName2) => {
|
|
|
86
86
|
ModifierName2["not"] = "not";
|
|
87
87
|
ModifierName2["rejects"] = "rejects";
|
|
88
88
|
ModifierName2["resolves"] = "resolves";
|
|
89
|
+
ModifierName2["returns"] = "returns";
|
|
90
|
+
ModifierName2["branded"] = "branded";
|
|
91
|
+
ModifierName2["asserts"] = "asserts";
|
|
92
|
+
ModifierName2["constructorParameters"] = "constructorParameters";
|
|
93
|
+
ModifierName2["parameters"] = "parameters";
|
|
94
|
+
ModifierName2["thisParameter"] = "thisParameter";
|
|
95
|
+
ModifierName2["guards"] = "guards";
|
|
96
|
+
ModifierName2["instance"] = "instance";
|
|
97
|
+
ModifierName2["items"] = "items";
|
|
89
98
|
return ModifierName2;
|
|
90
99
|
})(ModifierName || {});
|
|
91
100
|
var EqualityMatcher = /* @__PURE__ */ ((EqualityMatcher2) => {
|
|
@@ -2325,6 +2334,11 @@ const getPromiseCallExpressionNode = (node) => {
|
|
|
2325
2334
|
return null;
|
|
2326
2335
|
};
|
|
2327
2336
|
const promiseArrayExceptionKey = ({ start, end }) => `${start.line}:${start.column}-${end.line}:${end.column}`;
|
|
2337
|
+
const getNormalizeFunctionExpression = (functionExpression) => {
|
|
2338
|
+
if (functionExpression.parent.type === AST_NODE_TYPES.Property && functionExpression.type === AST_NODE_TYPES.FunctionExpression)
|
|
2339
|
+
return functionExpression.parent;
|
|
2340
|
+
return functionExpression;
|
|
2341
|
+
};
|
|
2328
2342
|
function getParentIfThenified(node) {
|
|
2329
2343
|
const grandParentNode = node.parent?.parent;
|
|
2330
2344
|
if (grandParentNode && grandParentNode.type === AST_NODE_TYPES.CallExpression && grandParentNode.callee.type === AST_NODE_TYPES.MemberExpression && isSupportedAccessor(grandParentNode.callee.property) && ["then", "catch"].includes(getAccessorValue(grandParentNode.callee.property)) && grandParentNode.parent)
|
|
@@ -2334,6 +2348,13 @@ function getParentIfThenified(node) {
|
|
|
2334
2348
|
const findPromiseCallExpressionNode = (node) => node.parent?.parent && [AST_NODE_TYPES.CallExpression, AST_NODE_TYPES.ArrayExpression].includes(
|
|
2335
2349
|
node.parent.type
|
|
2336
2350
|
) ? getPromiseCallExpressionNode(node.parent) : null;
|
|
2351
|
+
const findFirstFunctionExpression = ({
|
|
2352
|
+
parent
|
|
2353
|
+
}) => {
|
|
2354
|
+
if (!parent)
|
|
2355
|
+
return null;
|
|
2356
|
+
return isFunction(parent) ? parent : findFirstFunctionExpression(parent);
|
|
2357
|
+
};
|
|
2337
2358
|
const isAcceptableReturnNode = (node, allowReturn) => {
|
|
2338
2359
|
if (allowReturn && node.type === AST_NODE_TYPES.ReturnStatement)
|
|
2339
2360
|
return true;
|
|
@@ -2361,6 +2382,7 @@ const validExpect = createEslintRule({
|
|
|
2361
2382
|
promisesWithAsyncAssertionsMustBeAwaited: "Promises which return async assertions must be awaited{{orReturned}}"
|
|
2362
2383
|
},
|
|
2363
2384
|
type: "suggestion",
|
|
2385
|
+
fixable: "code",
|
|
2364
2386
|
schema: [
|
|
2365
2387
|
{
|
|
2366
2388
|
type: "object",
|
|
@@ -2394,6 +2416,7 @@ const validExpect = createEslintRule({
|
|
|
2394
2416
|
}],
|
|
2395
2417
|
create: (context, [{ alwaysAwait, asyncMatchers = defaultAsyncMatchers$1, minArgs = 1, maxArgs = 1 }]) => {
|
|
2396
2418
|
const arrayExceptions = /* @__PURE__ */ new Set();
|
|
2419
|
+
const descriptors = [];
|
|
2397
2420
|
const pushPromiseArrayException = (loc) => arrayExceptions.add(promiseArrayExceptionKey(loc));
|
|
2398
2421
|
const promiseArrayExceptionExists = (loc) => arrayExceptions.has(promiseArrayExceptionKey(loc));
|
|
2399
2422
|
const findTopMostMemberExpression = (node) => {
|
|
@@ -2410,6 +2433,7 @@ const validExpect = createEslintRule({
|
|
|
2410
2433
|
return {
|
|
2411
2434
|
CallExpression(node) {
|
|
2412
2435
|
const vitestFnCall = parseVitestFnCallWithReason(node, context);
|
|
2436
|
+
const settings = parsePluginSettings(context.settings);
|
|
2413
2437
|
if (typeof vitestFnCall === "string") {
|
|
2414
2438
|
const reportingNode = node.parent?.type === AST_NODE_TYPES.MemberExpression ? findTopMostMemberExpression(node.parent).property : node;
|
|
2415
2439
|
if (vitestFnCall === "matcher-not-found") {
|
|
@@ -2433,6 +2457,8 @@ const validExpect = createEslintRule({
|
|
|
2433
2457
|
return;
|
|
2434
2458
|
}
|
|
2435
2459
|
return;
|
|
2460
|
+
} else if (vitestFnCall?.type === "expectTypeOf" && settings.typecheck) {
|
|
2461
|
+
return;
|
|
2436
2462
|
} else if (vitestFnCall?.type !== "expect") {
|
|
2437
2463
|
return;
|
|
2438
2464
|
}
|
|
@@ -2488,19 +2514,47 @@ const validExpect = createEslintRule({
|
|
|
2488
2514
|
if (!parentNode?.parent || !shouldBeAwaited)
|
|
2489
2515
|
return;
|
|
2490
2516
|
const isParentArrayExpression = parentNode.parent.type === AST_NODE_TYPES.ArrayExpression;
|
|
2491
|
-
const orReturned = alwaysAwait ? "" : " or returned";
|
|
2492
2517
|
const targetNode = getParentIfThenified(parentNode);
|
|
2493
2518
|
const finalNode = findPromiseCallExpressionNode(targetNode) || targetNode;
|
|
2494
2519
|
if (finalNode.parent && !isAcceptableReturnNode(finalNode.parent, !alwaysAwait) && !promiseArrayExceptionExists(finalNode.loc)) {
|
|
2495
|
-
|
|
2496
|
-
loc: finalNode.loc,
|
|
2497
|
-
data: { orReturned },
|
|
2520
|
+
descriptors.push({
|
|
2498
2521
|
messageId: finalNode === targetNode ? "asyncMustBeAwaited" : "promisesWithAsyncAssertionsMustBeAwaited",
|
|
2499
|
-
node
|
|
2522
|
+
node: finalNode
|
|
2500
2523
|
});
|
|
2501
2524
|
if (isParentArrayExpression)
|
|
2502
2525
|
pushPromiseArrayException(finalNode.loc);
|
|
2503
2526
|
}
|
|
2527
|
+
},
|
|
2528
|
+
"Program:exit"() {
|
|
2529
|
+
const fixes = [];
|
|
2530
|
+
descriptors.forEach(({ node, messageId }, index) => {
|
|
2531
|
+
const orReturned = alwaysAwait ? "" : " or returned";
|
|
2532
|
+
context.report({
|
|
2533
|
+
loc: node.loc,
|
|
2534
|
+
data: { orReturned },
|
|
2535
|
+
messageId,
|
|
2536
|
+
node,
|
|
2537
|
+
fix(fixer) {
|
|
2538
|
+
const functionExpression = findFirstFunctionExpression(node);
|
|
2539
|
+
if (!functionExpression)
|
|
2540
|
+
return null;
|
|
2541
|
+
const foundAsyncFixer = fixes.some((fix) => fix.text === "async ");
|
|
2542
|
+
if (!functionExpression.async && !foundAsyncFixer) {
|
|
2543
|
+
const targetFunction = getNormalizeFunctionExpression(functionExpression);
|
|
2544
|
+
fixes.push(fixer.insertTextBefore(targetFunction, "async "));
|
|
2545
|
+
}
|
|
2546
|
+
const returnStatement = node.parent?.type === AST_NODE_TYPES.ReturnStatement ? node.parent : null;
|
|
2547
|
+
if (alwaysAwait && returnStatement) {
|
|
2548
|
+
const sourceCodeText = context.sourceCode.getText(returnStatement);
|
|
2549
|
+
const replacedText = sourceCodeText.replace("return", "await");
|
|
2550
|
+
fixes.push(fixer.replaceText(returnStatement, replacedText));
|
|
2551
|
+
} else {
|
|
2552
|
+
fixes.push(fixer.insertTextBefore(node, "await "));
|
|
2553
|
+
}
|
|
2554
|
+
return index === descriptors.length - 1 ? fixes : null;
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
});
|
|
2504
2558
|
}
|
|
2505
2559
|
};
|
|
2506
2560
|
}
|