diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index c0c53860c8cee..8389370662e58 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -189,6 +189,7 @@ mod jest { pub mod prefer_comparison_matcher; pub mod prefer_equality_matcher; pub mod prefer_expect_resolves; + pub mod prefer_hooks_in_order; pub mod prefer_hooks_on_top; pub mod prefer_jest_mocked; pub mod prefer_lowercase_title; @@ -588,6 +589,7 @@ oxc_macros::declare_all_lint_rules! { jest::prefer_comparison_matcher, jest::prefer_equality_matcher, jest::prefer_expect_resolves, + jest::prefer_hooks_in_order, jest::prefer_hooks_on_top, jest::prefer_jest_mocked, jest::prefer_lowercase_title, diff --git a/crates/oxc_linter/src/rules/jest/prefer_hooks_in_order.rs b/crates/oxc_linter/src/rules/jest/prefer_hooks_in_order.rs new file mode 100644 index 0000000000000..70bd6b730ce12 --- /dev/null +++ b/crates/oxc_linter/src/rules/jest/prefer_hooks_in_order.rs @@ -0,0 +1,1271 @@ +use oxc_ast::{ast::CallExpression, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::{AstNode, ScopeId}; +use oxc_span::Span; +use rustc_hash::FxHashMap; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{ + get_test_plugin_name, parse_jest_fn_call, JestFnKind, JestGeneralFnKind, + ParsedJestFnCallNew, PossibleJestNode, + }, +}; + +fn reorder_hooks(x0: &str, x1: &str, x2: &str, span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(format!( + "{x0}(prefer-hooks-in-order): Prefer having hooks in a consistent order.", + )) + .with_help(format!("{x1:?} hooks should be before any {x2:?} hooks")) + .with_label(span0) +} + +#[derive(Debug, Default, Clone)] +pub struct PreferHooksInOrder; + +declare_oxc_lint!( + /// ### What it does + /// + /// While hooks can be setup in any order, they're always called by `jest` in this + /// specific order: + /// 1. `beforeAll` + /// 2. `beforeEach` + /// 3. `afterEach` + /// 4. `afterAll` + /// + /// This rule aims to make that more obvious by enforcing grouped hooks be setup in + /// that order within tests. + /// + /// ### Example + /// + /// ```javascript + /// // invalid + /// describe('foo', () => { + /// beforeEach(() => { + /// seedMyDatabase(); + /// }); + /// beforeAll(() => { + /// createMyDatabase(); + /// }); + /// it('accepts this input', () => { + /// // ... + /// }); + /// it('returns that value', () => { + /// // ... + /// }); + /// describe('when the database has specific values', () => { + /// const specificValue = '...'; + /// beforeEach(() => { + /// seedMyDatabase(specificValue); + /// }); + /// + /// it('accepts that input', () => { + /// // ... + /// }); + /// it('throws an error', () => { + /// // ... + /// }); + /// afterEach(() => { + /// clearLogger(); + /// }); + /// beforeEach(() => { + /// mockLogger(); + /// }); + /// it('logs a message', () => { + /// // ... + /// }); + /// }); + /// afterAll(() => { + /// removeMyDatabase(); + /// }); + /// }); + /// ``` + /// + /// ```javascript + /// // valid + /// describe('foo', () => { + /// beforeAll(() => { + /// createMyDatabase(); + /// }); + /// + /// beforeEach(() => { + /// seedMyDatabase(); + /// }); + /// + /// it('accepts this input', () => { + /// // ... + /// }); + /// it('returns that value', () => { + /// // ... + /// }); + /// describe('when the database has specific values', () => { + /// const specificValue = '...'; + /// beforeEach(() => { + /// seedMyDatabase(specificValue); + /// }); + /// it('accepts that input', () => { + /// // ... + /// }); + /// it('throws an error', () => { + /// // ... + /// }); + /// beforeEach(() => { + /// mockLogger(); + /// }); + /// afterEach(() => { + /// clearLogger(); + /// }); + /// it('logs a message', () => { + /// // ... + /// }); + /// }); + /// afterAll(() => { + /// removeMyDatabase(); + /// }); + /// }); + /// ``` + /// + /// + /// This rule is compatible with [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/prefer-hooks-in-order.md), + /// to use it, add the following configuration to your `.eslintrc.json`: + /// + /// ```json + /// { + /// "rules": { + /// "vitest/prefer-hooks-in-order": "error" + /// } + /// } + PreferHooksInOrder, + style, +); + +impl Rule for PreferHooksInOrder { + fn run_once(&self, ctx: &LintContext) { + let mut hook_groups: FxHashMap> = FxHashMap::default(); + + for node in ctx.nodes().iter() { + hook_groups.entry(node.scope_id()).or_default().push(*node); + } + + for (_, nodes) in hook_groups { + let mut previous_hook_index = -1; + + for node in nodes { + if let AstKind::CallExpression(call_expr) = node.kind() { + let possible_jest_node = &PossibleJestNode { node: &node, original: None }; + Self::check(&mut previous_hook_index, possible_jest_node, call_expr, ctx); + }; + } + } + } +} + +impl PreferHooksInOrder { + fn check<'a>( + previous_hook_index: &mut i32, + possible_jest_node: &PossibleJestNode<'a, '_>, + call_expr: &'a CallExpression<'_>, + ctx: &LintContext<'a>, + ) { + let Some(ParsedJestFnCallNew::GeneralJestFnCall(jest_fn_call)) = + parse_jest_fn_call(call_expr, possible_jest_node, ctx) + else { + *previous_hook_index = -1; + return; + }; + + if !matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Hook)) { + *previous_hook_index = -1; + return; + } + + let hook_orders = ["beforeAll", "beforeEach", "afterEach", "afterAll"]; + let hook_name = jest_fn_call.name; + let hook_pos = + hook_orders.iter().position(|h| h.eq_ignore_ascii_case(&hook_name)).unwrap_or_default(); + let previous_hook_pos = usize::try_from(*previous_hook_index).unwrap_or(0); + + if hook_pos < previous_hook_pos { + let Some(previous_hook_name) = hook_orders.get(previous_hook_pos) else { + return; + }; + let plugin_name = get_test_plugin_name(ctx); + + ctx.diagnostic(reorder_hooks( + plugin_name, + &hook_name, + previous_hook_name, + call_expr.span, + )); + return; + } + + *previous_hook_index = i32::try_from(hook_pos).unwrap_or(-1); + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let mut pass = vec![ + ("beforeAll(() => {})", None), + ("beforeEach(() => {})", None), + ("afterEach(() => {})", None), + ("afterAll(() => {})", None), + ("describe(() => {})", None), + ( + " + beforeAll(() => {}); + beforeEach(() => {}); + afterEach(() => {}); + afterAll(() => {}); + ", + None, + ), + ( + " + describe('foo', () => { + someSetupFn(); + beforeEach(() => {}); + afterEach(() => {}); + + test('bar', () => { + someFn(); + }); + }); + ", + None, + ), + ( + " + beforeAll(() => {}); + afterAll(() => {}); + ", + None, + ), + ( + " + beforeEach(() => {}); + afterEach(() => {}); + ", + None, + ), + ( + " + beforeAll(() => {}); + afterEach(() => {}); + ", + None, + ), + ( + " + beforeAll(() => {}); + beforeEach(() => {}); + ", + None, + ), + ( + " + afterEach(() => {}); + afterAll(() => {}); + ", + None, + ), + ( + " + beforeAll(() => {}); + beforeAll(() => {}); + ", + None, + ), + ( + " + describe('my test', () => { + afterEach(() => {}); + afterAll(() => {}); + }); + ", + None, + ), + ( + " + describe('my test', () => { + afterAll(() => {}); + + describe('when something is true', () => { + beforeAll(() => {}); + beforeEach(() => {}); + }); + }); + ", + None, + ), + ( + " + describe('foo', () => { + beforeAll(() => { + createMyDatabase(); + }); + + beforeEach(() => { + seedMyDatabase(); + }); + + it('accepts this input', () => { + // ... + }); + + it('returns that value', () => { + // ... + }); + + describe('when the database has specific values', () => { + const specificValue = '...'; + + beforeEach(() => { + seedMyDatabase(specificValue); + }); + + it('accepts that input', () => { + // ... + }); + + it('throws an error', () => { + // ... + }); + + beforeEach(() => { + mockLogger(); + }); + + afterEach(() => { + clearLogger(); + }); + + it('logs a message', () => { + // ... + }); + }); + + afterAll(() => { + removeMyDatabase(); + }); + }); + ", + None, + ), + ( + " + describe('my test', () => { + afterEach(() => {}); + afterAll(() => {}); + + doSomething(); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + ", + None, + ), + ( + " + describe('my test', () => { + afterEach(() => {}); + afterAll(() => {}); + + it('is a test', () => {}); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + ", + None, + ), + ( + " + describe('my test', () => { + afterAll(() => {}); + + describe('when something is true', () => { + beforeAll(() => {}); + beforeEach(() => {}); + + it('does something', () => {}); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + describe('my test', () => { + beforeAll(() => {}); + beforeEach(() => {}); + afterAll(() => {}); + + describe('when something is true', () => { + it('does something', () => {}); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + ", + None, + ), + ( + " + const withDatabase = () => { + beforeAll(() => { + createMyDatabase(); + }); + afterAll(() => { + removeMyDatabase(); + }); + }; + + describe('my test', () => { + withDatabase(); + + afterAll(() => {}); + + describe('when something is true', () => { + beforeAll(() => {}); + beforeEach(() => {}); + + it('does something', () => {}); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + describe('my test', () => { + beforeAll(() => {}); + beforeEach(() => {}); + afterAll(() => {}); + + withDatabase(); + + describe('when something is true', () => { + it('does something', () => {}); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + ", + None, + ), + ( + " + describe('A file with a lot of test', () => { + beforeAll(() => { + setupTheDatabase(); + createMocks(); + }); + + beforeAll(() => { + doEvenMore(); + }); + + beforeEach(() => { + cleanTheDatabase(); + resetSomeThings(); + }); + + afterEach(() => { + cleanTheDatabase(); + resetSomeThings(); + }); + + afterAll(() => { + closeTheDatabase(); + stop(); + }); + + it('does something', () => { + const thing = getThing(); + expect(thing).toBe('something'); + }); + + it('throws', () => { + // Do something that throws + }); + + describe('Also have tests in here', () => { + afterAll(() => {}); + it('tests something', () => {}); + it('tests something else', () => {}); + beforeAll(()=>{}); + }); + }); + ", + None, + ), + ]; + + let mut fail = vec![ + ( + " + const withDatabase = () => { + afterAll(() => { + removeMyDatabase(); + }); + beforeAll(() => { + createMyDatabase(); + }); + }; + ", + None, + ), + ( + " + afterAll(() => { + removeMyDatabase(); + }); + beforeAll(() => { + createMyDatabase(); + }); + ", + None, + ), + ( + " + afterAll(() => {}); + beforeAll(() => {}); + ", + None, + ), + ( + " + afterEach(() => {}); + beforeEach(() => {}); + ", + None, + ), + ( + " + afterEach(() => {}); + beforeAll(() => {}); + ", + None, + ), + ( + " + beforeEach(() => {}); + beforeAll(() => {}); + ", + None, + ), + ( + " + afterAll(() => {}); + afterEach(() => {}); + ", + None, + ), + ( + " + afterAll(() => {}); + // The afterEach should do this + // This comment does not matter for the order + afterEach(() => {}); + ", + None, + ), + ( + " + afterAll(() => {}); + afterAll(() => {}); + afterEach(() => {}); + ", + None, + ), + ( + " + describe('my test', () => { + afterAll(() => {}); + afterEach(() => {}); + }); + ", + None, + ), + ( + " + describe('my test', () => { + afterAll(() => {}); + afterEach(() => {}); + + doSomething(); + + beforeEach(() => {}); + beforeAll(() => {}); + }); + ", + None, + ), + ( + " + describe('my test', () => { + afterAll(() => {}); + afterEach(() => {}); + + it('is a test', () => {}); + + beforeEach(() => {}); + beforeAll(() => {}); + }); + ", + None, + ), + ( + " + describe('my test', () => { + afterAll(() => {}); + + describe('when something is true', () => { + beforeEach(() => {}); + beforeAll(() => {}); + }); + }); + ", + None, + ), + ( + " + describe('my test', () => { + beforeAll(() => {}); + afterAll(() => {}); + beforeAll(() => {}); + + describe('when something is true', () => { + beforeAll(() => {}); + afterEach(() => {}); + beforeEach(() => {}); + afterEach(() => {}); + }); + }); + ", + None, + ), + ( + " + describe('my test', () => { + beforeAll(() => {}); + beforeAll(() => {}); + afterAll(() => {}); + + it('foo nested', () => { + // this is a test + }); + + describe('when something is true', () => { + beforeAll(() => {}); + afterEach(() => {}); + + it('foo nested', () => { + // this is a test + }); + + describe('deeply nested', () => { + afterAll(() => {}); + afterAll(() => {}); + // This comment does nothing + afterEach(() => {}); + + it('foo nested', () => { + // this is a test + }); + }) + beforeEach(() => {}); + afterEach(() => {}); + }); + }); + ", + None, + ), + ( + " + describe('my test', () => { + const setupDatabase = () => { + beforeEach(() => { + initDatabase(); + fillWithData(); + }); + beforeAll(() => { + setupMocks(); + }); + }; + + it('foo', () => { + // this is a test + }); + + describe('my nested test', () => { + afterAll(() => {}); + afterEach(() => {}); + + it('foo nested', () => { + // this is a test + }); + }); + }); + ", + None, + ), + ( + " + describe('foo', () => { + beforeEach(() => { + seedMyDatabase(); + }); + + beforeAll(() => { + createMyDatabase(); + }); + + it('accepts this input', () => { + // ... + }); + + it('returns that value', () => { + // ... + }); + + describe('when the database has specific values', () => { + const specificValue = '...'; + + beforeEach(() => { + seedMyDatabase(specificValue); + }); + + it('accepts that input', () => { + // ... + }); + + it('throws an error', () => { + // ... + }); + + afterEach(() => { + clearLogger(); + }); + + beforeEach(() => { + mockLogger(); + }); + + it('logs a message', () => { + // ... + }); + }); + + afterAll(() => { + removeMyDatabase(); + }); + }); + ", + None, + ), + ]; + + let pass_vitest = vec![ + r"beforeAll(() => {})", + r"beforeEach(() => {})", + r"afterEach(() => {})", + r"afterAll(() => {})", + r"describe(() => {})", + r" + beforeAll(() => {}); + beforeEach(() => {}); + afterEach(() => {}); + afterAll(() => {}); + ", + r" + describe('foo', () => { + someSetupFn(); + beforeEach(() => {}); + afterEach(() => {}); + + test('bar', () => { + someFn(); + }); + }); + ", + r" + beforeAll(() => {}); + afterAll(() => {}); + ", + r" + beforeEach(() => {}); + afterEach(() => {}); + ", + r" + beforeAll(() => {}); + afterEach(() => {}); + ", + r" + beforeAll(() => {}); + beforeEach(() => {}); + ", + r" + afterEach(() => {}); + afterAll(() => {}); + ", + r" + beforeAll(() => {}); + beforeAll(() => {}); + ", + r" + describe('my test', () => { + afterEach(() => {}); + afterAll(() => {}); + }); + ", + r" + describe('my test', () => { + afterEach(() => {}); + afterAll(() => {}); + + doSomething(); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + ", + r" + describe('my test', () => { + afterEach(() => {}); + afterAll(() => {}); + + it('is a test', () => {}); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + ", + r" + describe('my test', () => { + afterAll(() => {}); + + describe('when something is true', () => { + beforeAll(() => {}); + beforeEach(() => {}); + }); + }); + ", + r" + describe('my test', () => { + afterAll(() => {}); + + describe('when something is true', () => { + beforeAll(() => {}); + beforeEach(() => {}); + + it('does something', () => {}); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + describe('my test', () => { + beforeAll(() => {}); + beforeEach(() => {}); + afterAll(() => {}); + + describe('when something is true', () => { + it('does something', () => {}); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + ", + r" + const withDatabase = () => { + beforeAll(() => { + createMyDatabase(); + }); + afterAll(() => { + removeMyDatabase(); + }); + }; + + describe('my test', () => { + withDatabase(); + + afterAll(() => {}); + + describe('when something is true', () => { + beforeAll(() => {}); + beforeEach(() => {}); + + it('does something', () => {}); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + describe('my test', () => { + beforeAll(() => {}); + beforeEach(() => {}); + afterAll(() => {}); + + withDatabase(); + + describe('when something is true', () => { + it('does something', () => {}); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + + beforeAll(() => {}); + beforeEach(() => {}); + }); + ", + r" + describe('foo', () => { + beforeAll(() => { + createMyDatabase(); + }); + + beforeEach(() => { + seedMyDatabase(); + }); + + it('accepts this input', () => { + // ... + }); + + it('returns that value', () => { + // ... + }); + + describe('when the database has specific values', () => { + const specificValue = '...'; + + beforeEach(() => { + seedMyDatabase(specificValue); + }); + + it('accepts that input', () => { + // ... + }); + + it('throws an error', () => { + // ... + }); + + beforeEach(() => { + mockLogger(); + }); + + afterEach(() => { + clearLogger(); + }); + + it('logs a message', () => { + // ... + }); + }); + + afterAll(() => { + removeMyDatabase(); + }); + }); + ", + r" + describe('A file with a lot of test', () => { + beforeAll(() => { + setupTheDatabase(); + createMocks(); + }); + + beforeAll(() => { + doEvenMore(); + }); + + beforeEach(() => { + cleanTheDatabase(); + resetSomeThings(); + }); + + afterEach(() => { + cleanTheDatabase(); + resetSomeThings(); + }); + + afterAll(() => { + closeTheDatabase(); + stop(); + }); + + it('does something', () => { + const thing = getThing(); + expect(thing).toBe('something'); + }); + + it('throws', () => { + // Do something that throws + }); + + describe('Also have tests in here', () => { + afterAll(() => {}); + it('tests something', () => {}); + it('tests something else', () => {}); + beforeAll(()=>{}); + }); + }); + ", + ]; + + let fail_vitest = vec![ + r" + const withDatabase = () => { + afterAll(() => { + removeMyDatabase(); + }); + beforeAll(() => { + createMyDatabase(); + }); + }; + ", + r" + afterAll(() => { + removeMyDatabase(); + }); + beforeAll(() => { + createMyDatabase(); + }); + ", + r" + afterAll(() => {}); + beforeAll(() => {}); + ", + r" + afterEach(() => {}); + beforeEach(() => {}); + ", + r" + afterEach(() => {}); + beforeAll(() => {}); + ", + r" + beforeEach(() => {}); + beforeAll(() => {}); + ", + r" + afterAll(() => {}); + afterEach(() => {}); + ", + r" + afterAll(() => {}); + // The afterEach should do this + // This comment does not matter for the order + afterEach(() => {}); + ", + r" + afterAll(() => {}); + afterAll(() => {}); + afterEach(() => {}); + ", + r" + describe('my test', () => { + afterAll(() => {}); + afterEach(() => {}); + }); + ", + r" + describe('my test', () => { + afterAll(() => {}); + afterEach(() => {}); + + doSomething(); + + beforeEach(() => {}); + beforeAll(() => {}); + }); + ", + r" + describe('my test', () => { + afterAll(() => {}); + afterEach(() => {}); + + it('is a test', () => {}); + + beforeEach(() => {}); + beforeAll(() => {}); + }); + ", + r" + describe('my test', () => { + afterAll(() => {}); + + describe('when something is true', () => { + beforeEach(() => {}); + beforeAll(() => {}); + }); + }); + ", + r" + describe('my test', () => { + beforeAll(() => {}); + afterAll(() => {}); + beforeAll(() => {}); + + describe('when something is true', () => { + beforeAll(() => {}); + afterEach(() => {}); + beforeEach(() => {}); + afterEach(() => {}); + }); + }); + ", + r" + describe('my test', () => { + beforeAll(() => {}); + beforeAll(() => {}); + afterAll(() => {}); + + it('foo nested', () => { + // this is a test + }); + + describe('when something is true', () => { + beforeAll(() => {}); + afterEach(() => {}); + + it('foo nested', () => { + // this is a test + }); + + describe('deeply nested', () => { + afterAll(() => {}); + afterAll(() => {}); + // This comment does nothing + afterEach(() => {}); + + it('foo nested', () => { + // this is a test + }); + }) + beforeEach(() => {}); + afterEach(() => {}); + }); + }); + ", + r" + describe('my test', () => { + const setupDatabase = () => { + beforeEach(() => { + initDatabase(); + fillWithData(); + }); + beforeAll(() => { + setupMocks(); + }); + }; + + it('foo', () => { + // this is a test + }); + + describe('my nested test', () => { + afterAll(() => {}); + afterEach(() => {}); + + it('foo nested', () => { + // this is a test + }); + }); + }); + ", + r" + describe('foo', () => { + beforeEach(() => { + seedMyDatabase(); + }); + + beforeAll(() => { + createMyDatabase(); + }); + + it('accepts this input', () => { + // ... + }); + + it('returns that value', () => { + // ... + }); + + describe('when the database has specific values', () => { + const specificValue = '...'; + + beforeEach(() => { + seedMyDatabase(specificValue); + }); + + it('accepts that input', () => { + // ... + }); + + it('throws an error', () => { + // ... + }); + + afterEach(() => { + clearLogger(); + }); + + beforeEach(() => { + mockLogger(); + }); + + it('logs a message', () => { + // ... + }); + }); + + afterAll(() => { + removeMyDatabase(); + }); + }); + ", + ]; + + pass.extend(pass_vitest.into_iter().map(|x| (x, None))); + fail.extend(fail_vitest.into_iter().map(|x| (x, None))); + + Tester::new(PreferHooksInOrder::NAME, pass, fail) + .with_jest_plugin(true) + .with_vitest_plugin(true) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/prefer_hooks_in_order.snap b/crates/oxc_linter/src/snapshots/prefer_hooks_in_order.snap new file mode 100644 index 0000000000000..e1d651ce42902 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/prefer_hooks_in_order.snap @@ -0,0 +1,409 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 216 +--- + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:6:21] + 5 │ }); + 6 │ ╭─▶ beforeAll(() => { + 7 │ │ createMyDatabase(); + 8 │ ╰─▶ }); + 9 │ }; + ╰──── + help: "beforeAll" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:5:17] + 4 │ }); + 5 │ ╭─▶ beforeAll(() => { + 6 │ │ createMyDatabase(); + 7 │ ╰─▶ }); + 8 │ + ╰──── + help: "beforeAll" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:3:17] + 2 │ afterAll(() => {}); + 3 │ beforeAll(() => {}); + · ─────────────────── + 4 │ + ╰──── + help: "beforeAll" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:3:17] + 2 │ afterEach(() => {}); + 3 │ beforeEach(() => {}); + · ──────────────────── + 4 │ + ╰──── + help: "beforeEach" hooks should be before any "afterEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:3:17] + 2 │ afterEach(() => {}); + 3 │ beforeAll(() => {}); + · ─────────────────── + 4 │ + ╰──── + help: "beforeAll" hooks should be before any "afterEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:3:17] + 2 │ beforeEach(() => {}); + 3 │ beforeAll(() => {}); + · ─────────────────── + 4 │ + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:3:17] + 2 │ afterAll(() => {}); + 3 │ afterEach(() => {}); + · ─────────────────── + 4 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:5:17] + 4 │ // This comment does not matter for the order + 5 │ afterEach(() => {}); + · ─────────────────── + 6 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:4:17] + 3 │ afterAll(() => {}); + 4 │ afterEach(() => {}); + · ─────────────────── + 5 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:4:21] + 3 │ afterAll(() => {}); + 4 │ afterEach(() => {}); + · ─────────────────── + 5 │ }); + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:4:21] + 3 │ afterAll(() => {}); + 4 │ afterEach(() => {}); + · ─────────────────── + 5 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:9:21] + 8 │ beforeEach(() => {}); + 9 │ beforeAll(() => {}); + · ─────────────────── + 10 │ }); + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:4:21] + 3 │ afterAll(() => {}); + 4 │ afterEach(() => {}); + · ─────────────────── + 5 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:9:21] + 8 │ beforeEach(() => {}); + 9 │ beforeAll(() => {}); + · ─────────────────── + 10 │ }); + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:7:25] + 6 │ beforeEach(() => {}); + 7 │ beforeAll(() => {}); + · ─────────────────── + 8 │ }); + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:10:25] + 9 │ afterEach(() => {}); + 10 │ beforeEach(() => {}); + · ──────────────────── + 11 │ afterEach(() => {}); + ╰──── + help: "beforeEach" hooks should be before any "afterEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:5:21] + 4 │ afterAll(() => {}); + 5 │ beforeAll(() => {}); + · ─────────────────── + 6 │ + ╰──── + help: "beforeAll" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:23:29] + 22 │ // This comment does nothing + 23 │ afterEach(() => {}); + · ─────────────────── + 24 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:8:25] + 7 │ }); + 8 │ ╭─▶ beforeAll(() => { + 9 │ │ setupMocks(); + 10 │ ╰─▶ }); + 11 │ }; + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:19:25] + 18 │ afterAll(() => {}); + 19 │ afterEach(() => {}); + · ─────────────────── + 20 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:7:21] + 6 │ + 7 │ ╭─▶ beforeAll(() => { + 8 │ │ createMyDatabase(); + 9 │ ╰─▶ }); + 10 │ + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:38:25] + 37 │ + 38 │ ╭─▶ beforeEach(() => { + 39 │ │ mockLogger(); + 40 │ ╰─▶ }); + 41 │ + ╰──── + help: "beforeEach" hooks should be before any "afterEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:6:17] + 5 │ }); + 6 │ ╭─▶ beforeAll(() => { + 7 │ │ createMyDatabase(); + 8 │ ╰─▶ }); + 9 │ }; + ╰──── + help: "beforeAll" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:5:13] + 4 │ }); + 5 │ ╭─▶ beforeAll(() => { + 6 │ │ createMyDatabase(); + 7 │ ╰─▶ }); + 8 │ + ╰──── + help: "beforeAll" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:3:13] + 2 │ afterAll(() => {}); + 3 │ beforeAll(() => {}); + · ─────────────────── + 4 │ + ╰──── + help: "beforeAll" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:3:13] + 2 │ afterEach(() => {}); + 3 │ beforeEach(() => {}); + · ──────────────────── + 4 │ + ╰──── + help: "beforeEach" hooks should be before any "afterEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:3:13] + 2 │ afterEach(() => {}); + 3 │ beforeAll(() => {}); + · ─────────────────── + 4 │ + ╰──── + help: "beforeAll" hooks should be before any "afterEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:3:13] + 2 │ beforeEach(() => {}); + 3 │ beforeAll(() => {}); + · ─────────────────── + 4 │ + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:3:13] + 2 │ afterAll(() => {}); + 3 │ afterEach(() => {}); + · ─────────────────── + 4 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:5:13] + 4 │ // This comment does not matter for the order + 5 │ afterEach(() => {}); + · ─────────────────── + 6 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:4:13] + 3 │ afterAll(() => {}); + 4 │ afterEach(() => {}); + · ─────────────────── + 5 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:4:17] + 3 │ afterAll(() => {}); + 4 │ afterEach(() => {}); + · ─────────────────── + 5 │ }); + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:4:17] + 3 │ afterAll(() => {}); + 4 │ afterEach(() => {}); + · ─────────────────── + 5 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:9:17] + 8 │ beforeEach(() => {}); + 9 │ beforeAll(() => {}); + · ─────────────────── + 10 │ }); + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:4:17] + 3 │ afterAll(() => {}); + 4 │ afterEach(() => {}); + · ─────────────────── + 5 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:9:17] + 8 │ beforeEach(() => {}); + 9 │ beforeAll(() => {}); + · ─────────────────── + 10 │ }); + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:7:21] + 6 │ beforeEach(() => {}); + 7 │ beforeAll(() => {}); + · ─────────────────── + 8 │ }); + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:10:21] + 9 │ afterEach(() => {}); + 10 │ beforeEach(() => {}); + · ──────────────────── + 11 │ afterEach(() => {}); + ╰──── + help: "beforeEach" hooks should be before any "afterEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:5:17] + 4 │ afterAll(() => {}); + 5 │ beforeAll(() => {}); + · ─────────────────── + 6 │ + ╰──── + help: "beforeAll" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:23:25] + 22 │ // This comment does nothing + 23 │ afterEach(() => {}); + · ─────────────────── + 24 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:8:21] + 7 │ }); + 8 │ ╭─▶ beforeAll(() => { + 9 │ │ setupMocks(); + 10 │ ╰─▶ }); + 11 │ }; + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:19:21] + 18 │ afterAll(() => {}); + 19 │ afterEach(() => {}); + · ─────────────────── + 20 │ + ╰──── + help: "afterEach" hooks should be before any "afterAll" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:7:17] + 6 │ + 7 │ ╭─▶ beforeAll(() => { + 8 │ │ createMyDatabase(); + 9 │ ╰─▶ }); + 10 │ + ╰──── + help: "beforeAll" hooks should be before any "beforeEach" hooks + + ⚠ eslint-plugin-jest(prefer-hooks-in-order): Prefer having hooks in a consistent order. + ╭─[prefer_hooks_in_order.tsx:38:21] + 37 │ + 38 │ ╭─▶ beforeEach(() => { + 39 │ │ mockLogger(); + 40 │ ╰─▶ }); + 41 │ + ╰──── + help: "beforeEach" hooks should be before any "afterEach" hooks diff --git a/crates/oxc_linter/src/utils/mod.rs b/crates/oxc_linter/src/utils/mod.rs index 51af26ecb4e6b..e61e78a72df68 100644 --- a/crates/oxc_linter/src/utils/mod.rs +++ b/crates/oxc_linter/src/utils/mod.rs @@ -16,7 +16,8 @@ pub use self::{ /// Many Vitest rule are essentially ports of Jest plugin rules with minor modifications. /// For these rules, we use the corresponding jest rules with some adjustments for compatibility. pub fn is_jest_rule_adapted_to_vitest(rule_name: &str) -> bool { - let jest_rules: [&str; 2] = ["consistent_test_it", "no-disabled-tests"]; + let jest_rules: [&str; 3] = + ["consistent_test_it", "no-disabled-tests", "prefer-hooks-in-order"]; jest_rules.contains(&rule_name) }