@vitest/eslint-plugin 1.1.9 → 1.1.11

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.8";
26
+ const version = "1.1.10";
27
27
 
28
28
  function createEslintRule(rule) {
29
29
  const createRule = utils.ESLintUtils.RuleCreator(
@@ -2344,6 +2344,11 @@ const getPromiseCallExpressionNode = (node) => {
2344
2344
  return null;
2345
2345
  };
2346
2346
  const promiseArrayExceptionKey = ({ start, end }) => `${start.line}:${start.column}-${end.line}:${end.column}`;
2347
+ const getNormalizeFunctionExpression = (functionExpression) => {
2348
+ if (functionExpression.parent.type === utils.AST_NODE_TYPES.Property && functionExpression.type === utils.AST_NODE_TYPES.FunctionExpression)
2349
+ return functionExpression.parent;
2350
+ return functionExpression;
2351
+ };
2347
2352
  function getParentIfThenified(node) {
2348
2353
  const grandParentNode = node.parent?.parent;
2349
2354
  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 +2358,13 @@ function getParentIfThenified(node) {
2353
2358
  const findPromiseCallExpressionNode = (node) => node.parent?.parent && [utils.AST_NODE_TYPES.CallExpression, utils.AST_NODE_TYPES.ArrayExpression].includes(
2354
2359
  node.parent.type
2355
2360
  ) ? getPromiseCallExpressionNode(node.parent) : null;
2361
+ const findFirstFunctionExpression = ({
2362
+ parent
2363
+ }) => {
2364
+ if (!parent)
2365
+ return null;
2366
+ return isFunction(parent) ? parent : findFirstFunctionExpression(parent);
2367
+ };
2356
2368
  const isAcceptableReturnNode = (node, allowReturn) => {
2357
2369
  if (allowReturn && node.type === utils.AST_NODE_TYPES.ReturnStatement)
2358
2370
  return true;
@@ -2380,6 +2392,7 @@ const validExpect = createEslintRule({
2380
2392
  promisesWithAsyncAssertionsMustBeAwaited: "Promises which return async assertions must be awaited{{orReturned}}"
2381
2393
  },
2382
2394
  type: "suggestion",
2395
+ fixable: "code",
2383
2396
  schema: [
2384
2397
  {
2385
2398
  type: "object",
@@ -2413,6 +2426,7 @@ const validExpect = createEslintRule({
2413
2426
  }],
2414
2427
  create: (context, [{ alwaysAwait, asyncMatchers = defaultAsyncMatchers$1, minArgs = 1, maxArgs = 1 }]) => {
2415
2428
  const arrayExceptions = /* @__PURE__ */ new Set();
2429
+ const descriptors = [];
2416
2430
  const pushPromiseArrayException = (loc) => arrayExceptions.add(promiseArrayExceptionKey(loc));
2417
2431
  const promiseArrayExceptionExists = (loc) => arrayExceptions.has(promiseArrayExceptionKey(loc));
2418
2432
  const findTopMostMemberExpression = (node) => {
@@ -2507,19 +2521,47 @@ const validExpect = createEslintRule({
2507
2521
  if (!parentNode?.parent || !shouldBeAwaited)
2508
2522
  return;
2509
2523
  const isParentArrayExpression = parentNode.parent.type === utils.AST_NODE_TYPES.ArrayExpression;
2510
- const orReturned = alwaysAwait ? "" : " or returned";
2511
2524
  const targetNode = getParentIfThenified(parentNode);
2512
2525
  const finalNode = findPromiseCallExpressionNode(targetNode) || targetNode;
2513
2526
  if (finalNode.parent && !isAcceptableReturnNode(finalNode.parent, !alwaysAwait) && !promiseArrayExceptionExists(finalNode.loc)) {
2514
- context.report({
2515
- loc: finalNode.loc,
2516
- data: { orReturned },
2527
+ descriptors.push({
2517
2528
  messageId: finalNode === targetNode ? "asyncMustBeAwaited" : "promisesWithAsyncAssertionsMustBeAwaited",
2518
- node
2529
+ node: finalNode
2519
2530
  });
2520
2531
  if (isParentArrayExpression)
2521
2532
  pushPromiseArrayException(finalNode.loc);
2522
2533
  }
2534
+ },
2535
+ "Program:exit"() {
2536
+ const fixes = [];
2537
+ descriptors.forEach(({ node, messageId }, index) => {
2538
+ const orReturned = alwaysAwait ? "" : " or returned";
2539
+ context.report({
2540
+ loc: node.loc,
2541
+ data: { orReturned },
2542
+ messageId,
2543
+ node,
2544
+ fix(fixer) {
2545
+ const functionExpression = findFirstFunctionExpression(node);
2546
+ if (!functionExpression)
2547
+ return null;
2548
+ const foundAsyncFixer = fixes.some((fix) => fix.text === "async ");
2549
+ if (!functionExpression.async && !foundAsyncFixer) {
2550
+ const targetFunction = getNormalizeFunctionExpression(functionExpression);
2551
+ fixes.push(fixer.insertTextBefore(targetFunction, "async "));
2552
+ }
2553
+ const returnStatement = node.parent?.type === utils.AST_NODE_TYPES.ReturnStatement ? node.parent : null;
2554
+ if (alwaysAwait && returnStatement) {
2555
+ const sourceCodeText = context.sourceCode.getText(returnStatement);
2556
+ const replacedText = sourceCodeText.replace("return", "await");
2557
+ fixes.push(fixer.replaceText(returnStatement, replacedText));
2558
+ } else {
2559
+ fixes.push(fixer.insertTextBefore(node, "await "));
2560
+ }
2561
+ return index === descriptors.length - 1 ? fixes : null;
2562
+ }
2563
+ });
2564
+ });
2523
2565
  }
2524
2566
  };
2525
2567
  }
@@ -4634,7 +4676,7 @@ const isVariableAwaitedOrReturned = (variable, context) => {
4634
4676
  return isValueAwaitedOrReturned(variable.id, body, context);
4635
4677
  };
4636
4678
  const validExpectInPromise = createEslintRule({
4637
- name: __filename,
4679
+ name: RULE_NAME,
4638
4680
  meta: {
4639
4681
  docs: {
4640
4682
  description: "Require promises that have expectations in their chain to be valid"
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.8";
7
+ const version = "1.1.10";
8
8
 
9
9
  function createEslintRule(rule) {
10
10
  const createRule = ESLintUtils.RuleCreator(
@@ -2325,6 +2325,11 @@ const getPromiseCallExpressionNode = (node) => {
2325
2325
  return null;
2326
2326
  };
2327
2327
  const promiseArrayExceptionKey = ({ start, end }) => `${start.line}:${start.column}-${end.line}:${end.column}`;
2328
+ const getNormalizeFunctionExpression = (functionExpression) => {
2329
+ if (functionExpression.parent.type === AST_NODE_TYPES.Property && functionExpression.type === AST_NODE_TYPES.FunctionExpression)
2330
+ return functionExpression.parent;
2331
+ return functionExpression;
2332
+ };
2328
2333
  function getParentIfThenified(node) {
2329
2334
  const grandParentNode = node.parent?.parent;
2330
2335
  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 +2339,13 @@ function getParentIfThenified(node) {
2334
2339
  const findPromiseCallExpressionNode = (node) => node.parent?.parent && [AST_NODE_TYPES.CallExpression, AST_NODE_TYPES.ArrayExpression].includes(
2335
2340
  node.parent.type
2336
2341
  ) ? getPromiseCallExpressionNode(node.parent) : null;
2342
+ const findFirstFunctionExpression = ({
2343
+ parent
2344
+ }) => {
2345
+ if (!parent)
2346
+ return null;
2347
+ return isFunction(parent) ? parent : findFirstFunctionExpression(parent);
2348
+ };
2337
2349
  const isAcceptableReturnNode = (node, allowReturn) => {
2338
2350
  if (allowReturn && node.type === AST_NODE_TYPES.ReturnStatement)
2339
2351
  return true;
@@ -2361,6 +2373,7 @@ const validExpect = createEslintRule({
2361
2373
  promisesWithAsyncAssertionsMustBeAwaited: "Promises which return async assertions must be awaited{{orReturned}}"
2362
2374
  },
2363
2375
  type: "suggestion",
2376
+ fixable: "code",
2364
2377
  schema: [
2365
2378
  {
2366
2379
  type: "object",
@@ -2394,6 +2407,7 @@ const validExpect = createEslintRule({
2394
2407
  }],
2395
2408
  create: (context, [{ alwaysAwait, asyncMatchers = defaultAsyncMatchers$1, minArgs = 1, maxArgs = 1 }]) => {
2396
2409
  const arrayExceptions = /* @__PURE__ */ new Set();
2410
+ const descriptors = [];
2397
2411
  const pushPromiseArrayException = (loc) => arrayExceptions.add(promiseArrayExceptionKey(loc));
2398
2412
  const promiseArrayExceptionExists = (loc) => arrayExceptions.has(promiseArrayExceptionKey(loc));
2399
2413
  const findTopMostMemberExpression = (node) => {
@@ -2488,19 +2502,47 @@ const validExpect = createEslintRule({
2488
2502
  if (!parentNode?.parent || !shouldBeAwaited)
2489
2503
  return;
2490
2504
  const isParentArrayExpression = parentNode.parent.type === AST_NODE_TYPES.ArrayExpression;
2491
- const orReturned = alwaysAwait ? "" : " or returned";
2492
2505
  const targetNode = getParentIfThenified(parentNode);
2493
2506
  const finalNode = findPromiseCallExpressionNode(targetNode) || targetNode;
2494
2507
  if (finalNode.parent && !isAcceptableReturnNode(finalNode.parent, !alwaysAwait) && !promiseArrayExceptionExists(finalNode.loc)) {
2495
- context.report({
2496
- loc: finalNode.loc,
2497
- data: { orReturned },
2508
+ descriptors.push({
2498
2509
  messageId: finalNode === targetNode ? "asyncMustBeAwaited" : "promisesWithAsyncAssertionsMustBeAwaited",
2499
- node
2510
+ node: finalNode
2500
2511
  });
2501
2512
  if (isParentArrayExpression)
2502
2513
  pushPromiseArrayException(finalNode.loc);
2503
2514
  }
2515
+ },
2516
+ "Program:exit"() {
2517
+ const fixes = [];
2518
+ descriptors.forEach(({ node, messageId }, index) => {
2519
+ const orReturned = alwaysAwait ? "" : " or returned";
2520
+ context.report({
2521
+ loc: node.loc,
2522
+ data: { orReturned },
2523
+ messageId,
2524
+ node,
2525
+ fix(fixer) {
2526
+ const functionExpression = findFirstFunctionExpression(node);
2527
+ if (!functionExpression)
2528
+ return null;
2529
+ const foundAsyncFixer = fixes.some((fix) => fix.text === "async ");
2530
+ if (!functionExpression.async && !foundAsyncFixer) {
2531
+ const targetFunction = getNormalizeFunctionExpression(functionExpression);
2532
+ fixes.push(fixer.insertTextBefore(targetFunction, "async "));
2533
+ }
2534
+ const returnStatement = node.parent?.type === AST_NODE_TYPES.ReturnStatement ? node.parent : null;
2535
+ if (alwaysAwait && returnStatement) {
2536
+ const sourceCodeText = context.sourceCode.getText(returnStatement);
2537
+ const replacedText = sourceCodeText.replace("return", "await");
2538
+ fixes.push(fixer.replaceText(returnStatement, replacedText));
2539
+ } else {
2540
+ fixes.push(fixer.insertTextBefore(node, "await "));
2541
+ }
2542
+ return index === descriptors.length - 1 ? fixes : null;
2543
+ }
2544
+ });
2545
+ });
2504
2546
  }
2505
2547
  };
2506
2548
  }
@@ -4615,7 +4657,7 @@ const isVariableAwaitedOrReturned = (variable, context) => {
4615
4657
  return isValueAwaitedOrReturned(variable.id, body, context);
4616
4658
  };
4617
4659
  const validExpectInPromise = createEslintRule({
4618
- name: __filename,
4660
+ name: RULE_NAME,
4619
4661
  meta: {
4620
4662
  docs: {
4621
4663
  description: "Require promises that have expectations in their chain to be valid"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest/eslint-plugin",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "license": "MIT",
5
5
  "description": "Eslint plugin for vitest",
6
6
  "repository": "vitest-dev/eslint-plugin-vitest",