@vitest/eslint-plugin 1.5.4 → 1.6.1

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
@@ -251,12 +251,12 @@ export default defineConfig({
251
251
  | [prefer-to-be-object](docs/rules/prefer-to-be-object.md) | enforce using toBeObject() | | 🌐 | | 🔧 | | | |
252
252
  | [prefer-to-be-truthy](docs/rules/prefer-to-be-truthy.md) | enforce using `toBeTruthy` | | | 🌐 | 🔧 | | | |
253
253
  | [prefer-to-contain](docs/rules/prefer-to-contain.md) | enforce using toContain() | | 🌐 | | 🔧 | | | |
254
+ | [prefer-to-have-been-called-times](docs/rules/prefer-to-have-been-called-times.md) | Suggest using `toHaveBeenCalledTimes()` | | 🌐 | | 🔧 | | | |
254
255
  | [prefer-to-have-length](docs/rules/prefer-to-have-length.md) | enforce using toHaveLength() | | 🌐 | | 🔧 | | | |
255
256
  | [prefer-todo](docs/rules/prefer-todo.md) | enforce using `test.todo` | | 🌐 | | 🔧 | | | |
256
257
  | [prefer-vi-mocked](docs/rules/prefer-vi-mocked.md) | require `vi.mocked()` over `fn as Mock` | | 🌐 | | 🔧 | | 💭 | |
257
258
  | [require-awaited-expect-poll](docs/rules/require-awaited-expect-poll.md) | ensure that every `expect.poll` call is awaited | | 🌐 | | | | | |
258
259
  | [require-hook](docs/rules/require-hook.md) | require setup and teardown to be within a hook | | 🌐 | | | | | |
259
- | [require-import-vi-mock](docs/rules/require-import-vi-mock.md) | require usage of import in vi.mock() | | 🌐 | | 🔧 | | | |
260
260
  | [require-local-test-context-for-concurrent-snapshots](docs/rules/require-local-test-context-for-concurrent-snapshots.md) | require local Test Context for concurrent snapshot tests | ✅ | 🌐 | | | | | |
261
261
  | [require-mock-type-parameters](docs/rules/require-mock-type-parameters.md) | enforce using type parameters with vitest mock functions | | 🌐 | | 🔧 | | | |
262
262
  | [require-to-throw-message](docs/rules/require-to-throw-message.md) | require toThrow() to be called with an error message | | 🌐 | | | | | |
package/dist/index.cjs CHANGED
@@ -31,7 +31,7 @@ let __typescript_eslint_scope_manager = require("@typescript-eslint/scope-manage
31
31
  __typescript_eslint_scope_manager = __toESM(__typescript_eslint_scope_manager);
32
32
 
33
33
  //#region package.json
34
- var version = "1.5.4";
34
+ var version = "1.6.1";
35
35
 
36
36
  //#endregion
37
37
  //#region src/utils/index.ts
@@ -1738,7 +1738,16 @@ const resolveScope = (scope, identifier) => {
1738
1738
  if ((property?.key.type === __typescript_eslint_utils.AST_NODE_TYPES.Identifier ? property.key : void 0)?.name === identifier) return "testContext";
1739
1739
  }
1740
1740
  /** if detect test function is created with `.extend()` */
1741
- if (def.node.type === __typescript_eslint_utils.AST_NODE_TYPES.VariableDeclarator && def.node.id.type === __typescript_eslint_utils.AST_NODE_TYPES.Identifier && Object.prototype.hasOwnProperty.call(TestCaseName, def.node.id.name) && def.node.init?.type === __typescript_eslint_utils.AST_NODE_TYPES.CallExpression && def.node.init.callee.type === __typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && isIdentifier(def.node.init.callee.property, "extend")) return "testContext";
1741
+ if (def.node.type === __typescript_eslint_utils.AST_NODE_TYPES.VariableDeclarator && def.node.id.type === __typescript_eslint_utils.AST_NODE_TYPES.Identifier && def.node.init?.type === __typescript_eslint_utils.AST_NODE_TYPES.CallExpression && def.node.init.callee.type === __typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && isIdentifier(def.node.init.callee.property, "extend")) {
1742
+ const rootName = getNodeName(def.node.init.callee.object)?.split(".")[0];
1743
+ if (rootName && rootName !== identifier) {
1744
+ const resolved = resolveScope(currentScope, rootName);
1745
+ if (resolved && typeof resolved === "object" && Object.hasOwn(TestCaseName, resolved.imported)) return {
1746
+ ...resolved,
1747
+ local: identifier
1748
+ };
1749
+ }
1750
+ }
1742
1751
  const namedParam = isFunction(def.node) ? def.node.params.find((params) => params.type === __typescript_eslint_utils.AST_NODE_TYPES.Identifier) : void 0;
1743
1752
  if (namedParam && isAncestorTestCaseCall(namedParam.parent)) return "testContext";
1744
1753
  const importDetails = describePossibleImportDef(def);
@@ -4725,11 +4734,11 @@ var prefer_import_in_mock_default = createEslintRule({
4725
4734
  create(context) {
4726
4735
  return { CallExpression(node) {
4727
4736
  if (node.callee.type !== __typescript_eslint_utils.AST_NODE_TYPES.MemberExpression) return;
4728
- const { object, property } = node.callee;
4729
- if (object.type !== __typescript_eslint_utils.AST_NODE_TYPES.Identifier || object.name !== "vi" || property.type !== __typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
4730
- const apiName = property.name;
4737
+ if (parseVitestFnCall(node, context)?.type !== "vi") return false;
4738
+ const { property } = node.callee;
4739
+ if (property.type != __typescript_eslint_utils.AST_NODE_TYPES.Identifier || property.name != "mock") return;
4731
4740
  const pathArg = node.arguments[0];
4732
- if (apiName === "mock" && pathArg && pathArg.type === __typescript_eslint_utils.AST_NODE_TYPES.Literal) context.report({
4741
+ if (pathArg && pathArg.type === __typescript_eslint_utils.AST_NODE_TYPES.Literal) context.report({
4733
4742
  messageId: "preferImport",
4734
4743
  data: { path: pathArg.value },
4735
4744
  node,
@@ -5657,47 +5666,11 @@ var require_hook_default = createEslintRule({
5657
5666
  }
5658
5667
  });
5659
5668
 
5660
- //#endregion
5661
- //#region src/rules/require-import-vi-mock.ts
5662
- const RULE_NAME$10 = "require-import-vi-mock";
5663
- var require_import_vi_mock_default = createEslintRule({
5664
- name: RULE_NAME$10,
5665
- meta: {
5666
- fixable: "code",
5667
- type: "suggestion",
5668
- docs: {
5669
- description: "require usage of import in vi.mock()",
5670
- requiresTypeChecking: false,
5671
- recommended: false
5672
- },
5673
- messages: { requireImport: "Replace '{{path}}' with import('{{path}}')" },
5674
- schema: []
5675
- },
5676
- defaultOptions: [],
5677
- create(context) {
5678
- return { CallExpression(node) {
5679
- if (node.callee.type !== __typescript_eslint_utils.AST_NODE_TYPES.MemberExpression) return;
5680
- if (parseVitestFnCall(node, context)?.type !== "vi") return false;
5681
- const { property } = node.callee;
5682
- if (property.type !== __typescript_eslint_utils.AST_NODE_TYPES.Identifier || property.name !== "mock") return;
5683
- const pathArg = node.arguments[0];
5684
- if (pathArg && pathArg.type === __typescript_eslint_utils.AST_NODE_TYPES.Literal) context.report({
5685
- messageId: "requireImport",
5686
- data: { path: pathArg.value },
5687
- node: pathArg,
5688
- fix(fixer) {
5689
- return fixer.replaceText(pathArg, `import('${pathArg.value}')`);
5690
- }
5691
- });
5692
- } };
5693
- }
5694
- });
5695
-
5696
5669
  //#endregion
5697
5670
  //#region src/rules/require-local-test-context-for-concurrent-snapshots.ts
5698
- const RULE_NAME$9 = "require-local-test-context-for-concurrent-snapshots";
5671
+ const RULE_NAME$10 = "require-local-test-context-for-concurrent-snapshots";
5699
5672
  var require_local_test_context_for_concurrent_snapshots_default = createEslintRule({
5700
- name: RULE_NAME$9,
5673
+ name: RULE_NAME$10,
5701
5674
  meta: {
5702
5675
  docs: {
5703
5676
  description: "require local Test Context for concurrent snapshot tests",
@@ -5738,9 +5711,9 @@ var require_local_test_context_for_concurrent_snapshots_default = createEslintRu
5738
5711
 
5739
5712
  //#endregion
5740
5713
  //#region src/rules/require-mock-type-parameters.ts
5741
- const RULE_NAME$8 = "require-mock-type-parameters";
5714
+ const RULE_NAME$9 = "require-mock-type-parameters";
5742
5715
  var require_mock_type_parameters_default = createEslintRule({
5743
- name: RULE_NAME$8,
5716
+ name: RULE_NAME$9,
5744
5717
  meta: {
5745
5718
  type: "suggestion",
5746
5719
  docs: {
@@ -5777,9 +5750,9 @@ var require_mock_type_parameters_default = createEslintRule({
5777
5750
 
5778
5751
  //#endregion
5779
5752
  //#region src/rules/require-to-throw-message.ts
5780
- const RULE_NAME$7 = "require-to-throw-message";
5753
+ const RULE_NAME$8 = "require-to-throw-message";
5781
5754
  var require_to_throw_message_default = createEslintRule({
5782
- name: RULE_NAME$7,
5755
+ name: RULE_NAME$8,
5783
5756
  meta: {
5784
5757
  type: "suggestion",
5785
5758
  docs: {
@@ -5807,9 +5780,9 @@ var require_to_throw_message_default = createEslintRule({
5807
5780
 
5808
5781
  //#endregion
5809
5782
  //#region src/rules/require-top-level-describe.ts
5810
- const RULE_NAME$6 = "require-top-level-describe";
5783
+ const RULE_NAME$7 = "require-top-level-describe";
5811
5784
  var require_top_level_describe_default = createEslintRule({
5812
- name: RULE_NAME$6,
5785
+ name: RULE_NAME$7,
5813
5786
  meta: {
5814
5787
  docs: {
5815
5788
  description: "enforce that all tests are in a top-level describe",
@@ -5878,7 +5851,7 @@ var require_top_level_describe_default = createEslintRule({
5878
5851
 
5879
5852
  //#endregion
5880
5853
  //#region src/rules/valid-describe-callback.ts
5881
- const RULE_NAME$5 = "valid-describe-callback";
5854
+ const RULE_NAME$6 = "valid-describe-callback";
5882
5855
  const paramsLocation = (params) => {
5883
5856
  const [first] = params;
5884
5857
  const last = params[params.length - 1];
@@ -5900,7 +5873,7 @@ const reportUnexpectedReturnInDescribe = (blockStatement, context) => {
5900
5873
  });
5901
5874
  };
5902
5875
  var valid_describe_callback_default = createEslintRule({
5903
- name: RULE_NAME$5,
5876
+ name: RULE_NAME$6,
5904
5877
  meta: {
5905
5878
  type: "problem",
5906
5879
  docs: {
@@ -5967,7 +5940,7 @@ var valid_describe_callback_default = createEslintRule({
5967
5940
 
5968
5941
  //#endregion
5969
5942
  //#region src/rules/valid-expect-in-promise.ts
5970
- const RULE_NAME$4 = "valid-expect-in-promise";
5943
+ const RULE_NAME$5 = "valid-expect-in-promise";
5971
5944
  const defaultAsyncMatchers$1 = ["toRejectWith", "toResolveWith"];
5972
5945
  const isPromiseChainCall = (node) => {
5973
5946
  if (node.type === __typescript_eslint_utils.AST_NODE_TYPES.CallExpression && node.callee.type === __typescript_eslint_utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.callee.property)) {
@@ -6099,7 +6072,7 @@ const isVariableAwaitedOrReturned = (variable, context) => {
6099
6072
  return isValueAwaitedOrReturned(variable.id, body, context);
6100
6073
  };
6101
6074
  var valid_expect_in_promise_default = createEslintRule({
6102
- name: RULE_NAME$4,
6075
+ name: RULE_NAME$5,
6103
6076
  meta: {
6104
6077
  docs: { description: "require promises that have expectations in their chain to be valid" },
6105
6078
  messages: { expectInFloatingPromise: "This promise should either be returned or awaited to ensure the expects in its chain are called" },
@@ -6159,7 +6132,7 @@ var valid_expect_in_promise_default = createEslintRule({
6159
6132
 
6160
6133
  //#endregion
6161
6134
  //#region src/rules/valid-expect.ts
6162
- const RULE_NAME$3 = "valid-expect";
6135
+ const RULE_NAME$4 = "valid-expect";
6163
6136
  const defaultAsyncMatchers = ["toReject", "toResolve"];
6164
6137
  /**
6165
6138
  * Async assertions might be called in Promise
@@ -6194,7 +6167,7 @@ const isAcceptableReturnNode = (node, allowReturn) => {
6194
6167
  return [__typescript_eslint_utils.AST_NODE_TYPES.ArrowFunctionExpression, __typescript_eslint_utils.AST_NODE_TYPES.AwaitExpression].includes(node.type);
6195
6168
  };
6196
6169
  var valid_expect_default = createEslintRule({
6197
- name: RULE_NAME$3,
6170
+ name: RULE_NAME$4,
6198
6171
  meta: {
6199
6172
  docs: {
6200
6173
  description: "enforce valid `expect()` usage",
@@ -6387,7 +6360,7 @@ var valid_expect_default = createEslintRule({
6387
6360
 
6388
6361
  //#endregion
6389
6362
  //#region src/rules/valid-title.ts
6390
- const RULE_NAME$2 = "valid-title";
6363
+ const RULE_NAME$3 = "valid-title";
6391
6364
  const trimFXPrefix = (word) => ["f", "x"].includes(word.charAt(0)) ? word.substring(1) : word;
6392
6365
  const quoteStringValue = (node) => node.type === __typescript_eslint_utils.AST_NODE_TYPES.TemplateLiteral ? `\`${node.quasis[0].value.raw}\`` : node.raw;
6393
6366
  const MatcherAndMessageSchema = {
@@ -6426,7 +6399,7 @@ const doesBinaryExpressionContainStringNode = (binaryExp) => {
6426
6399
  return isStringNode(binaryExp.left);
6427
6400
  };
6428
6401
  var valid_title_default = createEslintRule({
6429
- name: RULE_NAME$2,
6402
+ name: RULE_NAME$3,
6430
6403
  meta: {
6431
6404
  docs: {
6432
6405
  description: "enforce valid titles",
@@ -6579,9 +6552,9 @@ var valid_title_default = createEslintRule({
6579
6552
 
6580
6553
  //#endregion
6581
6554
  //#region src/rules/warn-todo.ts
6582
- const RULE_NAME$1 = "warn-todo";
6555
+ const RULE_NAME$2 = "warn-todo";
6583
6556
  var warn_todo_default = createEslintRule({
6584
- name: RULE_NAME$1,
6557
+ name: RULE_NAME$2,
6585
6558
  meta: {
6586
6559
  docs: {
6587
6560
  description: "disallow `.todo` usage",
@@ -6608,7 +6581,7 @@ var warn_todo_default = createEslintRule({
6608
6581
 
6609
6582
  //#endregion
6610
6583
  //#region src/rules/no-unneeded-async-expect-function.ts
6611
- const RULE_NAME = "no-unneeded-async-expect-function";
6584
+ const RULE_NAME$1 = "no-unneeded-async-expect-function";
6612
6585
  const getAwaitedCallExpression = (expression) => {
6613
6586
  if (!expression.async) return null;
6614
6587
  if (expression.type === __typescript_eslint_utils.AST_NODE_TYPES.ArrowFunctionExpression && expression.body.type === __typescript_eslint_utils.AST_NODE_TYPES.AwaitExpression && expression.body.argument.type === __typescript_eslint_utils.AST_NODE_TYPES.CallExpression) return expression.body.argument;
@@ -6618,7 +6591,7 @@ const getAwaitedCallExpression = (expression) => {
6618
6591
  return null;
6619
6592
  };
6620
6593
  var no_unneeded_async_expect_function_default = createEslintRule({
6621
- name: RULE_NAME,
6594
+ name: RULE_NAME$1,
6622
6595
  meta: {
6623
6596
  docs: { description: "Disallow unnecessary async function wrapper for expected promises" },
6624
6597
  fixable: "code",
@@ -6649,6 +6622,42 @@ var no_unneeded_async_expect_function_default = createEslintRule({
6649
6622
  }
6650
6623
  });
6651
6624
 
6625
+ //#endregion
6626
+ //#region src/rules/prefer-to-have-been-called-times.ts
6627
+ const RULE_NAME = "prefer-to-have-been-called-times";
6628
+ var prefer_to_have_been_called_times_default = createEslintRule({
6629
+ name: RULE_NAME,
6630
+ meta: {
6631
+ fixable: "code",
6632
+ docs: { description: "Suggest using `toHaveBeenCalledTimes()`" },
6633
+ messages: { preferMatcher: "Prefer `toHaveBeenCalledTimes`" },
6634
+ type: "suggestion",
6635
+ schema: []
6636
+ },
6637
+ defaultOptions: [],
6638
+ create(context) {
6639
+ return { CallExpression(node) {
6640
+ const vitestFnCall = parseVitestFnCall(node, context);
6641
+ if (vitestFnCall?.type !== "expect") return;
6642
+ const { parent: expect } = vitestFnCall.head.node;
6643
+ if (expect?.type !== __typescript_eslint_utils.AST_NODE_TYPES.CallExpression) return;
6644
+ const { matcher } = vitestFnCall;
6645
+ if (!isSupportedAccessor(matcher, "toHaveLength")) return;
6646
+ const [argument] = expect.arguments;
6647
+ if (argument?.type !== __typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || !isSupportedAccessor(argument.property, "calls")) return;
6648
+ const { object } = argument;
6649
+ if (object.type !== __typescript_eslint_utils.AST_NODE_TYPES.MemberExpression || !isSupportedAccessor(object.property, "mock")) return;
6650
+ context.report({
6651
+ messageId: "preferMatcher",
6652
+ node: matcher,
6653
+ fix(fixer) {
6654
+ return [fixer.removeRange([object.property.range[0] - 1, argument.range[1]]), fixer.replaceTextRange([matcher.parent.object.range[1], matcher.parent.range[1]], ".toHaveBeenCalledTimes")];
6655
+ }
6656
+ });
6657
+ } };
6658
+ }
6659
+ });
6660
+
6652
6661
  //#endregion
6653
6662
  //#region src/rules/index.ts
6654
6663
  const rules = {
@@ -6716,12 +6725,12 @@ const rules = {
6716
6725
  "prefer-to-be-truthy": prefer_to_be_truthy_default,
6717
6726
  "prefer-to-be": prefer_to_be_default,
6718
6727
  "prefer-to-contain": prefer_to_contain_default,
6728
+ "prefer-to-have-been-called-times": prefer_to_have_been_called_times_default,
6719
6729
  "prefer-to-have-length": prefer_to_have_length_default,
6720
6730
  "prefer-todo": prefer_todo_default,
6721
6731
  "prefer-vi-mocked": prefer_vi_mocked_default,
6722
6732
  "require-awaited-expect-poll": require_awaited_expect_poll_default,
6723
6733
  "require-hook": require_hook_default,
6724
- "require-import-vi-mock": require_import_vi_mock_default,
6725
6734
  "require-local-test-context-for-concurrent-snapshots": require_local_test_context_for_concurrent_snapshots_default,
6726
6735
  "require-mock-type-parameters": require_mock_type_parameters_default,
6727
6736
  "require-to-throw-message": require_to_throw_message_default,
@@ -6814,6 +6823,7 @@ const allRules = {
6814
6823
  "prefer-to-be-truthy": "off",
6815
6824
  "prefer-to-be": "warn",
6816
6825
  "prefer-to-contain": "warn",
6826
+ "prefer-to-have-been-called-times": "warn",
6817
6827
  "prefer-to-have-length": "warn",
6818
6828
  "prefer-todo": "warn",
6819
6829
  "prefer-vi-mocked": "warn",
@@ -6826,8 +6836,7 @@ const allRules = {
6826
6836
  "valid-expect-in-promise": "warn",
6827
6837
  "valid-expect": "warn",
6828
6838
  "valid-title": "warn",
6829
- "require-awaited-expect-poll": "warn",
6830
- "require-import-vi-mock": "warn"
6839
+ "require-awaited-expect-poll": "warn"
6831
6840
  };
6832
6841
  const recommendedRules = {
6833
6842
  "expect-expect": "error",
package/dist/index.d.cts CHANGED
@@ -133,6 +133,7 @@ declare const plugin: {
133
133
  readonly "vitest/prefer-to-be-truthy": "off";
134
134
  readonly "vitest/prefer-to-be": "warn";
135
135
  readonly "vitest/prefer-to-contain": "warn";
136
+ readonly "vitest/prefer-to-have-been-called-times": "warn";
136
137
  readonly "vitest/prefer-to-have-length": "warn";
137
138
  readonly "vitest/prefer-todo": "warn";
138
139
  readonly "vitest/prefer-vi-mocked": "warn";
@@ -146,7 +147,6 @@ declare const plugin: {
146
147
  readonly "vitest/valid-expect": "warn";
147
148
  readonly "vitest/valid-title": "warn";
148
149
  readonly "vitest/require-awaited-expect-poll": "warn";
149
- readonly "vitest/require-import-vi-mock": "warn";
150
150
  };
151
151
  };
152
152
  readonly env: {
package/dist/index.d.ts CHANGED
@@ -133,6 +133,7 @@ declare const plugin: {
133
133
  readonly "vitest/prefer-to-be-truthy": "off";
134
134
  readonly "vitest/prefer-to-be": "warn";
135
135
  readonly "vitest/prefer-to-contain": "warn";
136
+ readonly "vitest/prefer-to-have-been-called-times": "warn";
136
137
  readonly "vitest/prefer-to-have-length": "warn";
137
138
  readonly "vitest/prefer-todo": "warn";
138
139
  readonly "vitest/prefer-vi-mocked": "warn";
@@ -146,7 +147,6 @@ declare const plugin: {
146
147
  readonly "vitest/valid-expect": "warn";
147
148
  readonly "vitest/valid-title": "warn";
148
149
  readonly "vitest/require-awaited-expect-poll": "warn";
149
- readonly "vitest/require-import-vi-mock": "warn";
150
150
  };
151
151
  };
152
152
  readonly env: {
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { isAbsolute, posix } from "node:path";
4
4
  import { DefinitionType } from "@typescript-eslint/scope-manager";
5
5
 
6
6
  //#region package.json
7
- var version = "1.5.4";
7
+ var version = "1.6.1";
8
8
 
9
9
  //#endregion
10
10
  //#region src/utils/index.ts
@@ -1711,7 +1711,16 @@ const resolveScope = (scope, identifier) => {
1711
1711
  if ((property?.key.type === AST_NODE_TYPES.Identifier ? property.key : void 0)?.name === identifier) return "testContext";
1712
1712
  }
1713
1713
  /** if detect test function is created with `.extend()` */
1714
- if (def.node.type === AST_NODE_TYPES.VariableDeclarator && def.node.id.type === AST_NODE_TYPES.Identifier && Object.prototype.hasOwnProperty.call(TestCaseName, def.node.id.name) && def.node.init?.type === AST_NODE_TYPES.CallExpression && def.node.init.callee.type === AST_NODE_TYPES.MemberExpression && isIdentifier(def.node.init.callee.property, "extend")) return "testContext";
1714
+ if (def.node.type === AST_NODE_TYPES.VariableDeclarator && def.node.id.type === AST_NODE_TYPES.Identifier && def.node.init?.type === AST_NODE_TYPES.CallExpression && def.node.init.callee.type === AST_NODE_TYPES.MemberExpression && isIdentifier(def.node.init.callee.property, "extend")) {
1715
+ const rootName = getNodeName(def.node.init.callee.object)?.split(".")[0];
1716
+ if (rootName && rootName !== identifier) {
1717
+ const resolved = resolveScope(currentScope, rootName);
1718
+ if (resolved && typeof resolved === "object" && Object.hasOwn(TestCaseName, resolved.imported)) return {
1719
+ ...resolved,
1720
+ local: identifier
1721
+ };
1722
+ }
1723
+ }
1715
1724
  const namedParam = isFunction(def.node) ? def.node.params.find((params) => params.type === AST_NODE_TYPES.Identifier) : void 0;
1716
1725
  if (namedParam && isAncestorTestCaseCall(namedParam.parent)) return "testContext";
1717
1726
  const importDetails = describePossibleImportDef(def);
@@ -4698,11 +4707,11 @@ var prefer_import_in_mock_default = createEslintRule({
4698
4707
  create(context) {
4699
4708
  return { CallExpression(node) {
4700
4709
  if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return;
4701
- const { object, property } = node.callee;
4702
- if (object.type !== AST_NODE_TYPES.Identifier || object.name !== "vi" || property.type !== AST_NODE_TYPES.Identifier) return;
4703
- const apiName = property.name;
4710
+ if (parseVitestFnCall(node, context)?.type !== "vi") return false;
4711
+ const { property } = node.callee;
4712
+ if (property.type != AST_NODE_TYPES.Identifier || property.name != "mock") return;
4704
4713
  const pathArg = node.arguments[0];
4705
- if (apiName === "mock" && pathArg && pathArg.type === AST_NODE_TYPES.Literal) context.report({
4714
+ if (pathArg && pathArg.type === AST_NODE_TYPES.Literal) context.report({
4706
4715
  messageId: "preferImport",
4707
4716
  data: { path: pathArg.value },
4708
4717
  node,
@@ -5630,47 +5639,11 @@ var require_hook_default = createEslintRule({
5630
5639
  }
5631
5640
  });
5632
5641
 
5633
- //#endregion
5634
- //#region src/rules/require-import-vi-mock.ts
5635
- const RULE_NAME$10 = "require-import-vi-mock";
5636
- var require_import_vi_mock_default = createEslintRule({
5637
- name: RULE_NAME$10,
5638
- meta: {
5639
- fixable: "code",
5640
- type: "suggestion",
5641
- docs: {
5642
- description: "require usage of import in vi.mock()",
5643
- requiresTypeChecking: false,
5644
- recommended: false
5645
- },
5646
- messages: { requireImport: "Replace '{{path}}' with import('{{path}}')" },
5647
- schema: []
5648
- },
5649
- defaultOptions: [],
5650
- create(context) {
5651
- return { CallExpression(node) {
5652
- if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return;
5653
- if (parseVitestFnCall(node, context)?.type !== "vi") return false;
5654
- const { property } = node.callee;
5655
- if (property.type !== AST_NODE_TYPES.Identifier || property.name !== "mock") return;
5656
- const pathArg = node.arguments[0];
5657
- if (pathArg && pathArg.type === AST_NODE_TYPES.Literal) context.report({
5658
- messageId: "requireImport",
5659
- data: { path: pathArg.value },
5660
- node: pathArg,
5661
- fix(fixer) {
5662
- return fixer.replaceText(pathArg, `import('${pathArg.value}')`);
5663
- }
5664
- });
5665
- } };
5666
- }
5667
- });
5668
-
5669
5642
  //#endregion
5670
5643
  //#region src/rules/require-local-test-context-for-concurrent-snapshots.ts
5671
- const RULE_NAME$9 = "require-local-test-context-for-concurrent-snapshots";
5644
+ const RULE_NAME$10 = "require-local-test-context-for-concurrent-snapshots";
5672
5645
  var require_local_test_context_for_concurrent_snapshots_default = createEslintRule({
5673
- name: RULE_NAME$9,
5646
+ name: RULE_NAME$10,
5674
5647
  meta: {
5675
5648
  docs: {
5676
5649
  description: "require local Test Context for concurrent snapshot tests",
@@ -5711,9 +5684,9 @@ var require_local_test_context_for_concurrent_snapshots_default = createEslintRu
5711
5684
 
5712
5685
  //#endregion
5713
5686
  //#region src/rules/require-mock-type-parameters.ts
5714
- const RULE_NAME$8 = "require-mock-type-parameters";
5687
+ const RULE_NAME$9 = "require-mock-type-parameters";
5715
5688
  var require_mock_type_parameters_default = createEslintRule({
5716
- name: RULE_NAME$8,
5689
+ name: RULE_NAME$9,
5717
5690
  meta: {
5718
5691
  type: "suggestion",
5719
5692
  docs: {
@@ -5750,9 +5723,9 @@ var require_mock_type_parameters_default = createEslintRule({
5750
5723
 
5751
5724
  //#endregion
5752
5725
  //#region src/rules/require-to-throw-message.ts
5753
- const RULE_NAME$7 = "require-to-throw-message";
5726
+ const RULE_NAME$8 = "require-to-throw-message";
5754
5727
  var require_to_throw_message_default = createEslintRule({
5755
- name: RULE_NAME$7,
5728
+ name: RULE_NAME$8,
5756
5729
  meta: {
5757
5730
  type: "suggestion",
5758
5731
  docs: {
@@ -5780,9 +5753,9 @@ var require_to_throw_message_default = createEslintRule({
5780
5753
 
5781
5754
  //#endregion
5782
5755
  //#region src/rules/require-top-level-describe.ts
5783
- const RULE_NAME$6 = "require-top-level-describe";
5756
+ const RULE_NAME$7 = "require-top-level-describe";
5784
5757
  var require_top_level_describe_default = createEslintRule({
5785
- name: RULE_NAME$6,
5758
+ name: RULE_NAME$7,
5786
5759
  meta: {
5787
5760
  docs: {
5788
5761
  description: "enforce that all tests are in a top-level describe",
@@ -5851,7 +5824,7 @@ var require_top_level_describe_default = createEslintRule({
5851
5824
 
5852
5825
  //#endregion
5853
5826
  //#region src/rules/valid-describe-callback.ts
5854
- const RULE_NAME$5 = "valid-describe-callback";
5827
+ const RULE_NAME$6 = "valid-describe-callback";
5855
5828
  const paramsLocation = (params) => {
5856
5829
  const [first] = params;
5857
5830
  const last = params[params.length - 1];
@@ -5873,7 +5846,7 @@ const reportUnexpectedReturnInDescribe = (blockStatement, context) => {
5873
5846
  });
5874
5847
  };
5875
5848
  var valid_describe_callback_default = createEslintRule({
5876
- name: RULE_NAME$5,
5849
+ name: RULE_NAME$6,
5877
5850
  meta: {
5878
5851
  type: "problem",
5879
5852
  docs: {
@@ -5940,7 +5913,7 @@ var valid_describe_callback_default = createEslintRule({
5940
5913
 
5941
5914
  //#endregion
5942
5915
  //#region src/rules/valid-expect-in-promise.ts
5943
- const RULE_NAME$4 = "valid-expect-in-promise";
5916
+ const RULE_NAME$5 = "valid-expect-in-promise";
5944
5917
  const defaultAsyncMatchers$1 = ["toRejectWith", "toResolveWith"];
5945
5918
  const isPromiseChainCall = (node) => {
5946
5919
  if (node.type === AST_NODE_TYPES.CallExpression && node.callee.type === AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.callee.property)) {
@@ -6072,7 +6045,7 @@ const isVariableAwaitedOrReturned = (variable, context) => {
6072
6045
  return isValueAwaitedOrReturned(variable.id, body, context);
6073
6046
  };
6074
6047
  var valid_expect_in_promise_default = createEslintRule({
6075
- name: RULE_NAME$4,
6048
+ name: RULE_NAME$5,
6076
6049
  meta: {
6077
6050
  docs: { description: "require promises that have expectations in their chain to be valid" },
6078
6051
  messages: { expectInFloatingPromise: "This promise should either be returned or awaited to ensure the expects in its chain are called" },
@@ -6132,7 +6105,7 @@ var valid_expect_in_promise_default = createEslintRule({
6132
6105
 
6133
6106
  //#endregion
6134
6107
  //#region src/rules/valid-expect.ts
6135
- const RULE_NAME$3 = "valid-expect";
6108
+ const RULE_NAME$4 = "valid-expect";
6136
6109
  const defaultAsyncMatchers = ["toReject", "toResolve"];
6137
6110
  /**
6138
6111
  * Async assertions might be called in Promise
@@ -6167,7 +6140,7 @@ const isAcceptableReturnNode = (node, allowReturn) => {
6167
6140
  return [AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.AwaitExpression].includes(node.type);
6168
6141
  };
6169
6142
  var valid_expect_default = createEslintRule({
6170
- name: RULE_NAME$3,
6143
+ name: RULE_NAME$4,
6171
6144
  meta: {
6172
6145
  docs: {
6173
6146
  description: "enforce valid `expect()` usage",
@@ -6360,7 +6333,7 @@ var valid_expect_default = createEslintRule({
6360
6333
 
6361
6334
  //#endregion
6362
6335
  //#region src/rules/valid-title.ts
6363
- const RULE_NAME$2 = "valid-title";
6336
+ const RULE_NAME$3 = "valid-title";
6364
6337
  const trimFXPrefix = (word) => ["f", "x"].includes(word.charAt(0)) ? word.substring(1) : word;
6365
6338
  const quoteStringValue = (node) => node.type === AST_NODE_TYPES.TemplateLiteral ? `\`${node.quasis[0].value.raw}\`` : node.raw;
6366
6339
  const MatcherAndMessageSchema = {
@@ -6399,7 +6372,7 @@ const doesBinaryExpressionContainStringNode = (binaryExp) => {
6399
6372
  return isStringNode(binaryExp.left);
6400
6373
  };
6401
6374
  var valid_title_default = createEslintRule({
6402
- name: RULE_NAME$2,
6375
+ name: RULE_NAME$3,
6403
6376
  meta: {
6404
6377
  docs: {
6405
6378
  description: "enforce valid titles",
@@ -6552,9 +6525,9 @@ var valid_title_default = createEslintRule({
6552
6525
 
6553
6526
  //#endregion
6554
6527
  //#region src/rules/warn-todo.ts
6555
- const RULE_NAME$1 = "warn-todo";
6528
+ const RULE_NAME$2 = "warn-todo";
6556
6529
  var warn_todo_default = createEslintRule({
6557
- name: RULE_NAME$1,
6530
+ name: RULE_NAME$2,
6558
6531
  meta: {
6559
6532
  docs: {
6560
6533
  description: "disallow `.todo` usage",
@@ -6581,7 +6554,7 @@ var warn_todo_default = createEslintRule({
6581
6554
 
6582
6555
  //#endregion
6583
6556
  //#region src/rules/no-unneeded-async-expect-function.ts
6584
- const RULE_NAME = "no-unneeded-async-expect-function";
6557
+ const RULE_NAME$1 = "no-unneeded-async-expect-function";
6585
6558
  const getAwaitedCallExpression = (expression) => {
6586
6559
  if (!expression.async) return null;
6587
6560
  if (expression.type === AST_NODE_TYPES.ArrowFunctionExpression && expression.body.type === AST_NODE_TYPES.AwaitExpression && expression.body.argument.type === AST_NODE_TYPES.CallExpression) return expression.body.argument;
@@ -6591,7 +6564,7 @@ const getAwaitedCallExpression = (expression) => {
6591
6564
  return null;
6592
6565
  };
6593
6566
  var no_unneeded_async_expect_function_default = createEslintRule({
6594
- name: RULE_NAME,
6567
+ name: RULE_NAME$1,
6595
6568
  meta: {
6596
6569
  docs: { description: "Disallow unnecessary async function wrapper for expected promises" },
6597
6570
  fixable: "code",
@@ -6622,6 +6595,42 @@ var no_unneeded_async_expect_function_default = createEslintRule({
6622
6595
  }
6623
6596
  });
6624
6597
 
6598
+ //#endregion
6599
+ //#region src/rules/prefer-to-have-been-called-times.ts
6600
+ const RULE_NAME = "prefer-to-have-been-called-times";
6601
+ var prefer_to_have_been_called_times_default = createEslintRule({
6602
+ name: RULE_NAME,
6603
+ meta: {
6604
+ fixable: "code",
6605
+ docs: { description: "Suggest using `toHaveBeenCalledTimes()`" },
6606
+ messages: { preferMatcher: "Prefer `toHaveBeenCalledTimes`" },
6607
+ type: "suggestion",
6608
+ schema: []
6609
+ },
6610
+ defaultOptions: [],
6611
+ create(context) {
6612
+ return { CallExpression(node) {
6613
+ const vitestFnCall = parseVitestFnCall(node, context);
6614
+ if (vitestFnCall?.type !== "expect") return;
6615
+ const { parent: expect } = vitestFnCall.head.node;
6616
+ if (expect?.type !== AST_NODE_TYPES.CallExpression) return;
6617
+ const { matcher } = vitestFnCall;
6618
+ if (!isSupportedAccessor(matcher, "toHaveLength")) return;
6619
+ const [argument] = expect.arguments;
6620
+ if (argument?.type !== AST_NODE_TYPES.MemberExpression || !isSupportedAccessor(argument.property, "calls")) return;
6621
+ const { object } = argument;
6622
+ if (object.type !== AST_NODE_TYPES.MemberExpression || !isSupportedAccessor(object.property, "mock")) return;
6623
+ context.report({
6624
+ messageId: "preferMatcher",
6625
+ node: matcher,
6626
+ fix(fixer) {
6627
+ return [fixer.removeRange([object.property.range[0] - 1, argument.range[1]]), fixer.replaceTextRange([matcher.parent.object.range[1], matcher.parent.range[1]], ".toHaveBeenCalledTimes")];
6628
+ }
6629
+ });
6630
+ } };
6631
+ }
6632
+ });
6633
+
6625
6634
  //#endregion
6626
6635
  //#region src/rules/index.ts
6627
6636
  const rules = {
@@ -6689,12 +6698,12 @@ const rules = {
6689
6698
  "prefer-to-be-truthy": prefer_to_be_truthy_default,
6690
6699
  "prefer-to-be": prefer_to_be_default,
6691
6700
  "prefer-to-contain": prefer_to_contain_default,
6701
+ "prefer-to-have-been-called-times": prefer_to_have_been_called_times_default,
6692
6702
  "prefer-to-have-length": prefer_to_have_length_default,
6693
6703
  "prefer-todo": prefer_todo_default,
6694
6704
  "prefer-vi-mocked": prefer_vi_mocked_default,
6695
6705
  "require-awaited-expect-poll": require_awaited_expect_poll_default,
6696
6706
  "require-hook": require_hook_default,
6697
- "require-import-vi-mock": require_import_vi_mock_default,
6698
6707
  "require-local-test-context-for-concurrent-snapshots": require_local_test_context_for_concurrent_snapshots_default,
6699
6708
  "require-mock-type-parameters": require_mock_type_parameters_default,
6700
6709
  "require-to-throw-message": require_to_throw_message_default,
@@ -6787,6 +6796,7 @@ const allRules = {
6787
6796
  "prefer-to-be-truthy": "off",
6788
6797
  "prefer-to-be": "warn",
6789
6798
  "prefer-to-contain": "warn",
6799
+ "prefer-to-have-been-called-times": "warn",
6790
6800
  "prefer-to-have-length": "warn",
6791
6801
  "prefer-todo": "warn",
6792
6802
  "prefer-vi-mocked": "warn",
@@ -6799,8 +6809,7 @@ const allRules = {
6799
6809
  "valid-expect-in-promise": "warn",
6800
6810
  "valid-expect": "warn",
6801
6811
  "valid-title": "warn",
6802
- "require-awaited-expect-poll": "warn",
6803
- "require-import-vi-mock": "warn"
6812
+ "require-awaited-expect-poll": "warn"
6804
6813
  };
6805
6814
  const recommendedRules = {
6806
6815
  "expect-expect": "error",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitest/eslint-plugin",
3
- "version": "1.5.4",
3
+ "version": "1.6.1",
4
4
  "license": "MIT",
5
5
  "description": "ESLint plugin for Vitest",
6
6
  "repository": "vitest-dev/eslint-plugin-vitest",
@@ -42,7 +42,7 @@
42
42
  "eslint": "^9.37.0",
43
43
  "eslint-config-flat-gitignore": "^2.1.0",
44
44
  "eslint-config-prettier": "^10.1.8",
45
- "eslint-doc-generator": "^2.3.0",
45
+ "eslint-doc-generator": "^2.4.0",
46
46
  "eslint-plugin-eslint-plugin": "^7.0.0",
47
47
  "eslint-remote-tester": "^4.0.3",
48
48
  "eslint-remote-tester-repositories": "^2.0.2",