diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index bdc947b13d22e..5c3f05064b26d 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -4317,6 +4317,11 @@ impl RuleRunner for crate::rules::vitest::require_local_test_context_for_concurr const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnJestNode; } +impl RuleRunner for crate::rules::vitest::require_mock_type_parameters::RequireMockTypeParameters { + const NODE_TYPES: Option<&AstTypesBitset> = None; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnJestNode; +} + impl RuleRunner for crate::rules::vitest::warn_todo::WarnTodo { const NODE_TYPES: Option<&AstTypesBitset> = None; const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnJestNode; diff --git a/crates/oxc_linter/src/generated/rules_enum.rs b/crates/oxc_linter/src/generated/rules_enum.rs index dabb432be2abe..723589627d377 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -692,6 +692,7 @@ pub use crate::rules::vitest::prefer_to_be_object::PreferToBeObject as VitestPre pub use crate::rules::vitest::prefer_to_be_truthy::PreferToBeTruthy as VitestPreferToBeTruthy; pub use crate::rules::vitest::require_awaited_expect_poll::RequireAwaitedExpectPoll as VitestRequireAwaitedExpectPoll; pub use crate::rules::vitest::require_local_test_context_for_concurrent_snapshots::RequireLocalTestContextForConcurrentSnapshots as VitestRequireLocalTestContextForConcurrentSnapshots; +pub use crate::rules::vitest::require_mock_type_parameters::RequireMockTypeParameters as VitestRequireMockTypeParameters; pub use crate::rules::vitest::warn_todo::WarnTodo as VitestWarnTodo; pub use crate::rules::vue::define_emits_declaration::DefineEmitsDeclaration as VueDefineEmitsDeclaration; pub use crate::rules::vue::define_props_declaration::DefinePropsDeclaration as VueDefinePropsDeclaration; @@ -1401,6 +1402,7 @@ pub enum RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots( VitestRequireLocalTestContextForConcurrentSnapshots, ), + VitestRequireMockTypeParameters(VitestRequireMockTypeParameters), VitestWarnTodo(VitestWarnTodo), NodeGlobalRequire(NodeGlobalRequire), NodeHandleCallbackErr(NodeHandleCallbackErr), @@ -2186,8 +2188,9 @@ const VITEST_PREFER_TO_BE_TRUTHY_ID: usize = VITEST_PREFER_TO_BE_OBJECT_ID + 1us const VITEST_REQUIRE_AWAITED_EXPECT_POLL_ID: usize = VITEST_PREFER_TO_BE_TRUTHY_ID + 1usize; const VITEST_REQUIRE_LOCAL_TEST_CONTEXT_FOR_CONCURRENT_SNAPSHOTS_ID: usize = VITEST_REQUIRE_AWAITED_EXPECT_POLL_ID + 1usize; -const VITEST_WARN_TODO_ID: usize = +const VITEST_REQUIRE_MOCK_TYPE_PARAMETERS_ID: usize = VITEST_REQUIRE_LOCAL_TEST_CONTEXT_FOR_CONCURRENT_SNAPSHOTS_ID + 1usize; +const VITEST_WARN_TODO_ID: usize = VITEST_REQUIRE_MOCK_TYPE_PARAMETERS_ID + 1usize; const NODE_GLOBAL_REQUIRE_ID: usize = VITEST_WARN_TODO_ID + 1usize; const NODE_HANDLE_CALLBACK_ERR_ID: usize = NODE_GLOBAL_REQUIRE_ID + 1usize; const NODE_NO_EXPORTS_ASSIGN_ID: usize = NODE_HANDLE_CALLBACK_ERR_ID + 1usize; @@ -3000,6 +3003,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VITEST_REQUIRE_LOCAL_TEST_CONTEXT_FOR_CONCURRENT_SNAPSHOTS_ID } + Self::VitestRequireMockTypeParameters(_) => VITEST_REQUIRE_MOCK_TYPE_PARAMETERS_ID, Self::VitestWarnTodo(_) => VITEST_WARN_TODO_ID, Self::NodeGlobalRequire(_) => NODE_GLOBAL_REQUIRE_ID, Self::NodeHandleCallbackErr(_) => NODE_HANDLE_CALLBACK_ERR_ID, @@ -3802,6 +3806,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::NAME } + Self::VitestRequireMockTypeParameters(_) => VitestRequireMockTypeParameters::NAME, Self::VitestWarnTodo(_) => VitestWarnTodo::NAME, Self::NodeGlobalRequire(_) => NodeGlobalRequire::NAME, Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::NAME, @@ -4650,6 +4655,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::CATEGORY } + Self::VitestRequireMockTypeParameters(_) => VitestRequireMockTypeParameters::CATEGORY, Self::VitestWarnTodo(_) => VitestWarnTodo::CATEGORY, Self::NodeGlobalRequire(_) => NodeGlobalRequire::CATEGORY, Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::CATEGORY, @@ -5455,6 +5461,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::FIX } + Self::VitestRequireMockTypeParameters(_) => VitestRequireMockTypeParameters::FIX, Self::VitestWarnTodo(_) => VitestWarnTodo::FIX, Self::NodeGlobalRequire(_) => NodeGlobalRequire::FIX, Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::FIX, @@ -6458,6 +6465,9 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::documentation() } + Self::VitestRequireMockTypeParameters(_) => { + VitestRequireMockTypeParameters::documentation() + } Self::VitestWarnTodo(_) => VitestWarnTodo::documentation(), Self::NodeGlobalRequire(_) => NodeGlobalRequire::documentation(), Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::documentation(), @@ -8418,6 +8428,10 @@ impl RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots::schema(generator) }) } + Self::VitestRequireMockTypeParameters(_) => { + VitestRequireMockTypeParameters::config_schema(generator) + .or_else(|| VitestRequireMockTypeParameters::schema(generator)) + } Self::VitestWarnTodo(_) => VitestWarnTodo::config_schema(generator) .or_else(|| VitestWarnTodo::schema(generator)), Self::NodeGlobalRequire(_) => NodeGlobalRequire::config_schema(generator) @@ -9165,6 +9179,7 @@ impl RuleEnum { Self::VitestPreferToBeTruthy(_) => "vitest", Self::VitestRequireAwaitedExpectPoll(_) => "vitest", Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => "vitest", + Self::VitestRequireMockTypeParameters(_) => "vitest", Self::VitestWarnTodo(_) => "vitest", Self::NodeGlobalRequire(_) => "node", Self::NodeHandleCallbackErr(_) => "node", @@ -11376,6 +11391,9 @@ impl RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots::from_configuration(value)?, )) } + Self::VitestRequireMockTypeParameters(_) => Ok(Self::VitestRequireMockTypeParameters( + VitestRequireMockTypeParameters::from_configuration(value)?, + )), Self::VitestWarnTodo(_) => { Ok(Self::VitestWarnTodo(VitestWarnTodo::from_configuration(value)?)) } @@ -12134,6 +12152,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => { rule.to_configuration() } + Self::VitestRequireMockTypeParameters(rule) => rule.to_configuration(), Self::VitestWarnTodo(rule) => rule.to_configuration(), Self::NodeGlobalRequire(rule) => rule.to_configuration(), Self::NodeHandleCallbackErr(rule) => rule.to_configuration(), @@ -12840,6 +12859,7 @@ impl RuleEnum { Self::VitestPreferToBeTruthy(rule) => rule.run(node, ctx), Self::VitestRequireAwaitedExpectPoll(rule) => rule.run(node, ctx), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run(node, ctx), + Self::VitestRequireMockTypeParameters(rule) => rule.run(node, ctx), Self::VitestWarnTodo(rule) => rule.run(node, ctx), Self::NodeGlobalRequire(rule) => rule.run(node, ctx), Self::NodeHandleCallbackErr(rule) => rule.run(node, ctx), @@ -13546,6 +13566,7 @@ impl RuleEnum { Self::VitestPreferToBeTruthy(rule) => rule.run_once(ctx), Self::VitestRequireAwaitedExpectPoll(rule) => rule.run_once(ctx), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run_once(ctx), + Self::VitestRequireMockTypeParameters(rule) => rule.run_once(ctx), Self::VitestWarnTodo(rule) => rule.run_once(ctx), Self::NodeGlobalRequire(rule) => rule.run_once(ctx), Self::NodeHandleCallbackErr(rule) => rule.run_once(ctx), @@ -14352,6 +14373,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => { rule.run_on_jest_node(jest_node, ctx) } + Self::VitestRequireMockTypeParameters(rule) => rule.run_on_jest_node(jest_node, ctx), Self::VitestWarnTodo(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeGlobalRequire(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeHandleCallbackErr(rule) => rule.run_on_jest_node(jest_node, ctx), @@ -15058,6 +15080,7 @@ impl RuleEnum { Self::VitestPreferToBeTruthy(rule) => rule.should_run(ctx), Self::VitestRequireAwaitedExpectPoll(rule) => rule.should_run(ctx), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.should_run(ctx), + Self::VitestRequireMockTypeParameters(rule) => rule.should_run(ctx), Self::VitestWarnTodo(rule) => rule.should_run(ctx), Self::NodeGlobalRequire(rule) => rule.should_run(ctx), Self::NodeHandleCallbackErr(rule) => rule.should_run(ctx), @@ -16060,6 +16083,9 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::IS_TSGOLINT_RULE } + Self::VitestRequireMockTypeParameters(_) => { + VitestRequireMockTypeParameters::IS_TSGOLINT_RULE + } Self::VitestWarnTodo(_) => VitestWarnTodo::IS_TSGOLINT_RULE, Self::NodeGlobalRequire(_) => NodeGlobalRequire::IS_TSGOLINT_RULE, Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::IS_TSGOLINT_RULE, @@ -16937,6 +16963,7 @@ impl RuleEnum { Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => { VitestRequireLocalTestContextForConcurrentSnapshots::HAS_CONFIG } + Self::VitestRequireMockTypeParameters(_) => VitestRequireMockTypeParameters::HAS_CONFIG, Self::VitestWarnTodo(_) => VitestWarnTodo::HAS_CONFIG, Self::NodeGlobalRequire(_) => NodeGlobalRequire::HAS_CONFIG, Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::HAS_CONFIG, @@ -17645,6 +17672,7 @@ impl RuleEnum { Self::VitestPreferToBeTruthy(rule) => rule.types_info(), Self::VitestRequireAwaitedExpectPoll(rule) => rule.types_info(), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.types_info(), + Self::VitestRequireMockTypeParameters(rule) => rule.types_info(), Self::VitestWarnTodo(rule) => rule.types_info(), Self::NodeGlobalRequire(rule) => rule.types_info(), Self::NodeHandleCallbackErr(rule) => rule.types_info(), @@ -18351,6 +18379,7 @@ impl RuleEnum { Self::VitestPreferToBeTruthy(rule) => rule.run_info(), Self::VitestRequireAwaitedExpectPoll(rule) => rule.run_info(), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run_info(), + Self::VitestRequireMockTypeParameters(rule) => rule.run_info(), Self::VitestWarnTodo(rule) => rule.run_info(), Self::NodeGlobalRequire(rule) => rule.run_info(), Self::NodeHandleCallbackErr(rule) => rule.run_info(), @@ -19175,6 +19204,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( RuleEnum::VitestRequireLocalTestContextForConcurrentSnapshots( VitestRequireLocalTestContextForConcurrentSnapshots::default(), ), + RuleEnum::VitestRequireMockTypeParameters(VitestRequireMockTypeParameters::default()), RuleEnum::VitestWarnTodo(VitestWarnTodo::default()), RuleEnum::NodeGlobalRequire(NodeGlobalRequire::default()), RuleEnum::NodeHandleCallbackErr(NodeHandleCallbackErr::default()), diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 69159f420f3cb..2eff534936008 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -720,6 +720,7 @@ pub(crate) mod vitest { pub mod prefer_to_be_truthy; pub mod require_awaited_expect_poll; pub mod require_local_test_context_for_concurrent_snapshots; + pub mod require_mock_type_parameters; pub mod warn_todo; } diff --git a/crates/oxc_linter/src/rules/vitest/require_mock_type_parameters.rs b/crates/oxc_linter/src/rules/vitest/require_mock_type_parameters.rs new file mode 100644 index 0000000000000..4b88a6cc9b392 --- /dev/null +++ b/crates/oxc_linter/src/rules/vitest/require_mock_type_parameters.rs @@ -0,0 +1,280 @@ +use schemars::JsonSchema; +use serde::Deserialize; + +use oxc_ast::{ + AstKind, + ast::{CallExpression, Expression}, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, Span}; + +use crate::{ + context::LintContext, + rule::{DefaultRuleConfig, Rule}, + rules::PossibleJestNode, + utils::{ + JestFnKind, JestGeneralFnKind, KnownMemberExpressionProperty, parse_general_jest_fn_call, + }, +}; + +fn require_mock_type_parameters_diagnostic(span: Span, method_name: &str) -> OxcDiagnostic { + OxcDiagnostic::warn("Missing type parameters on mock function call") + .with_help(format!( + "Add a type parameter to the mock function, e.g. `vi.{method_name}<() => void>()`." + )) + .with_label(span) +} + +#[derive(Debug, Default, Deserialize, Clone)] +pub struct RequireMockTypeParameters(Box); + +#[derive(Debug, Default, Deserialize, Clone, JsonSchema)] +#[serde(rename_all = "camelCase", default)] +pub struct RequireMockTypeParametersConfig { + /// Also require type parameters for `importActual` and `importMock`. + check_import_functions: bool, +} + +impl std::ops::Deref for RequireMockTypeParameters { + type Target = RequireMockTypeParametersConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Enforces the use of type parameters on vi.fn(), and optionally on vi.importActual() and vi.importMock(). + /// + /// By default, only vi.fn() is checked. Set checkImportFunctions to true to also check vi.importActual() and vi.importMock(). + /// + /// ### Why is this bad? + /// + /// Without explicit type parameters, vi.fn() creates a mock typed as (...args: any[]) => any. + /// This disables type checking between the mock and the real implementation, which can lead to two problems: + /// + /// - tests that fail due to incorrect mock usage when they should pass, or worse, tests that pass while the mock silently diverges from the actual runtime behavior. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule configured as `{ "checkImportFunctions": false }`: + /// ```js + /// import { vi } from 'vitest' + /// + /// test('foo', () => { + /// const myMockedFn = vi.fn() + /// }) + /// ``` + /// + /// Examples of **incorrect** code for this rule configured as `{ "checkImportFunctions": true }`: + /// ```js + /// import { vi } from 'vitest' + /// + /// vi.mock('./example.js', async () => { + /// const originalModule = await vi.importActual('./example.js') + /// + /// return { ...originalModule } + /// }) + /// const fs = await vi.importMock('fs') + /// ``` + /// + /// Examples of **correct** code for this rule configured as `{ "checkImportFunctions": false }`: + /// ```js + /// import { vi } from 'vitest' + /// + /// test('foo', () => { + /// const myMockedFnOne = vi.fn<(arg1: string, arg2: boolean) => number>() + /// const myMockedFnTwo = vi.fn<() => void>() + /// const myMockedFnThree = vi.fn() + /// }) + /// ``` + /// + /// Examples of **correct** code for this rule configured as `{ "checkImportFunctions": true }`: + /// ```js + /// import { vi } from 'vitest' + /// + /// vi.mock('./example.js', async () => { + /// const originalModule = await vi.importActual('./example.js') + /// + /// return { ...originalModule } + /// }) + /// const fs = await vi.importMock('fs') + /// ``` + RequireMockTypeParameters, + vitest, + correctness, + config = RequireMockTypeParametersConfig, +); + +impl Rule for RequireMockTypeParameters { + fn from_configuration(value: serde_json::Value) -> Result { + serde_json::from_value::>(value).map(DefaultRuleConfig::into_inner) + } + + fn run_on_jest_node<'a, 'c>( + &self, + jest_node: &crate::rules::PossibleJestNode<'a, 'c>, + ctx: &'c LintContext<'a>, + ) { + self.run_rule(jest_node, ctx); + } + + fn should_run(&self, ctx: &crate::rules::ContextHost) -> bool { + ctx.file_extension().is_some_and(|extension| { + let extension_str = extension.to_string_lossy(); + extension_str.ends_with("ts") || extension_str.ends_with("tsx") + }) + } +} + +impl RequireMockTypeParameters { + fn run_rule<'a>(&self, possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) { + let node = possible_jest_node.node; + + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + + let Some(vi_fn) = parse_general_jest_fn_call(call_expr, possible_jest_node, ctx) else { + return; + }; + + if vi_fn.kind != JestFnKind::General(JestGeneralFnKind::Vitest) { + return; + } + + let Some(type_vi_fn) = vi_fn.members.first() else { + return; + }; + + if is_not_require_mock_type(type_vi_fn, self.check_import_functions) { + return; + } + + if is_function_typed(call_expr) { + return; + } + + let method_name = if let Some(method) = type_vi_fn.name() { + CompactStr::from(method) + } else { + CompactStr::new("fn") + }; + + ctx.diagnostic(require_mock_type_parameters_diagnostic( + call_expr.span, + method_name.as_str(), + )); + } +} + +const MOCK_REQUIRED_TYPES: [&str; 3] = ["fn", "importMock", "importActual"]; + +fn is_not_require_mock_type( + member: &KnownMemberExpressionProperty<'_>, + check_import_functions: bool, +) -> bool { + if !check_import_functions { + return !member.is_name_equal("fn"); + } + + !MOCK_REQUIRED_TYPES.iter().any(|&mock_function_name| member.is_name_equal(mock_function_name)) +} + +fn is_function_typed(call_expr: &CallExpression<'_>) -> bool { + let Some(member_expression) = call_expr.callee.as_member_expression() else { + return true; + }; + + match member_expression.object() { + Expression::Identifier(_) => call_expr.type_arguments.is_some(), + // This case exist to handle when the full mock function looks like this vi.fn<>.mockReturnValue + Expression::CallExpression(inner_call) => inner_call.type_arguments.is_some(), + _ => true, + } +} + +#[test] +fn test() { + use crate::tester::Tester; + use std::path::PathBuf; + + let pass = vec![ + ("vi.fn<(...args: any[]) => any>()", None, None, Some(PathBuf::from("test.ts"))), + ("vi.fn<(...args: string[]) => any>()", None, None, Some(PathBuf::from("test.ts"))), + ("vi.fn<(arg1: string) => string>()", None, None, Some(PathBuf::from("test.ts"))), + ("vi.fn<(arg1: any) => string>()", None, None, Some(PathBuf::from("test.ts"))), + ("vi.fn<(arg1: string) => void>()", None, None, Some(PathBuf::from("test.ts"))), + ( + "vi.fn<(arg1: string, arg2: boolean) => string>()", + None, + None, + Some(PathBuf::from("test.ts")), + ), + ( + "vi.fn<(arg1: string, arg2: boolean, ...args: string[]) => string>()", + None, + None, + Some(PathBuf::from("test.ts")), + ), + ("vi.fn()", None, None, Some(PathBuf::from("test.ts"))), + ("vi.fn()", None, None, Some(PathBuf::from("test.ts"))), + ("vi.fn<(...args: any[]) => any>(() => {})", None, None, Some(PathBuf::from("test.ts"))), + ( + r#"vi.fn<() => string | undefined>().mockReturnValue("some error message");"#, + None, + None, + Some(PathBuf::from("test.ts")), + ), + ( + r#"vi.importActual<{ default: boolean }>("./example.js")"#, + None, + None, + Some(PathBuf::from("test.ts")), + ), + ( + r#"vi.importActual("./example.js")"#, + None, + None, + Some(PathBuf::from("test.ts")), + ), + (r#"vi.importActual("./example.js")"#, None, None, Some(PathBuf::from("test.ts"))), + ( + r#"vi.importMock<{ default: boolean }>("./example.js")"#, + None, + None, + Some(PathBuf::from("test.ts")), + ), + (r#"vi.importMock("./example.js")"#, None, None, Some(PathBuf::from("test.ts"))), + (r#"vi.importMock("./example.js")"#, None, None, Some(PathBuf::from("test.ts"))), + (r#"vi.importActual("./example.js")"#, None, None, Some(PathBuf::from("test.ts"))), + (r#"vi.importMock("./example.js")"#, None, None, Some(PathBuf::from("test.spec.ts"))), + //Ignoring js files to avoid false positives + ("vi.fn()", None, None, Some(PathBuf::from("test.spec.js"))), + ("vi.fn()", None, None, Some(PathBuf::from("test.jsx"))), + ]; + + let fail = vec![ + ("vi.fn()", None, None, Some(PathBuf::from("test.ts"))), + ("vi.fn(() => {})", None, None, Some(PathBuf::from("test.tsx"))), + ( + r#"vi.importActual("./example.js")"#, + Some(serde_json::json!([{ "checkImportFunctions": true }])), + None, + Some(PathBuf::from("test.ts")), + ), + ( + r#"vi.importMock("./example.js")"#, + Some(serde_json::json!([{ "checkImportFunctions": true }])), + None, + Some(PathBuf::from("test.ts")), + ), + ]; + + Tester::new(RequireMockTypeParameters::NAME, RequireMockTypeParameters::PLUGIN, pass, fail) + .with_vitest_plugin(true) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/vitest_require_mock_type_parameters.snap b/crates/oxc_linter/src/snapshots/vitest_require_mock_type_parameters.snap new file mode 100644 index 0000000000000..835373ce9273e --- /dev/null +++ b/crates/oxc_linter/src/snapshots/vitest_require_mock_type_parameters.snap @@ -0,0 +1,31 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint-plugin-vitest(require-mock-type-parameters): Missing type parameters on mock function call + ╭─[require_mock_type_parameters.tsx:1:1] + 1 │ vi.fn() + · ─────── + ╰──── + help: Add a type parameter to the mock function, e.g. `vi.fn<() => void>()`. + + ⚠ eslint-plugin-vitest(require-mock-type-parameters): Missing type parameters on mock function call + ╭─[require_mock_type_parameters.tsx:1:1] + 1 │ vi.fn(() => {}) + · ─────────────── + ╰──── + help: Add a type parameter to the mock function, e.g. `vi.fn<() => void>()`. + + ⚠ eslint-plugin-vitest(require-mock-type-parameters): Missing type parameters on mock function call + ╭─[require_mock_type_parameters.tsx:1:1] + 1 │ vi.importActual("./example.js") + · ─────────────────────────────── + ╰──── + help: Add a type parameter to the mock function, e.g. `vi.importActual<() => void>()`. + + ⚠ eslint-plugin-vitest(require-mock-type-parameters): Missing type parameters on mock function call + ╭─[require_mock_type_parameters.tsx:1:1] + 1 │ vi.importMock("./example.js") + · ───────────────────────────── + ╰──── + help: Add a type parameter to the mock function, e.g. `vi.importMock<() => void>()`.