diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index bdb4fb1828097..4f24018d85290 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -4330,6 +4330,11 @@ impl RuleRunner for crate::rules::vitest::require_mock_type_parameters::RequireM const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnJestNode; } +impl RuleRunner for crate::rules::vitest::require_test_timeout::RequireTestTimeout { + const NODE_TYPES: Option<&AstTypesBitset> = None; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; +} + 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 424ebfc7dda92..49e250f764c09 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -694,6 +694,7 @@ pub use crate::rules::vitest::prefer_to_be_truthy::PreferToBeTruthy as VitestPre 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::require_test_timeout::RequireTestTimeout as VitestRequireTestTimeout; 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; @@ -1405,6 +1406,7 @@ pub enum RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots, ), VitestRequireMockTypeParameters(VitestRequireMockTypeParameters), + VitestRequireTestTimeout(VitestRequireTestTimeout), VitestWarnTodo(VitestWarnTodo), NodeGlobalRequire(NodeGlobalRequire), NodeHandleCallbackErr(NodeHandleCallbackErr), @@ -2194,7 +2196,8 @@ const VITEST_REQUIRE_LOCAL_TEST_CONTEXT_FOR_CONCURRENT_SNAPSHOTS_ID: usize = VITEST_REQUIRE_AWAITED_EXPECT_POLL_ID + 1usize; 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 VITEST_REQUIRE_TEST_TIMEOUT_ID: usize = VITEST_REQUIRE_MOCK_TYPE_PARAMETERS_ID + 1usize; +const VITEST_WARN_TODO_ID: usize = VITEST_REQUIRE_TEST_TIMEOUT_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; @@ -3009,6 +3012,7 @@ impl RuleEnum { VITEST_REQUIRE_LOCAL_TEST_CONTEXT_FOR_CONCURRENT_SNAPSHOTS_ID } Self::VitestRequireMockTypeParameters(_) => VITEST_REQUIRE_MOCK_TYPE_PARAMETERS_ID, + Self::VitestRequireTestTimeout(_) => VITEST_REQUIRE_TEST_TIMEOUT_ID, Self::VitestWarnTodo(_) => VITEST_WARN_TODO_ID, Self::NodeGlobalRequire(_) => NODE_GLOBAL_REQUIRE_ID, Self::NodeHandleCallbackErr(_) => NODE_HANDLE_CALLBACK_ERR_ID, @@ -3813,6 +3817,7 @@ impl RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots::NAME } Self::VitestRequireMockTypeParameters(_) => VitestRequireMockTypeParameters::NAME, + Self::VitestRequireTestTimeout(_) => VitestRequireTestTimeout::NAME, Self::VitestWarnTodo(_) => VitestWarnTodo::NAME, Self::NodeGlobalRequire(_) => NodeGlobalRequire::NAME, Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::NAME, @@ -4665,6 +4670,7 @@ impl RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots::CATEGORY } Self::VitestRequireMockTypeParameters(_) => VitestRequireMockTypeParameters::CATEGORY, + Self::VitestRequireTestTimeout(_) => VitestRequireTestTimeout::CATEGORY, Self::VitestWarnTodo(_) => VitestWarnTodo::CATEGORY, Self::NodeGlobalRequire(_) => NodeGlobalRequire::CATEGORY, Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::CATEGORY, @@ -5472,6 +5478,7 @@ impl RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots::FIX } Self::VitestRequireMockTypeParameters(_) => VitestRequireMockTypeParameters::FIX, + Self::VitestRequireTestTimeout(_) => VitestRequireTestTimeout::FIX, Self::VitestWarnTodo(_) => VitestWarnTodo::FIX, Self::NodeGlobalRequire(_) => NodeGlobalRequire::FIX, Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::FIX, @@ -6481,6 +6488,7 @@ impl RuleEnum { Self::VitestRequireMockTypeParameters(_) => { VitestRequireMockTypeParameters::documentation() } + Self::VitestRequireTestTimeout(_) => VitestRequireTestTimeout::documentation(), Self::VitestWarnTodo(_) => VitestWarnTodo::documentation(), Self::NodeGlobalRequire(_) => NodeGlobalRequire::documentation(), Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::documentation(), @@ -8449,6 +8457,8 @@ impl RuleEnum { VitestRequireMockTypeParameters::config_schema(generator) .or_else(|| VitestRequireMockTypeParameters::schema(generator)) } + Self::VitestRequireTestTimeout(_) => VitestRequireTestTimeout::config_schema(generator) + .or_else(|| VitestRequireTestTimeout::schema(generator)), Self::VitestWarnTodo(_) => VitestWarnTodo::config_schema(generator) .or_else(|| VitestWarnTodo::schema(generator)), Self::NodeGlobalRequire(_) => NodeGlobalRequire::config_schema(generator) @@ -9198,6 +9208,7 @@ impl RuleEnum { Self::VitestRequireAwaitedExpectPoll(_) => "vitest", Self::VitestRequireLocalTestContextForConcurrentSnapshots(_) => "vitest", Self::VitestRequireMockTypeParameters(_) => "vitest", + Self::VitestRequireTestTimeout(_) => "vitest", Self::VitestWarnTodo(_) => "vitest", Self::NodeGlobalRequire(_) => "node", Self::NodeHandleCallbackErr(_) => "node", @@ -11417,6 +11428,9 @@ impl RuleEnum { Self::VitestRequireMockTypeParameters(_) => Ok(Self::VitestRequireMockTypeParameters( VitestRequireMockTypeParameters::from_configuration(value)?, )), + Self::VitestRequireTestTimeout(_) => Ok(Self::VitestRequireTestTimeout( + VitestRequireTestTimeout::from_configuration(value)?, + )), Self::VitestWarnTodo(_) => { Ok(Self::VitestWarnTodo(VitestWarnTodo::from_configuration(value)?)) } @@ -12177,6 +12191,7 @@ impl RuleEnum { rule.to_configuration() } Self::VitestRequireMockTypeParameters(rule) => rule.to_configuration(), + Self::VitestRequireTestTimeout(rule) => rule.to_configuration(), Self::VitestWarnTodo(rule) => rule.to_configuration(), Self::NodeGlobalRequire(rule) => rule.to_configuration(), Self::NodeHandleCallbackErr(rule) => rule.to_configuration(), @@ -12885,6 +12900,7 @@ impl RuleEnum { Self::VitestRequireAwaitedExpectPoll(rule) => rule.run(node, ctx), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run(node, ctx), Self::VitestRequireMockTypeParameters(rule) => rule.run(node, ctx), + Self::VitestRequireTestTimeout(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), @@ -13593,6 +13609,7 @@ impl RuleEnum { Self::VitestRequireAwaitedExpectPoll(rule) => rule.run_once(ctx), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run_once(ctx), Self::VitestRequireMockTypeParameters(rule) => rule.run_once(ctx), + Self::VitestRequireTestTimeout(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), @@ -14401,6 +14418,7 @@ impl RuleEnum { rule.run_on_jest_node(jest_node, ctx) } Self::VitestRequireMockTypeParameters(rule) => rule.run_on_jest_node(jest_node, ctx), + Self::VitestRequireTestTimeout(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), @@ -15109,6 +15127,7 @@ impl RuleEnum { Self::VitestRequireAwaitedExpectPoll(rule) => rule.should_run(ctx), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.should_run(ctx), Self::VitestRequireMockTypeParameters(rule) => rule.should_run(ctx), + Self::VitestRequireTestTimeout(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), @@ -16117,6 +16136,7 @@ impl RuleEnum { Self::VitestRequireMockTypeParameters(_) => { VitestRequireMockTypeParameters::IS_TSGOLINT_RULE } + Self::VitestRequireTestTimeout(_) => VitestRequireTestTimeout::IS_TSGOLINT_RULE, Self::VitestWarnTodo(_) => VitestWarnTodo::IS_TSGOLINT_RULE, Self::NodeGlobalRequire(_) => NodeGlobalRequire::IS_TSGOLINT_RULE, Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::IS_TSGOLINT_RULE, @@ -16998,6 +17018,7 @@ impl RuleEnum { VitestRequireLocalTestContextForConcurrentSnapshots::HAS_CONFIG } Self::VitestRequireMockTypeParameters(_) => VitestRequireMockTypeParameters::HAS_CONFIG, + Self::VitestRequireTestTimeout(_) => VitestRequireTestTimeout::HAS_CONFIG, Self::VitestWarnTodo(_) => VitestWarnTodo::HAS_CONFIG, Self::NodeGlobalRequire(_) => NodeGlobalRequire::HAS_CONFIG, Self::NodeHandleCallbackErr(_) => NodeHandleCallbackErr::HAS_CONFIG, @@ -17708,6 +17729,7 @@ impl RuleEnum { Self::VitestRequireAwaitedExpectPoll(rule) => rule.types_info(), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.types_info(), Self::VitestRequireMockTypeParameters(rule) => rule.types_info(), + Self::VitestRequireTestTimeout(rule) => rule.types_info(), Self::VitestWarnTodo(rule) => rule.types_info(), Self::NodeGlobalRequire(rule) => rule.types_info(), Self::NodeHandleCallbackErr(rule) => rule.types_info(), @@ -18416,6 +18438,7 @@ impl RuleEnum { Self::VitestRequireAwaitedExpectPoll(rule) => rule.run_info(), Self::VitestRequireLocalTestContextForConcurrentSnapshots(rule) => rule.run_info(), Self::VitestRequireMockTypeParameters(rule) => rule.run_info(), + Self::VitestRequireTestTimeout(rule) => rule.run_info(), Self::VitestWarnTodo(rule) => rule.run_info(), Self::NodeGlobalRequire(rule) => rule.run_info(), Self::NodeHandleCallbackErr(rule) => rule.run_info(), @@ -19242,6 +19265,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( VitestRequireLocalTestContextForConcurrentSnapshots::default(), ), RuleEnum::VitestRequireMockTypeParameters(VitestRequireMockTypeParameters::default()), + RuleEnum::VitestRequireTestTimeout(VitestRequireTestTimeout::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 1cfa6900f0556..2416105aea8c9 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -722,6 +722,7 @@ pub(crate) mod vitest { pub mod require_awaited_expect_poll; pub mod require_local_test_context_for_concurrent_snapshots; pub mod require_mock_type_parameters; + pub mod require_test_timeout; pub mod warn_todo; } diff --git a/crates/oxc_linter/src/rules/vitest/require_test_timeout.rs b/crates/oxc_linter/src/rules/vitest/require_test_timeout.rs new file mode 100644 index 0000000000000..df256accc6fb5 --- /dev/null +++ b/crates/oxc_linter/src/rules/vitest/require_test_timeout.rs @@ -0,0 +1,311 @@ +use oxc_ast::{ + AstKind, + ast::{Argument, Expression, ObjectPropertyKind, UnaryOperator}, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; + +use crate::{ + context::LintContext, + rule::Rule, + rules::PossibleJestNode, + utils::{ + JestFnKind, JestGeneralFnKind, KnownMemberExpressionProperty, + collect_possible_jest_call_node, parse_general_jest_fn_call, + }, +}; + +fn test_missing_timeout(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Test is missing a timeout.") + .with_help( + "Add a numeric third argument, a `{ timeout }` option object second argument, or call `vi.setConfig({ testTimeout: ... })` before this test.", + ) + .with_label(span) +} + +fn config_missing_timeout_object(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("`vi.setConfig()` is missing a `testTimeout` property.") + .with_help("Pass an object with a `testTimeout` property to `vi.setConfig()`.") + .with_label(span) +} + +fn test_options_missing_timeout_property(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Test options object is missing a `timeout` property.") + .with_help("Add a `timeout` property to the options object.") + .with_label(span) +} + +fn timeout_must_be_a_number(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Timeout must be a number.") + .with_help("Use a non-negative numeric literal for the timeout value.") + .with_label(span) +} + +fn timeout_must_be_non_negative(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Timeout must not be negative.") + .with_help("Use a non-negative numeric literal for the timeout value.") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct RequireTestTimeout; + +declare_oxc_lint!( + /// ### What it does + /// + /// Requires every test to have a timeout specified, either as a numeric third + /// argument, a `{ timeout }` option, or via `vi.setConfig({ testTimeout: ... })`. + /// + /// ### Why is this bad? + /// + /// Tests without an explicit timeout rely on the default, which may be too + /// generous to catch performance regressions or too short for slow CI + /// environments, leading to flaky failures. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// it('slow test', async () => { + /// await doSomethingSlow() + /// }) + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// // good (numeric timeout) + /// test('slow test', async () => { + /// await doSomethingSlow() + /// }, 1000) + /// + /// // good (options object) + /// test('slow test', { timeout: 1000 }, async () => { + /// await doSomethingSlow() + /// }) + /// + /// // good (file-level) + /// vi.setConfig({ testTimeout: 1000 }) + /// + /// test('slow test', async () => { + /// await doSomethingSlow() + /// }) + /// ``` + RequireTestTimeout, + vitest, + restriction, +); + +impl Rule for RequireTestTimeout { + fn run_once(&self, ctx: &LintContext) { + let mut config_positions: Vec = vec![]; + let mut possible_jest_nodes = collect_possible_jest_call_node(ctx); + possible_jest_nodes.sort_unstable_by_key(|n| n.node.id()); + + for possible_jest_node in possible_jest_nodes { + Self::run_rule(&possible_jest_node, &mut config_positions, ctx); + } + } +} + +impl RequireTestTimeout { + pub fn run_rule<'a>( + possible_jest_node: &PossibleJestNode<'a, '_>, + config_positions: &mut Vec, + ctx: &LintContext<'a>, + ) { + let node = possible_jest_node.node; + + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + + let Some(vi_node) = parse_general_jest_fn_call(call_expr, possible_jest_node, ctx) else { + return; + }; + + match vi_node.kind { + JestFnKind::General(JestGeneralFnKind::Vitest) => { + if !vi_node.members.first().is_some_and(|member| member.is_name_equal("setConfig")) + { + return; + } + + if let Some(Argument::ObjectExpression(test_config)) = call_expr.arguments.first() { + let Some(ObjectPropertyKind::ObjectProperty(property)) = test_config + .properties + .iter() + .find(|property| is_property_name_equals(property, "testTimeout")) + else { + return; + }; + + config_positions.push(call_expr.span); + config_positions.sort_by(|config_a, config_b| { + (config_a.end - config_b.end).cmp(&config_a.end) + }); + + parse_timeout_value(&property.value, property.value.span(), ctx); + } else { + ctx.diagnostic(config_missing_timeout_object(call_expr.span())); + } + } + JestFnKind::General(JestGeneralFnKind::Test) => { + if vi_node.members.iter().any(is_todo_or_skipped) || vi_node.name.starts_with('x') { + return; + } + + if !call_expr.arguments.iter().any(|argument| { + matches!( + argument, + Argument::ArrowFunctionExpression(_) | Argument::FunctionExpression(_) + ) + }) { + return; + } + + // test() and it() only have two options, a second parameter with options or a last argument with a number + + if let Some(Argument::ObjectExpression(test_config)) = call_expr.arguments.get(1) { + let Some(ObjectPropertyKind::ObjectProperty(property)) = test_config + .properties + .iter() + .find(|property| is_property_name_equals(property, "timeout")) + else { + ctx.diagnostic(test_options_missing_timeout_property(test_config.span())); + return; + }; + + parse_timeout_value(&property.value, property.value.span(), ctx); + } else if let Some(last_argument) = call_expr.arguments.get(2) { + let Some(argument_expression) = last_argument.as_expression() else { + return; + }; + + parse_timeout_value(argument_expression, last_argument.span(), ctx); + } else { + if config_positions + .last() + .is_some_and(|config_position| config_position.end < call_expr.span.start) + { + return; + } + + ctx.diagnostic(test_missing_timeout(call_expr.span())); + } + } + _ => {} + } + } +} + +fn is_todo_or_skipped(member: &KnownMemberExpressionProperty<'_>) -> bool { + member.is_name_equal("todo") || member.is_name_equal("skip") +} + +fn is_property_name_equals(property: &ObjectPropertyKind<'_>, name: &str) -> bool { + let ObjectPropertyKind::ObjectProperty(object_pair) = property else { + return false; + }; + + let Some(object_key_name) = object_pair.key.static_name() else { + return false; + }; + + object_key_name == name +} + +fn parse_timeout_value(expression: &Expression<'_>, span: Span, ctx: &LintContext<'_>) { + match expression { + Expression::NumericLiteral(_) => {} + Expression::UnaryExpression(expression) => { + let Expression::NumericLiteral(_) = &expression.argument else { + ctx.diagnostic(timeout_must_be_a_number(span)); + return; + }; + + if matches!(expression.operator, UnaryOperator::UnaryPlus) { + return; + } + + ctx.diagnostic(timeout_must_be_non_negative(span)); + } + _ => ctx.diagnostic(timeout_must_be_a_number(span)), + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + /* + * Commented tests are invalid because Vitest doesn't allow that API for timeout https://vitest.dev/api/test.html#timeout + */ + let pass = vec![ + r#"test.todo("a")"#, + r#"xit("a", () => {})"#, + r#"test("a", () => {}, 0)"#, + r#"it("a", () => {}, 500)"#, + r#"it.skip("a", () => {})"#, + r#"test.skip("a", () => {})"#, + r#"test("a", () => {}, 1000)"#, + r#"it.only("a", () => {}, 1234)"#, + r#"test.only("a", () => {}, 1234)"#, + r#"it.concurrent("a", () => {}, 400)"#, + //r#"test("a", () => {}, { timeout: 0 })"#, + r#"test.concurrent("a", () => {}, 400)"#, + //r#"test("a", () => {}, { timeout: 500 })"#, + r#"test("a", { timeout: 500 }, () => {})"#, + r#"vi.setConfig({ testTimeout: 1000 }); test("a", () => {})"#, + //r#"test("a", { foo: 1 }, { timeout: 500 }, () => {})"#, + r#"test("a", { timeout: 500 }, 1000, () => {})"#, + r#"test("a", () => {}, 1000, { extra: true })"#, + r#"test("a", () => {}, +500)"#, + r#"vi.setConfig({ testTimeout: +500 }); test("a", () => {})"#, + r#"test("a", { timeout: +500 }, () => {})"#, + r#"vi.setConfig({ testTimeout: 0 }); test("a", () => {})"#, + r#"vi.setConfig({ testTimeout: 1000 }); test("a", () => {}); vi.setConfig({ testTimeout: 2000 }); test("b", () => {})"#, + r#"vi.setConfig({ testTimeout: 1000 }); test("a", () => {}, 500)"#, + r#"describe("suite", () => {})"#, + ]; + + let fail = vec![ + r#"test("a", () => {})"#, + r#"it("a", () => {})"#, + r#"test.only("a", () => {})"#, + r#"test.concurrent("a", () => {})"#, + r#"it.concurrent("a", () => {})"#, + r#"vi.setConfig({}); test("a", () => {})"#, + r#"const t = 500; test("a", { timeout: t }, () => {})"#, + r#"test("a", () => {}, { timeout: null })"#, + r#"test("a", () => {}, { timeout: undefined })"#, + r#"test("a", () => {}, -100)"#, + r#"test("a", () => {}, { timeout: -1 })"#, + r#"vi.setConfig({ testTimeout: null }); test("a", () => {})"#, + r#"vi.setConfig({ testTimeout: undefined }); test("a", () => {})"#, + r#"test("a", () => {}); vi.setConfig({ testTimeout: 1000 })"#, + r#"const opts = { timeout: 1000 }; test("a", { ...opts }, () => {})"#, + r#"const opts = { timeout: 1000 }; test("a", { ...opts }, { foo: 1 }, () => {})"#, + r#"test("a", () => {}, { timeout: -1 }, { timeout: 500 })"#, + r#"test("a", () => {}, { timeout: 500 }, { timeout: -1 })"#, + r#"test("a", () => {}, { timeout: -1 }, 1000)"#, + //r#"test("a", () => {}, 1000, { timeout: -1 })"#, + r#"test("a", () => {}, null)"#, + r#"test("a", () => {}, "1000")"#, + r#"test("a", { timeout: "1000" }, () => {})"#, + r#"vi.setConfig({ testTimeout: "1000" })"#, + r#"vi.setConfig({ testTimeout: -"1" })"#, + r#"test("a", { timeout: null }, () => {})"#, + r#"test("a", () => {}, undefined)"#, + r#"vi.setConfig({ testTimeout: -100 }); test("a", () => {})"#, + r#"test("a", { timeout: -500 }, () => {})"#, + r#"vi.setConfig("invalid"); test("a", () => {})"#, + r#"test("a", { retries: 3 }, () => {})"#, + r#"vi.setConfig({ testTimeout: 1000 }); test("a", () => {}); vi.setConfig({ testTimeout: null }); test("b", () => {})"#, + r#"vi.setConfig({ testTimeout: 1000 }); test("a", () => {}, -1)"#, + ]; + + Tester::new(RequireTestTimeout::NAME, RequireTestTimeout::PLUGIN, pass, fail) + .with_vitest_plugin(true) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/vitest_require_test_timeout.snap b/crates/oxc_linter/src/snapshots/vitest_require_test_timeout.snap new file mode 100644 index 0000000000000..cc5c7e0768e7c --- /dev/null +++ b/crates/oxc_linter/src/snapshots/vitest_require_test_timeout.snap @@ -0,0 +1,234 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint-plugin-vitest(require-test-timeout): Test is missing a timeout. + ╭─[require_test_timeout.tsx:1:1] + 1 │ test("a", () => {}) + · ─────────────────── + ╰──── + help: Add a numeric third argument, a `{ timeout }` option object second argument, or call `vi.setConfig({ testTimeout: ... })` before this test. + + ⚠ eslint-plugin-vitest(require-test-timeout): Test is missing a timeout. + ╭─[require_test_timeout.tsx:1:1] + 1 │ it("a", () => {}) + · ───────────────── + ╰──── + help: Add a numeric third argument, a `{ timeout }` option object second argument, or call `vi.setConfig({ testTimeout: ... })` before this test. + + ⚠ eslint-plugin-vitest(require-test-timeout): Test is missing a timeout. + ╭─[require_test_timeout.tsx:1:1] + 1 │ test.only("a", () => {}) + · ──────────────────────── + ╰──── + help: Add a numeric third argument, a `{ timeout }` option object second argument, or call `vi.setConfig({ testTimeout: ... })` before this test. + + ⚠ eslint-plugin-vitest(require-test-timeout): Test is missing a timeout. + ╭─[require_test_timeout.tsx:1:1] + 1 │ test.concurrent("a", () => {}) + · ────────────────────────────── + ╰──── + help: Add a numeric third argument, a `{ timeout }` option object second argument, or call `vi.setConfig({ testTimeout: ... })` before this test. + + ⚠ eslint-plugin-vitest(require-test-timeout): Test is missing a timeout. + ╭─[require_test_timeout.tsx:1:1] + 1 │ it.concurrent("a", () => {}) + · ──────────────────────────── + ╰──── + help: Add a numeric third argument, a `{ timeout }` option object second argument, or call `vi.setConfig({ testTimeout: ... })` before this test. + + ⚠ eslint-plugin-vitest(require-test-timeout): Test is missing a timeout. + ╭─[require_test_timeout.tsx:1:19] + 1 │ vi.setConfig({}); test("a", () => {}) + · ─────────────────── + ╰──── + help: Add a numeric third argument, a `{ timeout }` option object second argument, or call `vi.setConfig({ testTimeout: ... })` before this test. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:37] + 1 │ const t = 500; test("a", { timeout: t }, () => {}) + · ─ + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:21] + 1 │ test("a", () => {}, { timeout: null }) + · ───────────────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:21] + 1 │ test("a", () => {}, { timeout: undefined }) + · ────────────────────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must not be negative. + ╭─[require_test_timeout.tsx:1:21] + 1 │ test("a", () => {}, -100) + · ──── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:21] + 1 │ test("a", () => {}, { timeout: -1 }) + · ─────────────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:29] + 1 │ vi.setConfig({ testTimeout: null }); test("a", () => {}) + · ──── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:29] + 1 │ vi.setConfig({ testTimeout: undefined }); test("a", () => {}) + · ───────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Test is missing a timeout. + ╭─[require_test_timeout.tsx:1:1] + 1 │ test("a", () => {}); vi.setConfig({ testTimeout: 1000 }) + · ─────────────────── + ╰──── + help: Add a numeric third argument, a `{ timeout }` option object second argument, or call `vi.setConfig({ testTimeout: ... })` before this test. + + ⚠ eslint-plugin-vitest(require-test-timeout): Test options object is missing a `timeout` property. + ╭─[require_test_timeout.tsx:1:43] + 1 │ const opts = { timeout: 1000 }; test("a", { ...opts }, () => {}) + · ─────────── + ╰──── + help: Add a `timeout` property to the options object. + + ⚠ eslint-plugin-vitest(require-test-timeout): Test options object is missing a `timeout` property. + ╭─[require_test_timeout.tsx:1:43] + 1 │ const opts = { timeout: 1000 }; test("a", { ...opts }, { foo: 1 }, () => {}) + · ─────────── + ╰──── + help: Add a `timeout` property to the options object. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:21] + 1 │ test("a", () => {}, { timeout: -1 }, { timeout: 500 }) + · ─────────────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:21] + 1 │ test("a", () => {}, { timeout: 500 }, { timeout: -1 }) + · ──────────────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:21] + 1 │ test("a", () => {}, { timeout: -1 }, 1000) + · ─────────────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:21] + 1 │ test("a", () => {}, null) + · ──── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:21] + 1 │ test("a", () => {}, "1000") + · ────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:22] + 1 │ test("a", { timeout: "1000" }, () => {}) + · ────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:29] + 1 │ vi.setConfig({ testTimeout: "1000" }) + · ────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:29] + 1 │ vi.setConfig({ testTimeout: -"1" }) + · ──── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:22] + 1 │ test("a", { timeout: null }, () => {}) + · ──── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:21] + 1 │ test("a", () => {}, undefined) + · ───────── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must not be negative. + ╭─[require_test_timeout.tsx:1:29] + 1 │ vi.setConfig({ testTimeout: -100 }); test("a", () => {}) + · ──── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must not be negative. + ╭─[require_test_timeout.tsx:1:22] + 1 │ test("a", { timeout: -500 }, () => {}) + · ──── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): `vi.setConfig()` is missing a `testTimeout` property. + ╭─[require_test_timeout.tsx:1:1] + 1 │ vi.setConfig("invalid"); test("a", () => {}) + · ─────────────────────── + ╰──── + help: Pass an object with a `testTimeout` property to `vi.setConfig()`. + + ⚠ eslint-plugin-vitest(require-test-timeout): Test is missing a timeout. + ╭─[require_test_timeout.tsx:1:26] + 1 │ vi.setConfig("invalid"); test("a", () => {}) + · ─────────────────── + ╰──── + help: Add a numeric third argument, a `{ timeout }` option object second argument, or call `vi.setConfig({ testTimeout: ... })` before this test. + + ⚠ eslint-plugin-vitest(require-test-timeout): Test options object is missing a `timeout` property. + ╭─[require_test_timeout.tsx:1:11] + 1 │ test("a", { retries: 3 }, () => {}) + · ────────────── + ╰──── + help: Add a `timeout` property to the options object. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must be a number. + ╭─[require_test_timeout.tsx:1:87] + 1 │ vi.setConfig({ testTimeout: 1000 }); test("a", () => {}); vi.setConfig({ testTimeout: null }); test("b", () => {}) + · ──── + ╰──── + help: Use a non-negative numeric literal for the timeout value. + + ⚠ eslint-plugin-vitest(require-test-timeout): Timeout must not be negative. + ╭─[require_test_timeout.tsx:1:58] + 1 │ vi.setConfig({ testTimeout: 1000 }); test("a", () => {}, -1) + · ── + ╰──── + help: Use a non-negative numeric literal for the timeout value.