diff --git a/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/mod.rs b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/mod.rs new file mode 100644 index 0000000000000..a147aa9b1ab1a --- /dev/null +++ b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/mod.rs @@ -0,0 +1,317 @@ +use oxc_ast::{ast::Argument, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, Span}; + +#[cfg(test)] +mod tests; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{ + parse_jest_fn_call, JestFnKind, JestGeneralFnKind, ParsedJestFnCallNew, PossibleJestNode, + }, +}; + +fn prefer_lowercase_title_diagnostic(title: &str, span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Enforce lowercase test names") + .with_help(format!("`{title:?}`s should begin with lowercase")) + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct PreferLowercaseTitleConfig { + allowed_prefixes: Vec, + ignore: Vec, + ignore_top_level_describe: bool, + lowercase_first_character_only: bool, +} + +impl std::ops::Deref for PreferLowercaseTitle { + type Target = PreferLowercaseTitleConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Default, Clone)] +pub struct PreferLowercaseTitle(Box); + +declare_oxc_lint!( + /// ### What it does + /// + /// Enforce `it`, `test`, `describe`, and `bench` to have descriptions that begin with a + /// lowercase letter. This provides more readable test failures. This rule is not + /// enabled by default. + /// + /// ### Example + /// + /// ```javascript + /// // invalid + /// it('Adds 1 + 2 to equal 3', () => { + /// expect(sum(1, 2)).toBe(3); + /// }); + /// + /// // valid + /// it('adds 1 + 2 to equal 3', () => { + /// expect(sum(1, 2)).toBe(3); + /// }); + /// ``` + /// + /// ## Options + /// ```json + /// { + /// "jest/prefer-lowercase-title": [ + /// "error", + /// { + /// "ignore": ["describe", "test"] + /// } + /// ] + /// } + /// ``` + /// + /// ### `ignore` + /// + /// This array option controls which Jest or Vitest functions are checked by this rule. There + /// are four possible values: + /// - `"describe"` + /// - `"test"` + /// - `"it"` + /// - `"bench"` + /// + /// By default, none of these options are enabled (the equivalent of + /// `{ "ignore": [] }`). + /// + /// Example of **correct** code for the `{ "ignore": ["describe"] }` option: + /// ```js + /// /* eslint jest/prefer-lowercase-title: ["error", { "ignore": ["describe"] }] */ + /// describe('Uppercase description'); + /// ``` + /// + /// Example of **correct** code for the `{ "ignore": ["test"] }` option: + /// + /// ```js + /// /* eslint jest/prefer-lowercase-title: ["error", { "ignore": ["test"] }] */ + /// test('Uppercase description'); + /// ``` + /// + /// Example of **correct** code for the `{ "ignore": ["it"] }` option: + /// ```js + /// /* eslint jest/prefer-lowercase-title: ["error", { "ignore": ["it"] }] */ + /// it('Uppercase description'); + /// ``` + /// + /// ### `allowedPrefixes` + /// This array option allows specifying prefixes, which contain capitals that titles + /// can start with. This can be useful when writing tests for API endpoints, where + /// you'd like to prefix with the HTTP method. + /// By default, nothing is allowed (the equivalent of `{ "allowedPrefixes": [] }`). + /// + /// Example of **correct** code for the `{ "allowedPrefixes": ["GET"] }` option: + /// ```js + /// /* eslint jest/prefer-lowercase-title: ["error", { "allowedPrefixes": ["GET"] }] */ + /// describe('GET /live'); + /// ``` + /// + /// ### `ignoreTopLevelDescribe` + /// This option can be set to allow only the top-level `describe` blocks to have a + /// title starting with an upper-case letter. + /// Example of **correct** code for the `{ "ignoreTopLevelDescribe": true }` option: + /// + /// ```js + /// /* eslint jest/prefer-lowercase-title: ["error", { "ignoreTopLevelDescribe": true }] */ + /// describe('MyClass', () => { + /// describe('#myMethod', () => { + /// it('does things', () => { + /// // + /// }); + /// }); + /// }); + /// ``` + /// + /// ### `lowercaseFirstCharacterOnly` + /// This option can be set to only validate that the first character of a test name is lowercased. + /// + /// Example of **correct** code for the `{ "lowercaseFirstCharacterOnly": true }` option: + /// + /// ```js + /// /* eslint vitest/prefer-lowercase-title: ["error", { "lowercaseFirstCharacterOnly": true }] */ + /// describe('myClass', () => { + /// describe('myMethod', () => { + /// it('does things', () => { + /// // + /// }); + /// }); + /// }); + /// ``` + /// + /// Example of **incorrect** code for the `{ "lowercaseFirstCharacterOnly": true }` option: + /// + /// ```js + /// /* eslint vitest/prefer-lowercase-title: ["error", { "lowercaseFirstCharacterOnly": true }] */ + /// describe('MyClass', () => { + /// describe('MyMethod', () => { + /// it('does things', () => { + /// // + /// }); + /// }); + /// }); + /// ``` + PreferLowercaseTitle, + style, + fix +); + +impl Rule for PreferLowercaseTitle { + fn from_configuration(value: serde_json::Value) -> Self { + let obj = value.get(0); + let ignore_top_level_describe = obj + .and_then(|config| config.get("ignoreTopLevelDescribe")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(false); + let lowercase_first_character_only = obj + .and_then(|config| config.get("lowercaseFirstCharacterOnly")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(true); + let ignore = obj + .and_then(|config| config.get("ignore")) + .and_then(serde_json::Value::as_array) + .map(|v| v.iter().filter_map(serde_json::Value::as_str).map(CompactStr::from).collect()) + .unwrap_or_default(); + let allowed_prefixes = obj + .and_then(|config| config.get("allowedPrefixes")) + .and_then(serde_json::Value::as_array) + .map(|v| v.iter().filter_map(serde_json::Value::as_str).map(CompactStr::from).collect()) + .unwrap_or_default(); + + Self(Box::new(PreferLowercaseTitleConfig { + allowed_prefixes, + ignore, + ignore_top_level_describe, + lowercase_first_character_only, + })) + } + + fn run_on_jest_node<'a, 'c>( + &self, + possible_jest_node: &PossibleJestNode<'a, 'c>, + ctx: &'c LintContext<'a>, + ) { + let node = possible_jest_node.node; + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + let Some(ParsedJestFnCallNew::GeneralJest(jest_fn_call)) = + parse_jest_fn_call(call_expr, possible_jest_node, ctx) + else { + return; + }; + + let scopes = ctx.scopes(); + + let ignores = Self::populate_ignores(&self.ignore); + + if ignores.contains(&jest_fn_call.name.as_ref()) { + return; + } + + if matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Describe)) { + if self.ignore_top_level_describe && scopes.get_flags(node.scope_id()).is_top() { + return; + } + } else if !matches!( + jest_fn_call.kind, + JestFnKind::General(JestGeneralFnKind::Test | JestGeneralFnKind::Bench) + ) { + return; + } + + let Some(arg) = call_expr.arguments.first() else { + return; + }; + + if let Argument::StringLiteral(string_expr) = arg { + self.lint_string(ctx, string_expr.value.as_str(), string_expr.span); + } else if let Argument::TemplateLiteral(template_expr) = arg { + let Some(template_string) = template_expr.quasi() else { + return; + }; + self.lint_string(ctx, template_string.as_str(), template_expr.span); + } + } +} + +impl PreferLowercaseTitle { + fn lint_string<'a>(&self, ctx: &LintContext<'a>, literal: &'a str, span: Span) { + if literal.is_empty() + || self.allowed_prefixes.iter().any(|name| literal.starts_with(name.as_str())) + { + return; + } + + if self.lowercase_first_character_only { + let Some(first_char) = literal.chars().next() else { + return; + }; + + let lower = first_char.to_ascii_lowercase(); + if first_char == lower { + return; + } + } else { + for n in 0..literal.chars().count() { + let Some(next_char) = literal.chars().nth(n) else { + return; + }; + + let next_lower = next_char.to_ascii_lowercase(); + + if next_char != next_lower { + break; + } + } + } + + let replacement = if self.lowercase_first_character_only { + cow_utils::CowUtils::cow_to_ascii_lowercase(&literal.chars().as_str()[0..1]) + } else { + cow_utils::CowUtils::cow_to_ascii_lowercase(literal) + }; + + #[allow(clippy::cast_possible_truncation)] + let replacement_len = replacement.len() as u32; + + ctx.diagnostic_with_fix(prefer_lowercase_title_diagnostic(literal, span), |fixer| { + fixer.replace(Span::sized(span.start + 1, replacement_len), replacement) + }); + } + + fn populate_ignores(ignore: &[CompactStr]) -> Vec<&str> { + let mut ignores: Vec<&str> = vec![]; + let test_case_name = ["fit", "it", "xit", "test", "xtest"]; + let describe_alias = ["describe", "fdescribe", "xdescribe"]; + let test_name = "test"; + let it_name = "it"; + let bench_name = "bench"; + + if ignore.iter().any(|alias| alias == "describe") { + ignores.extend(describe_alias.iter()); + } + + if ignore.iter().any(|alias| alias == bench_name) { + ignores.push(bench_name); + } + + if ignore.iter().any(|alias| alias == test_name) { + ignores.extend(test_case_name.iter().filter(|alias| alias.ends_with(test_name))); + } + + if ignore.iter().any(|alias| alias == it_name) { + ignores.extend(test_case_name.iter().filter(|alias| alias.ends_with(it_name))); + } + + ignores + } +} diff --git a/crates/oxc_linter/src/rules/jest/prefer_lowercase_title.rs b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/tests/jest.rs similarity index 63% rename from crates/oxc_linter/src/rules/jest/prefer_lowercase_title.rs rename to crates/oxc_linter/src/rules/jest/prefer_lowercase_title/tests/jest.rs index 1acb980de3183..f4e2edf228704 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_lowercase_title.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/tests/jest.rs @@ -1,257 +1,7 @@ -use oxc_ast::{ast::Argument, AstKind}; -use oxc_diagnostics::OxcDiagnostic; -use oxc_macros::declare_oxc_lint; -use oxc_span::{CompactStr, Span}; - -use crate::{ - context::LintContext, - rule::Rule, - utils::{ - parse_jest_fn_call, JestFnKind, JestGeneralFnKind, ParsedJestFnCallNew, PossibleJestNode, - }, -}; - -fn unexpected_lowercase(x0: &str, span1: Span) -> OxcDiagnostic { - OxcDiagnostic::warn("Enforce lowercase test names") - .with_help(format!("`{x0:?}`s should begin with lowercase")) - .with_label(span1) -} - -#[derive(Debug, Default, Clone)] -pub struct PreferLowercaseTitleConfig { - allowed_prefixes: Vec, - ignore: Vec, - ignore_top_level_describe: bool, -} - -impl std::ops::Deref for PreferLowercaseTitle { - type Target = PreferLowercaseTitleConfig; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[derive(Debug, Default, Clone)] -pub struct PreferLowercaseTitle(Box); - -declare_oxc_lint!( - /// ### What it does - /// - /// Enforce `it`, `test` and `describe` to have descriptions that begin with a - /// lowercase letter. This provides more readable test failures. This rule is not - /// enabled by default. - /// - /// ### Example - /// - /// ```javascript - /// // invalid - /// it('Adds 1 + 2 to equal 3', () => { - /// expect(sum(1, 2)).toBe(3); - /// }); - /// - /// // valid - /// it('adds 1 + 2 to equal 3', () => { - /// expect(sum(1, 2)).toBe(3); - /// }); - /// ``` - /// - /// ## Options - /// ```json - /// { - /// "jest/prefer-lowercase-title": [ - /// "error", - /// { - /// "ignore": ["describe", "test"] - /// } - /// ] - /// } - /// ``` - /// - /// ### `ignore` - /// - /// This array option controls which Jest functions are checked by this rule. There - /// are three possible values: - /// - `"describe"` - /// - `"test"` - /// - `"it"` - /// - /// By default, none of these options are enabled (the equivalent of - /// `{ "ignore": [] }`). - /// - /// Example of **correct** code for the `{ "ignore": ["describe"] }` option: - /// ```js - /// /* eslint jest/prefer-lowercase-title: ["error", { "ignore": ["describe"] }] */ - /// describe('Uppercase description'); - /// ``` - /// - /// Example of **correct** code for the `{ "ignore": ["test"] }` option: - /// - /// ```js - /// /* eslint jest/prefer-lowercase-title: ["error", { "ignore": ["test"] }] */ - /// test('Uppercase description'); - /// ``` - /// - /// Example of **correct** code for the `{ "ignore": ["it"] }` option: - /// ```js - /// /* eslint jest/prefer-lowercase-title: ["error", { "ignore": ["it"] }] */ - /// it('Uppercase description'); - /// ``` - /// - /// ### `allowedPrefixes` - /// This array option allows specifying prefixes, which contain capitals that titles - /// can start with. This can be useful when writing tests for API endpoints, where - /// you'd like to prefix with the HTTP method. - /// By default, nothing is allowed (the equivalent of `{ "allowedPrefixes": [] }`). - /// - /// Example of **correct** code for the `{ "allowedPrefixes": ["GET"] }` option: - /// ```js - /// /* eslint jest/prefer-lowercase-title: ["error", { "allowedPrefixes": ["GET"] }] */ - /// describe('GET /live'); - /// ``` - /// - /// ### `ignoreTopLevelDescribe` - /// This option can be set to allow only the top-level `describe` blocks to have a - /// title starting with an upper-case letter. - /// Example of **correct** code for the `{ "ignoreTopLevelDescribe": true }` option: - /// - /// ```js - /// /* eslint jest/prefer-lowercase-title: ["error", { "ignoreTopLevelDescribe": true }] */ - /// describe('MyClass', () => { - /// describe('#myMethod', () => { - /// it('does things', () => { - /// // - /// }); - /// }); - /// }); - /// ``` - /// - PreferLowercaseTitle, - style, - fix -); - -impl Rule for PreferLowercaseTitle { - fn from_configuration(value: serde_json::Value) -> Self { - let obj = value.get(0); - let ignore_top_level_describe = obj - .and_then(|config| config.get("ignoreTopLevelDescribe")) - .and_then(serde_json::Value::as_bool) - .unwrap_or(false); - let ignore = obj - .and_then(|config| config.get("ignore")) - .and_then(serde_json::Value::as_array) - .map(|v| v.iter().filter_map(serde_json::Value::as_str).map(CompactStr::from).collect()) - .unwrap_or_default(); - let allowed_prefixes = obj - .and_then(|config| config.get("allowedPrefixes")) - .and_then(serde_json::Value::as_array) - .map(|v| v.iter().filter_map(serde_json::Value::as_str).map(CompactStr::from).collect()) - .unwrap_or_default(); - - Self(Box::new(PreferLowercaseTitleConfig { - allowed_prefixes, - ignore, - ignore_top_level_describe, - })) - } - - fn run_on_jest_node<'a, 'c>( - &self, - jest_node: &PossibleJestNode<'a, 'c>, - ctx: &'c LintContext<'a>, - ) { - self.run(jest_node, ctx); - } -} - -impl PreferLowercaseTitle { - fn run<'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(ParsedJestFnCallNew::GeneralJest(jest_fn_call)) = - parse_jest_fn_call(call_expr, possible_jest_node, ctx) - else { - return; - }; - - let scopes = ctx.scopes(); - let ignores = Self::populate_ignores(&self.ignore); - - if ignores.contains(&jest_fn_call.name.as_ref()) { - return; - } - - if matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Describe)) { - if self.ignore_top_level_describe && scopes.get_flags(node.scope_id()).is_top() { - return; - } - } else if !matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Test)) { - return; - } - - let Some(arg) = call_expr.arguments.first() else { - return; - }; - - if let Argument::StringLiteral(string_expr) = arg { - self.lint_string(ctx, string_expr.value.as_str(), string_expr.span); - } else if let Argument::TemplateLiteral(template_expr) = arg { - let Some(template_string) = template_expr.quasi() else { - return; - }; - self.lint_string(ctx, template_string.as_str(), template_expr.span); - } - } - - fn populate_ignores(ignore: &[CompactStr]) -> Vec<&str> { - let mut ignores: Vec<&str> = vec![]; - let test_case_name = ["fit", "it", "xit", "test", "xtest"]; - let describe_alias = ["describe", "fdescribe", "xdescribe"]; - let test_name = "test"; - let it_name = "it"; - - if ignore.iter().any(|alias| alias == "describe") { - ignores.extend(describe_alias.iter()); - } - - if ignore.iter().any(|alias| alias == test_name) { - ignores.extend(test_case_name.iter().filter(|alias| alias.ends_with(test_name))); - } - - if ignore.iter().any(|alias| alias == it_name) { - ignores.extend(test_case_name.iter().filter(|alias| alias.ends_with(it_name))); - } - - ignores - } - - fn lint_string<'a>(&self, ctx: &LintContext<'a>, literal: &'a str, span: Span) { - if literal.is_empty() - || self.allowed_prefixes.iter().any(|name| literal.starts_with(name.as_str())) - { - return; - } - - let Some(first_char) = literal.chars().next() else { - return; - }; - - let lower = first_char.to_ascii_lowercase(); - if first_char == lower { - return; - } - - ctx.diagnostic_with_fix(unexpected_lowercase(literal, span), |fixer| { - fixer.replace(Span::sized(span.start + 1, 1), lower.to_string()) - }); - } -} - #[test] fn test() { + use super::PreferLowercaseTitle; + use crate::rule::RuleMeta; use crate::tester::Tester; let pass = vec![ @@ -629,5 +379,6 @@ fn test() { Tester::new(PreferLowercaseTitle::NAME, PreferLowercaseTitle::CATEGORY, pass, fail) .with_jest_plugin(true) .expect_fix(fix) + .with_snapshot_suffix("jest") .test_and_snapshot(); } diff --git a/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/tests/mod.rs b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/tests/mod.rs new file mode 100644 index 0000000000000..8fc470f2b7d53 --- /dev/null +++ b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/tests/mod.rs @@ -0,0 +1,4 @@ +mod jest; +mod vitest; + +use super::PreferLowercaseTitle; diff --git a/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/tests/vitest.rs b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/tests/vitest.rs new file mode 100644 index 0000000000000..90531153c1627 --- /dev/null +++ b/crates/oxc_linter/src/rules/jest/prefer_lowercase_title/tests/vitest.rs @@ -0,0 +1,43 @@ +#[test] +fn test() { + use super::PreferLowercaseTitle; + use crate::rule::RuleMeta; + use crate::tester::Tester; + + let pass: Vec<(&str, Option)> = vec![ + ("it.each()", None), + ("it.each()(1)", None), + ("it.todo();", None), + (r#"describe("oo", function () {})"#, None), + (r#"test("foo", function () {})"#, None), + ("test(`123`, function () {})", None), + ]; + + let fail: Vec<(&str, Option)> = vec![ + (r#"it("Foo MM mm", function () {})"#, None), + ("test(`Foo MM mm`, function () {})", None), + ( + "test(`SFC Compile`, function () {})", + Some(serde_json::json!([{ "lowercaseFirstCharacterOnly": false }])), + ), + ("bench(`Foo MM mm`, function () {})", None), + ]; + + let fix: Vec<(&str, &str, Option)> = vec![ + (r#"it("Foo MM mm", function () {})"#, r#"it("foo MM mm", function () {})"#, None), + ("test(`Foo MM mm`, function () {})", "test(`foo MM mm`, function () {})", None), + ( + "test(`SFC Compile`, function () {})", + "test(`sfc compile`, function () {})", + Some(serde_json::json!([{ "lowercaseFirstCharacterOnly": false }])), + ), + ("bench(`Foo MM mm`, function () {})", "bench(`foo MM mm`, function () {})", None), + ]; + + Tester::new(PreferLowercaseTitle::NAME, PreferLowercaseTitle::CATEGORY, pass, fail) + .expect_fix(fix) + .with_jest_plugin(true) + .with_vitest_plugin(true) + .with_snapshot_suffix("vitest") + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/jest_prefer_lowercase_title.snap b/crates/oxc_linter/src/snapshots/jest_prefer_lowercase_title@jest.snap similarity index 100% rename from crates/oxc_linter/src/snapshots/jest_prefer_lowercase_title.snap rename to crates/oxc_linter/src/snapshots/jest_prefer_lowercase_title@jest.snap diff --git a/crates/oxc_linter/src/snapshots/jest_prefer_lowercase_title@vitest.snap b/crates/oxc_linter/src/snapshots/jest_prefer_lowercase_title@vitest.snap new file mode 100644 index 0000000000000..f9b0d29053861 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/jest_prefer_lowercase_title@vitest.snap @@ -0,0 +1,31 @@ +--- +source: crates/oxc_linter/src/tester.rs +snapshot_kind: text +--- + ⚠ eslint-plugin-vitest(prefer-lowercase-title): Enforce lowercase test names + ╭─[prefer_lowercase_title.tsx:1:4] + 1 │ it("Foo MM mm", function () {}) + · ─────────── + ╰──── + help: `"Foo MM mm"`s should begin with lowercase + + ⚠ eslint-plugin-vitest(prefer-lowercase-title): Enforce lowercase test names + ╭─[prefer_lowercase_title.tsx:1:6] + 1 │ test(`Foo MM mm`, function () {}) + · ─────────── + ╰──── + help: `"Foo MM mm"`s should begin with lowercase + + ⚠ eslint-plugin-vitest(prefer-lowercase-title): Enforce lowercase test names + ╭─[prefer_lowercase_title.tsx:1:6] + 1 │ test(`SFC Compile`, function () {}) + · ───────────── + ╰──── + help: `"SFC Compile"`s should begin with lowercase + + ⚠ eslint-plugin-vitest(prefer-lowercase-title): Enforce lowercase test names + ╭─[prefer_lowercase_title.tsx:1:7] + 1 │ bench(`Foo MM mm`, function () {}) + · ─────────── + ╰──── + help: `"Foo MM mm"`s should begin with lowercase diff --git a/crates/oxc_linter/src/utils/jest.rs b/crates/oxc_linter/src/utils/jest.rs index 9ae9b6a6b1e37..4bee87c9c35df 100644 --- a/crates/oxc_linter/src/utils/jest.rs +++ b/crates/oxc_linter/src/utils/jest.rs @@ -24,6 +24,7 @@ pub use crate::utils::jest::parse_jest_fn::{ pub const JEST_METHOD_NAMES: phf::Set<&'static str> = phf_set![ "afterAll", "afterEach", + "bench", "beforeAll", "beforeEach", "describe", @@ -55,6 +56,7 @@ impl JestFnKind { "expect" => Self::Expect, "expectTypeOf" => Self::ExpectTypeOf, "vi" => Self::General(JestGeneralFnKind::Vitest), + "bench" => Self::General(JestGeneralFnKind::Bench), "jest" => Self::General(JestGeneralFnKind::Jest), "describe" | "fdescribe" | "xdescribe" => Self::General(JestGeneralFnKind::Describe), "fit" | "it" | "test" | "xit" | "xtest" => Self::General(JestGeneralFnKind::Test), @@ -80,6 +82,7 @@ pub enum JestGeneralFnKind { Test, Jest, Vitest, + Bench, } /// diff --git a/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs b/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs index 794da7e2db21f..81a6ff66ec331 100644 --- a/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs +++ b/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs @@ -567,11 +567,12 @@ fn recurse_extend_node_chain<'a>( } // sorted list for binary search. -const VALID_JEST_FN_CALL_CHAINS: [[&str; 4]; 51] = [ +const VALID_JEST_FN_CALL_CHAINS: [[&str; 4]; 52] = [ ["afterAll", "", "", ""], ["afterEach", "", "", ""], ["beforeAll", "", "", ""], ["beforeEach", "", "", ""], + ["bench", "", "", ""], ["describe", "", "", ""], ["describe", "each", "", ""], ["describe", "only", "", ""], diff --git a/crates/oxc_linter/src/utils/mod.rs b/crates/oxc_linter/src/utils/mod.rs index c3627fb7d6cd8..6fa3777e86fec 100644 --- a/crates/oxc_linter/src/utils/mod.rs +++ b/crates/oxc_linter/src/utils/mod.rs @@ -30,6 +30,7 @@ const VITEST_COMPATIBLE_JEST_RULES: phf::Set<&'static str> = phf::phf_set! { "no-restricted-jest-methods", "no-test-prefixes", "prefer-hooks-in-order", + "prefer-lowercase-title", "valid-describe-callback", "valid-expect", };