From 1681b11adba83a08839884245dfdfb13d8395100 Mon Sep 17 00:00:00 2001 From: cinchen Date: Sat, 6 Jul 2024 11:03:14 +0800 Subject: [PATCH] feat(linter): eslint-plugin-jest/consistent-test-it (#4053) part of https://github.com/oxc-project/oxc/issues/492 Rule Detail: [link](https://github.com/jest-community/eslint-plugin-jest/blob/main/src/rules/consistent-test-it.ts) --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/jest/consistent_test_it.rs | 926 ++++++++++++++++++ .../src/rules/jest/prefer_jest_mocked.rs | 2 + .../src/snapshots/consistent_test_it.snap | 455 +++++++++ crates/oxc_linter/src/utils/jest.rs | 2 +- .../src/utils/jest/parse_jest_fn.rs | 2 + crates/oxc_linter/src/utils/mod.rs | 4 +- 7 files changed, 1391 insertions(+), 2 deletions(-) create mode 100644 crates/oxc_linter/src/rules/jest/consistent_test_it.rs create mode 100644 crates/oxc_linter/src/snapshots/consistent_test_it.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 9ff9ff70f4e29..d18bf05479dfb 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -158,6 +158,7 @@ mod typescript { } mod jest { + pub mod consistent_test_it; pub mod expect_expect; pub mod max_expects; pub mod max_nested_describe; @@ -555,6 +556,7 @@ oxc_macros::declare_all_lint_rules! { typescript::no_non_null_assertion, typescript::no_non_null_asserted_nullish_coalescing, typescript::no_dynamic_delete, + jest::consistent_test_it, jest::expect_expect, jest::max_expects, jest::max_nested_describe, diff --git a/crates/oxc_linter/src/rules/jest/consistent_test_it.rs b/crates/oxc_linter/src/rules/jest/consistent_test_it.rs new file mode 100644 index 0000000000000..ec35b6ee5833f --- /dev/null +++ b/crates/oxc_linter/src/rules/jest/consistent_test_it.rs @@ -0,0 +1,926 @@ +use std::str::FromStr; + +use oxc_ast::{ast::Expression, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::ScopeId; +use oxc_span::{GetSpan, Span}; +use rustc_hash::FxHashMap; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{ + collect_possible_jest_call_node, get_test_plugin_name, parse_jest_fn_call, JestFnKind, + JestGeneralFnKind, ParsedJestFnCallNew, PossibleJestNode, + }, +}; + +fn consistent_method(x0: &str, x1: &str, x2: &str, span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(format!( + "{x0}(consistent-test-it): Enforce `test` and `it` usage conventions", + )) + .with_help(format!("Prefer using {x1:?} instead of {x2:?}")) + .with_label(span0) +} + +fn consistent_method_within_describe(x0: &str, x1: &str, x2: &str, span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(format!( + "{x0}(consistent-test-it): Enforce `test` and `it` usage conventions", + )) + .with_help(format!("Prefer using {x1:?} instead of {x2:?} within describe")) + .with_label(span0) +} + +#[derive(Debug, Clone, Eq, PartialEq)] +enum TestCaseName { + Fit, + IT, + Test, + Xit, + Xtest, +} + +impl std::fmt::Display for TestCaseName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Fit => write!(f, "fit"), + Self::IT => write!(f, "it"), + Self::Test => write!(f, "test"), + Self::Xit => write!(f, "xit"), + Self::Xtest => write!(f, "xtest"), + } + } +} + +impl std::str::FromStr for TestCaseName { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "fit" => Ok(TestCaseName::Fit), + "it" => Ok(TestCaseName::IT), + "test" => Ok(TestCaseName::Test), + "xit" => Ok(TestCaseName::Xit), + "xtest" => Ok(TestCaseName::Xtest), + _ => Err("Unknown Test case name"), + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct ConsistentTestIt(Box); + +#[derive(Debug, Clone)] +pub struct ConsistentTestItConfig { + within_describe: TestCaseName, + within_fn: TestCaseName, +} + +impl std::ops::Deref for ConsistentTestIt { + type Target = ConsistentTestItConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Default for ConsistentTestItConfig { + fn default() -> Self { + Self { within_describe: TestCaseName::IT, within_fn: TestCaseName::Test } + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Jest allows you to choose how you want to define your tests, using the `it` or + /// the `test` keywords, with multiple permutations for each: + /// + /// - **it:** `it`, `xit`, `fit`, `it.only`, `it.skip`. + /// - **test:** `test`, `xtest`, `test.only`, `test.skip`. + /// + /// ### Example + /// + /// ```javascript + /// /*eslint jest/consistent-test-it: ["error", {"fn": "test"}]*/ + /// test('foo'); // valid + /// test.only('foo'); // valid + /// + /// it('foo'); // invalid + /// it.only('foo'); // invalid + /// ``` + /// + /// ```javascript + /// /*eslint jest/consistent-test-it: ["error", {"fn": "it"}]*/ + /// it('foo'); // valid + /// it.only('foo'); // valid + /// test('foo'); // invalid + /// test.only('foo'); // invalid + /// ``` + /// + /// ```javascript + /// /*eslint jest/consistent-test-it: ["error", {"fn": "it", "withinDescribe": "test"}]*/ + /// it('foo'); // valid + /// describe('foo', function () { + /// test('bar'); // valid + /// }); + /// + /// test('foo'); // invalid + /// describe('foo', function () { + /// it('bar'); // invalid + /// }); + /// ``` + /// + /// #### Options + /// + /// This rule can be configured as follows + /// ```json5 + /// { + /// type: 'object', + /// properties: { + /// fn: { + /// enum: ['it', 'test'], + /// }, + /// withinDescribe: { + /// enum: ['it', 'test'], + /// }, + /// }, + /// additionalProperties: false, + /// } + /// ``` + /// + /// ##### fn + /// Decides whether to use `test` or `it`. + /// + /// ##### withinDescribe + /// Decides whether to use `test` or `it` within a `describe` scope. + /// + /// + /// This rule is compatible with [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/consistent-test-it.md), + /// to use it, add the following configuration to your `.eslintrc.json`: + /// + /// ```json + /// { + /// "rules": { + /// "vitest/consistent-test-it": "error" + /// } + /// } + ConsistentTestIt, + style, +); + +impl Rule for ConsistentTestIt { + fn from_configuration(value: serde_json::Value) -> Self { + let config = value.get(0); + + let within_fn = config + .and_then(|config| config.get("fn")) + .and_then(serde_json::Value::as_str) + .and_then(|x| TestCaseName::from_str(x).ok()) + .unwrap_or(TestCaseName::Test); + + let within_describe = config + .and_then(|config| config.get("withinDescribe")) + .and_then(serde_json::Value::as_str) + .and_then(|x| TestCaseName::from_str(x).ok()) + .unwrap_or( + config + .and_then(|config| config.get("fn")) + .and_then(serde_json::Value::as_str) + .and_then(|x| TestCaseName::from_str(x).ok()) + .unwrap_or(TestCaseName::IT), + ); + + Self(Box::new(ConsistentTestItConfig { within_describe, within_fn })) + } + + fn run_once(&self, ctx: &LintContext) { + let plugin_name = get_test_plugin_name(ctx); + let mut describe_nesting_hash: FxHashMap = FxHashMap::default(); + let mut possible_jest_nodes = collect_possible_jest_call_node(ctx); + possible_jest_nodes.sort_by_key(|n| n.node.id()); + + for possible_jest_node in &possible_jest_nodes { + self.run(&mut describe_nesting_hash, plugin_name, possible_jest_node, ctx); + } + } +} + +impl ConsistentTestIt { + fn run<'a>( + &self, + describe_nesting_hash: &mut FxHashMap, + plugin_name: &str, + 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::GeneralJestFnCall(jest_fn_call)) = + parse_jest_fn_call(call_expr, possible_jest_node, ctx) + else { + return; + }; + + if matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Describe)) { + let scope_id = node.scope_id(); + let current_count = describe_nesting_hash.get(&scope_id).unwrap_or(&0); + describe_nesting_hash.insert(scope_id, *current_count + 1); + return; + } + + let is_test = matches!(jest_fn_call.kind, JestFnKind::General(JestGeneralFnKind::Test)); + let fn_to_str = self.within_fn.to_string(); + + if is_test && describe_nesting_hash.is_empty() && !jest_fn_call.name.ends_with(&fn_to_str) { + let opposite_test_keyword = Self::get_opposite_test_case(&self.within_fn); + if let Some((span, prefer_test_name)) = Self::get_prefer_test_name_and_span( + call_expr.callee.get_inner_expression(), + &jest_fn_call.name, + &fn_to_str, + ) { + ctx.diagnostic_with_fix( + consistent_method(plugin_name, &fn_to_str, &opposite_test_keyword, span), + |fixer| fixer.replace(span, prefer_test_name), + ); + } + } + + let describe_to_str = self.within_describe.to_string(); + + if is_test + && !describe_nesting_hash.is_empty() + && !jest_fn_call.name.ends_with(&describe_to_str) + { + let opposite_test_keyword = Self::get_opposite_test_case(&self.within_describe); + if let Some((span, prefer_test_name)) = Self::get_prefer_test_name_and_span( + call_expr.callee.get_inner_expression(), + &jest_fn_call.name, + &describe_to_str, + ) { + ctx.diagnostic_with_fix( + consistent_method_within_describe( + plugin_name, + &describe_to_str, + &opposite_test_keyword, + span, + ), + |fixer| fixer.replace(span, prefer_test_name), + ); + } + } + } + + fn get_opposite_test_case(test_case_name: &TestCaseName) -> String { + if matches!(test_case_name, TestCaseName::Test) { + TestCaseName::IT.to_string() + } else { + TestCaseName::Test.to_string() + } + } + + fn get_prefer_test_name_and_span( + expr: &Expression, + test_name: &str, + fix_jest_name: &str, + ) -> Option<(Span, String)> { + match expr { + Expression::Identifier(ident) => { + if ident.name.eq("fit") { + return Some((ident.span, "test.only".to_string())); + } + + let prefer_test_name = match test_name.chars().next() { + Some('x') => format!("x{fix_jest_name}"), + Some('f') => format!("f{fix_jest_name}"), + _ => fix_jest_name.to_string(), + }; + Some((ident.span(), prefer_test_name)) + } + Expression::StaticMemberExpression(expr) => { + Self::get_prefer_test_name_and_span(&expr.object, test_name, fix_jest_name) + } + Expression::CallExpression(call_expr) => Self::get_prefer_test_name_and_span( + call_expr.callee.get_inner_expression(), + test_name, + fix_jest_name, + ), + Expression::TaggedTemplateExpression(expr) => Self::get_prefer_test_name_and_span( + expr.tag.get_inner_expression(), + test_name, + fix_jest_name, + ), + _ => None, + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let mut pass = vec![ + // consistent-test-it with fn=test + ("test(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("test.only(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("test.skip(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("test.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("xtest(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("test.each([])(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("test.each``(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "fn": "test" }])), + ), + // consistent-test-it with fn=it + ("it(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("fit(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("xit(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("it.only(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("it.skip(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("it.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("it.each([])(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("it.each``(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "fn": "it" }]))), + // consistent-test-it with fn=test and withinDescribe=it + ("test(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }]))), + ("test.only(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }]))), + ("test.skip(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }]))), + ( + "test.concurrent(\"foo\")", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ("xtest(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }]))), + ( + "[1,2,3].forEach(() => { test(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + // consistent-test-it with fn=it and withinDescribe=test + ("it(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }]))), + ("it.only(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }]))), + ("it.skip(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }]))), + ( + "it.concurrent(\"foo\")", + Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }])), + ), + ("xit(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }]))), + ( + "[1,2,3].forEach(() => { it(\"foo\") })", + Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }])), + ), + // consistent-test-it with fn=test and withinDescribe=test + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "test" }])), + ), + ("test(\"foo\");", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "test" }]))), + // consistent-test-it with fn=it and withinDescribe=it + ( + "describe(\"suite\", () => { it(\"foo\") })", + Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }])), + ), + ("it(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }]))), + // consistent-test-it defaults without config object + ("test(\"foo\")", None), + // consistent-test-it with withinDescribe=it + ("test(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "it" }]))), + ( + "describe(\"suite\", () => { it(\"foo\") })", + Some(serde_json::json!([{ "withinDescribe": "it" }])), + ), + // consistent-test-it with withinDescribe=test + ("test(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "test" }]))), + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "withinDescribe": "test" }])), + ), + ]; + + let mut fail = vec![ + // consistent-test-it with fn=test + ("it(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ( + " + import { it } from '@jest/globals'; + + it(\"foo\") + ", + Some(serde_json::json!([{ "fn": "test" }])), + ), + ( + " + import { it as testThisThing } from '@jest/globals'; + + testThisThing(\"foo\") + ", + Some(serde_json::json!([{ "fn": "test" }])), + ), + ("xit(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("fit(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("it.skip(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("it.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("it.only(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("it.each([])(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("it.each``(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ( + "describe.each``(\"foo\", () => { it.each``(\"bar\") })", + Some(serde_json::json!([{ "fn": "test" }])), + ), + ( + "describe.each``(\"foo\", () => { test.each``(\"bar\") })", + Some(serde_json::json!([{ "fn": "it" }])), + ), + ( + " + describe.each()(\"%s\", () => { + test(\"is valid, but should not be\", () => {}); + + it(\"is not valid, but should be\", () => {}); + }); + ", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + " + describe.only.each()(\"%s\", () => { + test(\"is valid, but should not be\", () => {}); + + it(\"is not valid, but should be\", () => {}); + }); + ", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ("describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "fn": "test" }]))), + // consistent-test-it with fn=it + ("test(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("xtest(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("test.skip(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("test.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("test.only(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("test.each([])(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ( + "describe.each``(\"foo\", () => { test.each``(\"bar\") })", + Some(serde_json::json!([{ "fn": "it" }])), + ), + ("test.each``(\"foo\")", Some(serde_json::json!([{ "fn": "it" }]))), + ("describe(\"suite\", () => { test(\"foo\") })", Some(serde_json::json!([{ "fn": "it" }]))), + // consistent-test-it with fn=test and withinDescribe=it + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + "describe(\"suite\", () => { test.only(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + "describe(\"suite\", () => { xtest(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + " + import { xtest as dontTestThis } from '@jest/globals'; + + describe(\"suite\", () => { dontTestThis(\"foo\") }); + ", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + " + import { describe as context, xtest as dontTestThis } from '@jest/globals'; + + context(\"suite\", () => { dontTestThis(\"foo\") }); + ", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + "describe(\"suite\", () => { test.skip(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + "describe(\"suite\", () => { test.concurrent(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + // consistent-test-it with fn=it and withinDescribe=test + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + "describe(\"suite\", () => { test.only(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + "describe(\"suite\", () => { xtest(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + " + import { xtest as dontTestThis } from '@jest/globals'; + + describe(\"suite\", () => { dontTestThis(\"foo\") }); + ", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + " + import { describe as context, xtest as dontTestThis } from '@jest/globals'; + + context(\"suite\", () => { dontTestThis(\"foo\") }); + ", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + "describe(\"suite\", () => { test.skip(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + ( + "describe(\"suite\", () => { test.concurrent(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "it" }])), + ), + // consistent-test-it with fn=test and withinDescribe=test + ( + "describe(\"suite\", () => { it(\"foo\") })", + Some(serde_json::json!([{ "fn": "test", "withinDescribe": "test" }])), + ), + ("it(\"foo\")", Some(serde_json::json!([{ "fn": "test", "withinDescribe": "test" }]))), + // consistent-test-it with fn=it and withinDescribe=it + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }])), + ), + ("test(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }]))), + // consistent-test-it defaults without config object + ("describe(\"suite\", () => { test(\"foo\") })", None), + // consistent-test-it with withinDescribe=it + ("it(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "it" }]))), + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "withinDescribe": "it" }])), + ), + // consistent-test-it with withinDescribe=test + ("it(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "test" }]))), + ( + "describe(\"suite\", () => { it(\"foo\") })", + Some(serde_json::json!([{ "withinDescribe": "test" }])), + ), + ]; + + let mut fix = vec![ + // consistent-test-it with fn=test + ("it(\"foo\")", "test(\"foo\")"), + ( + " + import { it } from '@jest/globals'; + it(\"foo\") + ", + " + import { it } from '@jest/globals'; + test(\"foo\") + ", + ), + ( + " + import { it as testThisThing } from '@jest/globals'; + testThisThing(\"foo\") + ", + " + import { it as testThisThing } from '@jest/globals'; + test(\"foo\") + ", + ), + ("xit(\"foo\")", "xtest(\"foo\")"), + ("fit(\"foo\")", "test.only(\"foo\")"), + ("it.skip(\"foo\")", "test.skip(\"foo\")"), + ("it.concurrent(\"foo\")", "test.concurrent(\"foo\")"), + ("it.only(\"foo\")", "test.only(\"foo\")"), + ("it.each([])(\"foo\")", "test.each([])(\"foo\")"), + ("it.each``(\"foo\")", "test.each``(\"foo\")"), + // Note: couldn't fix + // Todo: this need to fixer support option configuration. + // ( + // "describe.each``(\"foo\", () => { it.each``(\"bar\") })", + // "describe.each``(\"foo\", () => { test.each``(\"bar\") })", + // ), + ( + "describe.each``(\"foo\", () => { test.each``(\"bar\") })", + "describe.each``(\"foo\", () => { it.each``(\"bar\") })", + ), + ( + " + describe.each()(\"%s\", () => { + test(\"is valid, but should not be\", () => {}); + it(\"is not valid, but should be\", () => {}); + }); + ", + " + describe.each()(\"%s\", () => { + it(\"is valid, but should not be\", () => {}); + it(\"is not valid, but should be\", () => {}); + }); + ", + ), + ( + " + describe.only.each()(\"%s\", () => { + test(\"is valid, but should not be\", () => {}); + it(\"is not valid, but should be\", () => {}); + }); + ", + " + describe.only.each()(\"%s\", () => { + it(\"is valid, but should not be\", () => {}); + it(\"is not valid, but should be\", () => {}); + }); + ", + ), + // Note: couldn't fix, because the fixer couldn't be set option `fn=it` + // ( + // "describe(\"suite\", () => { it(\"foo\") })", + // "describe(\"suite\", () => { test(\"foo\") })", + // ), + // consistent-test-it with fn=it + // ("test(\"foo\")", "it(\"foo\")"), + // ("xtest(\"foo\")", "xit(\"foo\")"), + // ("test.skip(\"foo\")", "it.skip(\"foo\")"), + // ("test.concurrent(\"foo\")", "it.concurrent(\"foo\")"), + // ("test.only(\"foo\")", "it.only(\"foo\")"), + // ("test.each([])(\"foo\")", "it.each([])(\"foo\")"), + // ("test.each``(\"foo\")", "it.each``(\"foo\")"), + ( + "describe.each``(\"foo\", () => { test.each``(\"bar\") })", + "describe.each``(\"foo\", () => { it.each``(\"bar\") })", + ), + ( + "describe(\"suite\", () => { test(\"foo\") })", + "describe(\"suite\", () => { it(\"foo\") })", + ), + // + // consistent-test-it with fn=test and withinDescribe=it + ( + "describe(\"suite\", () => { test(\"foo\") })", + "describe(\"suite\", () => { it(\"foo\") })", + ), + ( + "describe(\"suite\", () => { test.only(\"foo\") })", + "describe(\"suite\", () => { it.only(\"foo\") })", + ), + ( + "describe(\"suite\", () => { xtest(\"foo\") })", + "describe(\"suite\", () => { xit(\"foo\") })", + ), + ( + " + import { xtest as dontTestThis } from '@jest/globals'; + describe(\"suite\", () => { dontTestThis(\"foo\") }); + ", + " + import { xtest as dontTestThis } from '@jest/globals'; + describe(\"suite\", () => { xit(\"foo\") }); + ", + ), + ( + " + import { describe as context, xtest as dontTestThis } from '@jest/globals'; + context(\"suite\", () => { dontTestThis(\"foo\") }); + ", + " + import { describe as context, xtest as dontTestThis } from '@jest/globals'; + context(\"suite\", () => { xit(\"foo\") }); + ", + ), + ( + "describe(\"suite\", () => { test.skip(\"foo\") })", + "describe(\"suite\", () => { it.skip(\"foo\") })", + ), + ( + "describe(\"suite\", () => { test.concurrent(\"foo\") })", + "describe(\"suite\", () => { it.concurrent(\"foo\") })", + ), + // consistent-test-it with fn=it and withinDescribe=test + ( + "describe(\"suite\", () => { test(\"foo\") })", + "describe(\"suite\", () => { it(\"foo\") })", + ), + ( + "describe(\"suite\", () => { test.only(\"foo\") })", + "describe(\"suite\", () => { it.only(\"foo\") })", + ), + ( + "describe(\"suite\", () => { xtest(\"foo\") })", + "describe(\"suite\", () => { xit(\"foo\") })", + ), + ( + " + import { xtest as dontTestThis } from '@jest/globals'; + describe(\"suite\", () => { dontTestThis(\"foo\") }); + ", + " + import { xtest as dontTestThis } from '@jest/globals'; + describe(\"suite\", () => { xit(\"foo\") }); + ", + ), + ( + " + import { describe as context, xtest as dontTestThis } from '@jest/globals'; + context(\"suite\", () => { dontTestThis(\"foo\") }); + ", + " + import { describe as context, xtest as dontTestThis } from '@jest/globals'; + context(\"suite\", () => { xit(\"foo\") }); + ", + ), + ( + "describe(\"suite\", () => { test.skip(\"foo\") })", + "describe(\"suite\", () => { it.skip(\"foo\") })", + ), + ( + "describe(\"suite\", () => { test.concurrent(\"foo\") })", + "describe(\"suite\", () => { it.concurrent(\"foo\") })", + ), + // Note: couldn't fix + // Todo: this need to fixer support option configuration. + // consistent-test-it with fn=test and withinDescribe=test + // ( + // "describe(\"suite\", () => { it(\"foo\") })", + // "describe(\"suite\", () => { test(\"foo\") })", + // ), + // ("it(\"foo\")", "test(\"foo\")"), + // + // consistent-test-it with fn=it and withinDescribe=it + ( + "describe(\"suite\", () => { test(\"foo\") })", + "describe(\"suite\", () => { it(\"foo\") })", + ), + // ("test(\"foo\")", "it(\"foo\")"), + // consistent-test-it defaults without config object + ( + "describe(\"suite\", () => { test(\"foo\") })", + "describe(\"suite\", () => { it(\"foo\") })", + ), + // consistent-test-it with withinDescribe=it + ("it(\"foo\")", "test(\"foo\")"), + ( + "describe(\"suite\", () => { test(\"foo\") })", + "describe(\"suite\", () => { it(\"foo\") })", + ), + // consistent-test-it with withinDescribe=test + ("it(\"foo\")", "test(\"foo\")"), + // Note: couldn't fixed + // Todo: this need to fixer support option configuration. + // ( + // "describe(\"suite\", () => { it(\"foo\") })", + // "describe(\"suite\", () => { test(\"foo\") })", + // ), + ]; + + let pass_vitest = vec![ + ( + " + it(\"shows error\", () => { + expect(true).toBe(false); + }); + ", + Some(serde_json::json!([{ "fn": "it" }])), + ), + ( + " + it(\"foo\", function () { + expect(true).toBe(false); + }) + ", + Some(serde_json::json!([{ "fn": "it" }])), + ), + ( + " + it('foo', () => { + expect(true).toBe(false); + }); + function myTest() { if ('bar') {} } + ", + Some(serde_json::json!([{ "fn": "it" }])), + ), + ( + " + test(\"shows error\", () => { + expect(true).toBe(false); + }); + ", + Some(serde_json::json!([{ "fn": "test" }])), + ), + ("test.skip(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("test.concurrent(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("xtest(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("test.each([])(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ("test.each``(\"foo\")", Some(serde_json::json!([{ "fn": "test" }]))), + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "fn": "test" }])), + ), + ( + "describe(\"suite\", () => { it(\"foo\") })", + Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }])), + ), + ("it(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }]))), + ("test(\"shows error\", () => {});", None), + ("test(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "it" }]))), + ( + "describe(\"suite\", () => { it(\"foo\") })", + Some(serde_json::json!([{ "withinDescribe": "it" }])), + ), + ("test(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "test" }]))), + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "withinDescribe": "test" }])), + ), + ]; + + let fail_vitest = vec![ + ("test(\"shows error\", () => {});", Some(serde_json::json!([{ "fn": "it" }]))), + ("test.skip(\"shows error\");", Some(serde_json::json!([{ "fn": "it" }]))), + ("test.only('shows error');", Some(serde_json::json!([{ "fn": "it" }]))), + ( + "describe('foo', () => { it('bar', () => {}); });", + Some(serde_json::json!([{ "fn": "it", "withinDescribe": "test" }])), + ), + ( + "import { test } from \"vitest\"\ntest(\"shows error\", () => {});", + Some(serde_json::json!([{ "fn": "it" }])), + ), + ("it(\"shows error\", () => {});", Some(serde_json::json!([{ "fn": "test" }]))), + ("describe(\"suite\", () => { it(\"foo\") })", Some(serde_json::json!([{ "fn": "test" }]))), + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }])), + ), + ("test(\"foo\")", Some(serde_json::json!([{ "fn": "it", "withinDescribe": "it" }]))), + ("describe(\"suite\", () => { test(\"foo\") })", None), + ("it(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "it" }]))), + ( + "describe(\"suite\", () => { test(\"foo\") })", + Some(serde_json::json!([{ "withinDescribe": "it" }])), + ), + ("it(\"foo\")", Some(serde_json::json!([{ "withinDescribe": "test" }]))), + ( + "import { it } from \"vitest\"\nit(\"foo\")", + Some(serde_json::json!([{ "withinDescribe": "test" }])), + ), + ( + "describe(\"suite\", () => { it(\"foo\") })", + Some(serde_json::json!([{ "withinDescribe": "test" }])), + ), + ]; + + let fix_vitest = vec![ + // Note: couldn't fixed, because the fixer doesn't support to set the options for the fix cases. + // Todo: this need to fixer support option configuration. + // ("test(\"shows error\", () => {});", "it(\"shows error\", () => {});"), + // ("test.skip(\"shows error\");", "it.skip(\"shows error\");"), + // ("test.only('shows error');", "it.only('shows error');"), + // ( + // "describe('foo', () => { it('bar', () => {}); });", + // "describe('foo', () => { test('bar', () => {}); });" + // ), + // ( + // "import { test } from \"vitest\"\ntest(\"shows error\", () => {});", + // "import { it } from \"vitest\"\nit(\"shows error\", () => {});", + // ), + // ("describe(\"suite\", () => { it(\"foo\") })", "describe(\"suite\", () => { test(\"foo\") })"), + // ("test(\"foo\")", "it(\"foo\")"), + // ("describe(\"suite\", () => { it(\"foo\") })", "describe(\"suite\", () => { test(\"foo\") })"), + // + ("it(\"shows error\", () => {});", "test(\"shows error\", () => {});"), + ( + "describe(\"suite\", () => { test(\"foo\") })", + "describe(\"suite\", () => { it(\"foo\") })", + ), + ( + "describe(\"suite\", () => { test(\"foo\") })", + "describe(\"suite\", () => { it(\"foo\") })", + ), + ("it(\"foo\")", "test(\"foo\")"), + ( + "describe(\"suite\", () => { test(\"foo\") })", + "describe(\"suite\", () => { it(\"foo\") })", + ), + ("it(\"foo\")", "test(\"foo\")"), + // Todo: need to be fixed + // ( + // "import { it } from \"vitest\"\nit(\"foo\")", + // "import { test } from \"vitest\"\ntest(\"foo\")" + // ), + ]; + + pass.extend(pass_vitest); + fail.extend(fail_vitest); + fix.extend(fix_vitest); + + Tester::new(ConsistentTestIt::NAME, pass, fail) + .with_jest_plugin(true) + .with_vitest_plugin(true) + .expect_fix(fix) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/jest/prefer_jest_mocked.rs b/crates/oxc_linter/src/rules/jest/prefer_jest_mocked.rs index 994fc7042e94f..e0e2fbc4cb25a 100644 --- a/crates/oxc_linter/src/rules/jest/prefer_jest_mocked.rs +++ b/crates/oxc_linter/src/rules/jest/prefer_jest_mocked.rs @@ -233,6 +233,7 @@ fn test() { "(jest.mocked(foo)).mockReturnValue(1);", ), // Note: couldn't fix + // Todo: this need to fixer support option configuration. // ( // "(foo).mockReturnValue(1);", // "(jest.mocked(foo)).mockReturnValue(1);", @@ -243,6 +244,7 @@ fn test() { "(jest.mocked(foo)).mockReturnValue(1);", ), // Note: couldn't fix + // Todo: this need to fixer support option configuration. // ( // "(foo as unknown).mockReturnValue(1);", // "(jest.mocked(foo) as unknown).mockReturnValue(1);", diff --git a/crates/oxc_linter/src/snapshots/consistent_test_it.snap b/crates/oxc_linter/src/snapshots/consistent_test_it.snap new file mode 100644 index 0000000000000..d89a5e9cfa00d --- /dev/null +++ b/crates/oxc_linter/src/snapshots/consistent_test_it.snap @@ -0,0 +1,455 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 216 +--- + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:4:17] + 3 │ + 4 │ it("foo") + · ── + 5 │ + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:4:17] + 3 │ + 4 │ testThisThing("foo") + · ───────────── + 5 │ + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ xit("foo") + · ─── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ fit("foo") + · ─── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it.skip("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it.concurrent("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it.only("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it.each([])("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it.each``("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:32] + 1 │ describe.each``("foo", () => { it.each``("bar") }) + · ── + ╰──── + help: Prefer using "test" instead of "it" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:32] + 1 │ describe.each``("foo", () => { test.each``("bar") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:3:21] + 2 │ describe.each()("%s", () => { + 3 │ test("is valid, but should not be", () => {}); + · ──── + 4 │ + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:3:21] + 2 │ describe.only.each()("%s", () => { + 3 │ test("is valid, but should not be", () => {}); + · ──── + 4 │ + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { it("foo") }) + · ── + ╰──── + help: Prefer using "test" instead of "it" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test("foo") + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ xtest("foo") + · ───── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test.skip("foo") + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test.concurrent("foo") + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test.only("foo") + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test.each([])("foo") + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:32] + 1 │ describe.each``("foo", () => { test.each``("bar") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test.each``("foo") + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test.only("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { xtest("foo") }) + · ───── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:4:43] + 3 │ + 4 │ describe("suite", () => { dontTestThis("foo") }); + · ──────────── + 5 │ + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:4:42] + 3 │ + 4 │ context("suite", () => { dontTestThis("foo") }); + · ──────────── + 5 │ + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test.skip("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test.concurrent("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test.only("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { xtest("foo") }) + · ───── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:4:43] + 3 │ + 4 │ describe("suite", () => { dontTestThis("foo") }); + · ──────────── + 5 │ + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:4:38] + 3 │ + 4 │ context("suite", () => { dontTestThis("foo") }); + · ──────────── + 5 │ + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test.skip("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test.concurrent("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { it("foo") }) + · ── + ╰──── + help: Prefer using "test" instead of "it" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test("foo") + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { it("foo") }) + · ── + ╰──── + help: Prefer using "test" instead of "it" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test("shows error", () => {}); + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test.skip("shows error"); + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test.only('shows error'); + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:25] + 1 │ describe('foo', () => { it('bar', () => {}); }); + · ── + ╰──── + help: Prefer using "test" instead of "it" within describe + + ⚠ eslint-plugin-vitest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:2:1] + 1 │ import { test } from "vitest" + 2 │ test("shows error", () => {}); + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it("shows error", () => {}); + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { it("foo") }) + · ── + ╰──── + help: Prefer using "test" instead of "it" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ test("foo") + · ──── + ╰──── + help: Prefer using "it" instead of "test" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { test("foo") }) + · ──── + ╰──── + help: Prefer using "it" instead of "test" within describe + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:1] + 1 │ it("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-vitest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:2:1] + 1 │ import { it } from "vitest" + 2 │ it("foo") + · ── + ╰──── + help: Prefer using "test" instead of "it" + + ⚠ eslint-plugin-jest(consistent-test-it): Enforce `test` and `it` usage conventions + ╭─[consistent_test_it.tsx:1:27] + 1 │ describe("suite", () => { it("foo") }) + · ── + ╰──── + help: Prefer using "test" instead of "it" within describe diff --git a/crates/oxc_linter/src/utils/jest.rs b/crates/oxc_linter/src/utils/jest.rs index aebcf7cc471f8..090bb1092f973 100644 --- a/crates/oxc_linter/src/utils/jest.rs +++ b/crates/oxc_linter/src/utils/jest.rs @@ -19,7 +19,7 @@ pub use crate::utils::jest::parse_jest_fn::{ ParsedGeneralJestFnCall, ParsedJestFnCall as ParsedJestFnCallNew, }; -const JEST_METHOD_NAMES: phf::Set<&'static str> = phf_set![ +pub const JEST_METHOD_NAMES: phf::Set<&'static str> = phf_set![ "afterAll", "afterEach", "beforeAll", 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 e24721abc28e7..24b6a31a0b079 100644 --- a/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs +++ b/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs @@ -368,6 +368,7 @@ pub enum KnownMemberExpressionParentKind { TaggedTemplate, } +#[derive(Debug)] pub struct KnownMemberExpressionProperty<'a> { pub element: MemberExpressionElement<'a>, pub parent: Option<&'a Expression<'a>>, @@ -413,6 +414,7 @@ impl<'a> KnownMemberExpressionProperty<'a> { } } +#[derive(Debug)] pub enum MemberExpressionElement<'a> { Expression(&'a Expression<'a>), IdentName(&'a IdentifierName<'a>), diff --git a/crates/oxc_linter/src/utils/mod.rs b/crates/oxc_linter/src/utils/mod.rs index 1c0410d65b084..51af26ecb4e6b 100644 --- a/crates/oxc_linter/src/utils/mod.rs +++ b/crates/oxc_linter/src/utils/mod.rs @@ -16,7 +16,9 @@ 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 { - matches!(rule_name, "no-disabled-tests") + let jest_rules: [&str; 2] = ["consistent_test_it", "no-disabled-tests"]; + + jest_rules.contains(&rule_name) } pub fn get_test_plugin_name(ctx: &LintContext) -> &'static str {