@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 CHANGED
@@ -3,7 +3,7 @@
3
3
  ![npm](https://img.shields.io/npm/v/@vitest/eslint-plugin)
4
4
  [![ci](https://github.com/vitest-dev/eslint-plugin-vitest/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/veritem/eslint-plugin-vitest/actions/workflows/ci.yml)
5
5
 
6
- Eslint plugin for vitest
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 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.
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 eslint (version `v8.57.0` or lower) you can setup this plugin using the following configuration
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 Eslint configuration, make sure to use legacy key like the following
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.9";
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
- context.report({
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.9";
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
- context.report({
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest/eslint-plugin",
3
- "version": "1.1.10",
3
+ "version": "1.1.12",
4
4
  "license": "MIT",
5
5
  "description": "Eslint plugin for vitest",
6
6
  "repository": "vitest-dev/eslint-plugin-vitest",