diff --git a/crates/oxc_linter/src/jest_ast_util.rs b/crates/oxc_linter/src/jest_ast_util.rs index 0f0078cc4..adf5f4779 100644 --- a/crates/oxc_linter/src/jest_ast_util.rs +++ b/crates/oxc_linter/src/jest_ast_util.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow; +use std::{borrow::Cow, cmp::Ordering}; use oxc_ast::{ ast::{ @@ -86,30 +86,9 @@ pub fn parse_jest_fn_call<'a>( members.push(member); } - let is_valid_jest_call = if members.is_empty() { - VALID_JEST_FN_CALL_CHAINS.iter().any(|chain| chain[0] == name) - } else if members.len() == 1 { - VALID_JEST_FN_CALL_CHAINS_2 - .iter() - .any(|chain| chain[0] == name && members[0].is_name_equal(chain[1])) - } else if members.len() == 2 { - VALID_JEST_FN_CALL_CHAINS_3.iter().any(|chain| { - chain[0] == name - && members[0].is_name_equal(chain[1]) - && members[1].is_name_equal(chain[2]) - }) - } else if members.len() == 3 { - VALID_JEST_FN_CALL_CHAINS_4.iter().any(|chain| { - chain[0] == name - && members[0].is_name_equal(chain[1]) - && members[1].is_name_equal(chain[2]) - && members[2].is_name_equal(chain[3]) - }) - } else { - false - }; - - if !is_valid_jest_call { + let mut call_chains = Vec::from([Cow::Borrowed(name)]); + call_chains.extend(members.iter().filter_map(KnownMemberExpressionProperty::name)); + if !is_valid_jest_call(&call_chains) { return None; } @@ -123,6 +102,25 @@ pub fn parse_jest_fn_call<'a>( None } +// If find a match in `VALID_JEST_FN_CALL_CHAINS`, return true. +fn is_valid_jest_call(members: &[Cow]) -> bool { + VALID_JEST_FN_CALL_CHAINS + .binary_search_by(|chain| { + chain + .iter() + .zip(members.iter()) + .find_map(|(&chain, member)| { + let ordering = chain.cmp(member.as_ref()); + if ordering != Ordering::Equal { + return Some(ordering); + } + None + }) + .unwrap_or(Ordering::Equal) + }) + .is_ok() +} + fn resolve_to_jest_fn<'a>( call_expr: &'a CallExpression<'a>, ctx: &LintContext<'a>, @@ -360,65 +358,57 @@ fn get_node_chain<'a>( chain } -const VALID_JEST_FN_CALL_CHAINS: [[&str; 1]; 12] = [ - ["afterAll"], - ["afterEach"], - ["beforeAll"], - ["beforeEach"], - ["describe"], - ["fdescribe"], - ["xdescribe"], - ["it"], - ["fit"], - ["xit"], - ["test"], - ["xtest"], -]; - -const VALID_JEST_FN_CALL_CHAINS_2: [[&str; 2]; 23] = [ - ["describe", "each"], - ["describe", "only"], - ["describe", "skip"], - ["fdescribe", "each"], - ["xdescribe", "each"], - ["it", "concurrent"], - ["it", "each"], - ["it", "failing"], - ["it", "only"], - ["it", "skip"], - ["it", "todo"], - ["fit", "each"], - ["fit", "failing"], - ["xit", "each"], - ["xit", "failing"], - ["test", "concurrent"], - ["test", "each"], - ["test", "failing"], - ["test", "only"], - ["test", "skip"], - ["test", "todo"], - ["xtest", "each"], - ["xtest", "failing"], -]; - -const VALID_JEST_FN_CALL_CHAINS_3: [[&str; 3]; 12] = [ - ["describe", "only", "each"], - ["describe", "skip", "each"], - ["it", "concurrent", "each"], - ["it", "only", "each"], - ["it", "only", "failing"], - ["it", "skip", "each"], - ["it", "skip", "failing"], - ["test", "concurrent", "each"], - ["test", "only", "each"], - ["test", "only", "failing"], - ["test", "skip", "each"], - ["test", "skip", "failing"], -]; - -const VALID_JEST_FN_CALL_CHAINS_4: [[&str; 4]; 4] = [ +// sorted list for binary search. +const VALID_JEST_FN_CALL_CHAINS: [[&str; 4]; 51] = [ + ["afterAll", "", "", ""], + ["afterEach", "", "", ""], + ["beforeAll", "", "", ""], + ["beforeEach", "", "", ""], + ["describe", "", "", ""], + ["describe", "each", "", ""], + ["describe", "only", "", ""], + ["describe", "only", "each", ""], + ["describe", "skip", "", ""], + ["describe", "skip", "each", ""], + ["fdescribe", "", "", ""], + ["fdescribe", "each", "", ""], + ["fit", "", "", ""], + ["fit", "each", "", ""], + ["fit", "failing", "", ""], + ["it", "", "", ""], + ["it", "concurrent", "", ""], + ["it", "concurrent", "each", ""], ["it", "concurrent", "only", "each"], ["it", "concurrent", "skip", "each"], + ["it", "each", "", ""], + ["it", "failing", "", ""], + ["it", "only", "", ""], + ["it", "only", "each", ""], + ["it", "only", "failing", ""], + ["it", "skip", "", ""], + ["it", "skip", "each", ""], + ["it", "skip", "failing", ""], + ["it", "todo", "", ""], + ["test", "", "", ""], + ["test", "concurrent", "", ""], + ["test", "concurrent", "each", ""], ["test", "concurrent", "only", "each"], ["test", "concurrent", "skip", "each"], + ["test", "each", "", ""], + ["test", "failing", "", ""], + ["test", "only", "", ""], + ["test", "only", "each", ""], + ["test", "only", "failing", ""], + ["test", "skip", "", ""], + ["test", "skip", "each", ""], + ["test", "skip", "failing", ""], + ["test", "todo", "", ""], + ["xdescribe", "", "", ""], + ["xdescribe", "each", "", ""], + ["xit", "", "", ""], + ["xit", "each", "", ""], + ["xit", "failing", "", ""], + ["xtest", "", "", ""], + ["xtest", "each", "", ""], + ["xtest", "failing", "", ""], ]; diff --git a/crates/oxc_linter/src/rules/jest/no_focused_tests.rs b/crates/oxc_linter/src/rules/jest/no_focused_tests.rs index 9674e68a6..d4872fe1e 100644 --- a/crates/oxc_linter/src/rules/jest/no_focused_tests.rs +++ b/crates/oxc_linter/src/rules/jest/no_focused_tests.rs @@ -18,7 +18,7 @@ use crate::{ }; #[derive(Debug, Error, Diagnostic)] -#[error("Unexpected focused test.")] +#[error("eslint(jest/no-focused-tests): Unexpected focused test.")] #[diagnostic(severity(warning), help("Remove focus from test."))] struct NoFocusedTestsDiagnostic(#[label] pub Span); diff --git a/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs b/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs index bf0465e2c..1076aebbb 100644 --- a/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs +++ b/crates/oxc_linter/src/rules/jest/valid_describe_callback.rs @@ -17,7 +17,7 @@ use crate::{ }; #[derive(Debug, Error, Diagnostic)] -#[error("{0:?}")] +#[error("eslint(jest/valid-describe-callback): {0:?}")] #[diagnostic(severity(warning), help("{1:?}"))] struct ValidDescribeCallbackDiagnostic(&'static str, &'static str, #[label] pub Span); @@ -253,7 +253,7 @@ fn test() { " import { fdescribe } from '@jest/globals'; fdescribe('foo', async function () {}) - ", + ", None, ), ("describe.only('foo', async function () {})", None), @@ -271,7 +271,7 @@ fn test() { }); }); }); - ", + ", None, ), ( @@ -283,7 +283,7 @@ fn test() { }) }) }) - ", + ", None, ), ( @@ -302,7 +302,7 @@ fn test() { }) }) }) - ", + ", None, ), ( @@ -318,7 +318,7 @@ fn test() { }) }) }) - ", + ", None, ), ("describe('foo', () => test('bar', () => {})) ", None), diff --git a/crates/oxc_linter/src/snapshots/no_focused_tests.snap b/crates/oxc_linter/src/snapshots/no_focused_tests.snap index a7afacdfd..343b27c83 100644 --- a/crates/oxc_linter/src/snapshots/no_focused_tests.snap +++ b/crates/oxc_linter/src/snapshots/no_focused_tests.snap @@ -2,126 +2,126 @@ source: crates/oxc_linter/src/tester.rs expression: no_focused_tests --- - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ describe.only() · ─────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ describe.only.each()() · ────────────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ describe.only.each`table`() · ─────────────────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ describe["only"]() · ────────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ it.only() · ───────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ it.concurrent.only.each``() · ─────────────────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ it.only.each()() · ──────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ it.only.each`table`() · ───────────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ it["only"]() · ──────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ test.only() · ─────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ test.concurrent.only.each()() · ───────────────────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ test.only.each()() · ────────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ test.only.each`table`() · ─────────────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ test["only"]() · ────────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ fdescribe() · ─────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ fit() · ───── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ fit.each()() · ──────────── ╰──── help: Remove focus from test. - ⚠ Unexpected focused test. + ⚠ eslint(jest/no-focused-tests): Unexpected focused test. ╭─[no_focused_tests.tsx:1:1] 1 │ fit.each`table`() · ───────────────── diff --git a/crates/oxc_linter/src/snapshots/valid_describe_callback.snap b/crates/oxc_linter/src/snapshots/valid_describe_callback.snap index 54311b3ce..c6ca7fdb3 100644 --- a/crates/oxc_linter/src/snapshots/valid_describe_callback.snap +++ b/crates/oxc_linter/src/snapshots/valid_describe_callback.snap @@ -2,142 +2,142 @@ source: crates/oxc_linter/src/tester.rs expression: valid_describe_callback --- - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe.each()() · ───────────────── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe['each']()() · ──────────────────── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe.each(() => {})() · ───────────────────────── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe.each(() => {})('foo') · ───── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe.each()(() => {}) · ──────── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe['each']()(() => {}) · ──────── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe.each('foo')(() => {}) · ──────── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe.only.each('foo')(() => {}) · ──────── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe(() => {}) · ──────── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo') · ───── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "Second argument must be a function" + ⚠ eslint(jest/valid-describe-callback): "Second argument must be a function" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo', 'foo2') · ────── ╰──── help: "Replace second argument with a function" - ⚠ "Describe requires name and callback arguments" + ⚠ eslint(jest/valid-describe-callback): "Describe requires name and callback arguments" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe() · ────────── ╰──── help: "Add name as first argument and callback as second argument" - ⚠ "No async describe callback" + ⚠ eslint(jest/valid-describe-callback): "No async describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo', async () => {}) · ────────────── ╰──── help: "Remove `async` keyword" - ⚠ "No async describe callback" + ⚠ eslint(jest/valid-describe-callback): "No async describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo', async function () {}) · ──────────────────── ╰──── help: "Remove `async` keyword" - ⚠ "No async describe callback" + ⚠ eslint(jest/valid-describe-callback): "No async describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ xdescribe('foo', async function () {}) · ──────────────────── ╰──── help: "Remove `async` keyword" - ⚠ "No async describe callback" + ⚠ eslint(jest/valid-describe-callback): "No async describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ fdescribe('foo', async function () {}) · ──────────────────── ╰──── help: "Remove `async` keyword" - ⚠ "No async describe callback" + ⚠ eslint(jest/valid-describe-callback): "No async describe callback" ╭─[valid_describe_callback.tsx:2:1] 2 │ import { fdescribe } from '@jest/globals'; 3 │ fdescribe('foo', async function () {}) · ──────────────────── - 4 │ + 4 │ ╰──── help: "Remove `async` keyword" - ⚠ "No async describe callback" + ⚠ eslint(jest/valid-describe-callback): "No async describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe.only('foo', async function () {}) · ──────────────────── ╰──── help: "Remove `async` keyword" - ⚠ "No async describe callback" + ⚠ eslint(jest/valid-describe-callback): "No async describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe.skip('foo', async function () {}) · ──────────────────── ╰──── help: "Remove `async` keyword" - ⚠ "No async describe callback" + ⚠ eslint(jest/valid-describe-callback): "No async describe callback" ╭─[valid_describe_callback.tsx:5:1] 5 │ }); 6 │ ╭─▶ describe('async', async () => { @@ -150,7 +150,7 @@ expression: valid_describe_callback ╰──── help: "Remove `async` keyword" - ⚠ "Unexpected return statement in describe callback" + ⚠ eslint(jest/valid-describe-callback): "Unexpected return statement in describe callback" ╭─[valid_describe_callback.tsx:2:1] 2 │ describe('foo', function () { 3 │ ╭─▶ return Promise.resolve().then(() => { @@ -162,7 +162,7 @@ expression: valid_describe_callback ╰──── help: "Remove return statement in your describe callback" - ⚠ "Unexpected return statement in describe callback" + ⚠ eslint(jest/valid-describe-callback): "Unexpected return statement in describe callback" ╭─[valid_describe_callback.tsx:2:1] 2 │ describe('foo', () => { 3 │ ╭─▶ return Promise.resolve().then(() => { @@ -174,7 +174,7 @@ expression: valid_describe_callback ╰──── help: "Remove return statement in your describe callback" - ⚠ "Unexpected return statement in describe callback" + ⚠ eslint(jest/valid-describe-callback): "Unexpected return statement in describe callback" ╭─[valid_describe_callback.tsx:8:1] 8 │ describe('nested', () => { 9 │ ╭─▶ return Promise.resolve().then(() => { @@ -186,7 +186,7 @@ expression: valid_describe_callback ╰──── help: "Remove return statement in your describe callback" - ⚠ "No async describe callback" + ⚠ eslint(jest/valid-describe-callback): "No async describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ 2 │ ╭─▶ describe('foo', async () => { @@ -200,11 +200,11 @@ expression: valid_describe_callback 10 │ │ }) 11 │ │ }) 12 │ ╰─▶ }) - 13 │ + 13 │ ╰──── help: "Remove `async` keyword" - ⚠ "Unexpected return statement in describe callback" + ⚠ eslint(jest/valid-describe-callback): "Unexpected return statement in describe callback" ╭─[valid_describe_callback.tsx:5:1] 5 │ describe('nested', () => { 6 │ ╭─▶ return Promise.resolve().then(() => { @@ -216,42 +216,42 @@ expression: valid_describe_callback ╰──── help: "Remove return statement in your describe callback" - ⚠ "Unexpected return statement in describe callback" + ⚠ eslint(jest/valid-describe-callback): "Unexpected return statement in describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo', () => test('bar', () => {})) · ───────────────────── ╰──── help: "Remove return statement in your describe callback" - ⚠ "Unexpected argument(s) in describe callback" + ⚠ eslint(jest/valid-describe-callback): "Unexpected argument(s) in describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo', done => {}) · ────────── ╰──── help: "Remove argument(s) of describe callback" - ⚠ "Unexpected argument(s) in describe callback" + ⚠ eslint(jest/valid-describe-callback): "Unexpected argument(s) in describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo', function (done) {}) · ────────────────── ╰──── help: "Remove argument(s) of describe callback" - ⚠ "Unexpected argument(s) in describe callback" + ⚠ eslint(jest/valid-describe-callback): "Unexpected argument(s) in describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo', function (one, two, three) {}) · ───────────────────────────── ╰──── help: "Remove argument(s) of describe callback" - ⚠ "No async describe callback" + ⚠ eslint(jest/valid-describe-callback): "No async describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo', async function (done) {}) · ──────────────────────── ╰──── help: "Remove `async` keyword" - ⚠ "Unexpected argument(s) in describe callback" + ⚠ eslint(jest/valid-describe-callback): "Unexpected argument(s) in describe callback" ╭─[valid_describe_callback.tsx:1:1] 1 │ describe('foo', async function (done) {}) · ────────────────────────