diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index ccad7740e77d7..ebed8e21138bc 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -452,6 +452,7 @@ mod promise { } mod vitest { + pub mod no_conditional_tests; pub mod no_import_node_test; pub mod prefer_to_be_falsy; pub mod prefer_to_be_truthy; @@ -866,4 +867,5 @@ oxc_macros::declare_all_lint_rules! { vitest::no_import_node_test, vitest::prefer_to_be_falsy, vitest::prefer_to_be_truthy, + vitest::no_conditional_tests, } diff --git a/crates/oxc_linter/src/rules/vitest/no_conditional_tests.rs b/crates/oxc_linter/src/rules/vitest/no_conditional_tests.rs new file mode 100644 index 0000000000000..a492bd7b171a7 --- /dev/null +++ b/crates/oxc_linter/src/rules/vitest/no_conditional_tests.rs @@ -0,0 +1,139 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{ + collect_possible_jest_call_node, is_type_of_jest_fn_call, JestFnKind, JestGeneralFnKind, + PossibleJestNode, + }, +}; + +fn no_conditional_tests(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Avoid having conditionals in tests") + .with_help("Remove the surrounding if statement.") + .with_label(span0) +} + +#[derive(Debug, Default, Clone)] +pub struct NoConditionalTests; + +declare_oxc_lint!( + /// ### What it does + /// The rule disallows the use of conditional statements within test cases to ensure that tests are deterministic and clearly readable. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// describe('my tests', () => { + /// if (true) { + /// it('is awesome', () => { + /// doTheThing() + /// }) + /// } + /// }) + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// describe('my tests', () => { + /// it('is awesome', () => { + /// doTheThing() + /// }) + /// }) + /// ``` + NoConditionalTests, + correctness, +); + +impl Rule for NoConditionalTests { + fn run_once(&self, ctx: &LintContext) { + for node in &collect_possible_jest_call_node(ctx) { + run(node, ctx); + } + } +} + +fn run<'a>(possible_jest_node: &PossibleJestNode<'a, '_>, ctx: &LintContext<'a>) { + let node = possible_jest_node.node; + if let AstKind::CallExpression(call_expr) = node.kind() { + if is_type_of_jest_fn_call( + call_expr, + possible_jest_node, + ctx, + &[ + JestFnKind::General(JestGeneralFnKind::Describe), + JestFnKind::General(JestGeneralFnKind::Test), + ], + ) { + let if_statement_node = ctx + .nodes() + .iter_parents(node.id()) + .find(|node| matches!(node.kind(), AstKind::IfStatement(_))); + + let Some(node) = if_statement_node else { return }; + + if let AstKind::IfStatement(if_statement) = node.kind() { + ctx.diagnostic(no_conditional_tests(if_statement.span)); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + r#"test("shows error", () => {});"#, + r#"it("foo", function () {})"#, + "it('foo', () => {}); function myTest() { if ('bar') {} }", + r#"function myFunc(str: string) { + return str; + } + describe("myTest", () => { + it("convert shortened equal filter", () => { + expect( + myFunc("5") + ).toEqual("5"); + }); + });"#, + r#"describe("shows error", () => { + if (1 === 2) { + myFunc(); + } + expect(true).toBe(false); + });"#, + ]; + + let fail = vec![ + r#"describe("shows error", () => { + if(true) { + test("shows error", () => { + expect(true).toBe(true); + }) + } + })"#, + r#" + describe("shows error", () => { + if(true) { + it("shows error", () => { + expect(true).toBe(true); + }) + } + })"#, + r#"describe("errors", () => { + if (Math.random() > 0) { + test("test2", () => { + expect(true).toBeTruthy(); + }); + } + });"#, + ]; + + Tester::new(NoConditionalTests::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_conditional_tests.snap b/crates/oxc_linter/src/snapshots/no_conditional_tests.snap new file mode 100644 index 0000000000000..eda7c4a9fc8c8 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_conditional_tests.snap @@ -0,0 +1,38 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint-plugin-vitest(no-conditional-tests): Avoid having conditionals in tests + ╭─[no_conditional_tests.tsx:2:8] + 1 │ describe("shows error", () => { + 2 │ ╭─▶ if(true) { + 3 │ │ test("shows error", () => { + 4 │ │ expect(true).toBe(true); + 5 │ │ }) + 6 │ ╰─▶ } + 7 │ }) + ╰──── + help: Remove the surrounding if statement. + + ⚠ eslint-plugin-vitest(no-conditional-tests): Avoid having conditionals in tests + ╭─[no_conditional_tests.tsx:3:8] + 2 │ describe("shows error", () => { + 3 │ ╭─▶ if(true) { + 4 │ │ it("shows error", () => { + 5 │ │ expect(true).toBe(true); + 6 │ │ }) + 7 │ ╰─▶ } + 8 │ }) + ╰──── + help: Remove the surrounding if statement. + + ⚠ eslint-plugin-vitest(no-conditional-tests): Avoid having conditionals in tests + ╭─[no_conditional_tests.tsx:2:8] + 1 │ describe("errors", () => { + 2 │ ╭─▶ if (Math.random() > 0) { + 3 │ │ test("test2", () => { + 4 │ │ expect(true).toBeTruthy(); + 5 │ │ }); + 6 │ ╰─▶ } + 7 │ }); + ╰──── + help: Remove the surrounding if statement.