From 2ddd9b9d48a28627860504b65bdc83b57f67b746 Mon Sep 17 00:00:00 2001 From: shulaoda Date: Mon, 19 Aug 2024 04:10:03 +0800 Subject: [PATCH 1/2] feat(linter/eslint-plugin-vitest): implement no-restricted-vi-methods --- crates/oxc_linter/src/rules.rs | 2 + .../rules/jest/no_restricted_jest_methods.rs | 120 ++--------------- .../rules/vitest/no_restricted_vi_methods.rs | 92 +++++++++++++ .../snapshots/no_restricted_vi_methods.snap | 32 +++++ crates/oxc_linter/src/utils/jest.rs | 3 + .../src/utils/jest/parse_jest_fn.rs | 12 +- crates/oxc_linter/src/utils/mod.rs | 5 +- crates/oxc_linter/src/utils/test.rs | 124 ++++++++++++++++++ 8 files changed, 276 insertions(+), 114 deletions(-) create mode 100644 crates/oxc_linter/src/rules/vitest/no_restricted_vi_methods.rs create mode 100644 crates/oxc_linter/src/snapshots/no_restricted_vi_methods.snap create mode 100644 crates/oxc_linter/src/utils/test.rs diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 3db35d40aba20..4126a04c68eb4 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -456,6 +456,7 @@ mod promise { mod vitest { pub mod no_conditional_tests; pub mod no_import_node_test; + pub mod no_restricted_vi_methods; pub mod prefer_to_be_falsy; pub mod prefer_to_be_truthy; pub mod require_local_test_context_for_concurrent_snapshots; @@ -873,5 +874,6 @@ oxc_macros::declare_all_lint_rules! { vitest::prefer_to_be_falsy, vitest::prefer_to_be_truthy, vitest::no_conditional_tests, + vitest::no_restricted_vi_methods, vitest::require_local_test_context_for_concurrent_snapshots, } diff --git a/crates/oxc_linter/src/rules/jest/no_restricted_jest_methods.rs b/crates/oxc_linter/src/rules/jest/no_restricted_jest_methods.rs index 2ad91f8df99b8..9b5e540f7eddd 100644 --- a/crates/oxc_linter/src/rules/jest/no_restricted_jest_methods.rs +++ b/crates/oxc_linter/src/rules/jest/no_restricted_jest_methods.rs @@ -1,45 +1,16 @@ -use oxc_ast::AstKind; -use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; use rustc_hash::FxHashMap; use crate::{ context::LintContext, rule::Rule, utils::{ - collect_possible_jest_call_node, is_type_of_jest_fn_call, JestFnKind, JestGeneralFnKind, - PossibleJestNode, + collect_possible_jest_call_node, NoRestrictedTestMethods, NoRestrictedTestMethodsConfig, }, }; -fn restricted_jest_method(x0: &str, span1: Span) -> OxcDiagnostic { - OxcDiagnostic::warn("Disallow specific `jest.` methods") - .with_help(format!("Use of `{x0:?}` is disallowed")) - .with_label(span1) -} - -fn restricted_jest_method_with_message(x0: &str, span1: Span) -> OxcDiagnostic { - OxcDiagnostic::warn("Disallow specific `jest.` methods") - .with_help(format!("{x0:?}")) - .with_label(span1) -} - #[derive(Debug, Default, Clone)] -pub struct NoRestrictedJestMethods(Box); - -#[derive(Debug, Default, Clone)] -pub struct NoRestrictedJestMethodsConfig { - restricted_jest_methods: FxHashMap, -} - -impl std::ops::Deref for NoRestrictedJestMethods { - type Target = NoRestrictedJestMethodsConfig; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} +pub struct NoRestrictedJestMethods(Box); declare_oxc_lint!( /// ### What it does @@ -67,91 +38,24 @@ declare_oxc_lint!( style, ); +impl NoRestrictedTestMethods for NoRestrictedJestMethods { + fn restricted_test_methods(&self) -> &FxHashMap { + &self.0.restricted_test_methods + } +} + impl Rule for NoRestrictedJestMethods { fn from_configuration(value: serde_json::Value) -> Self { - let restricted_jest_methods = &value - .get(0) - .and_then(serde_json::Value::as_object) - .and_then(Self::compile_restricted_jest_methods) - .unwrap_or_default(); - - Self(Box::new(NoRestrictedJestMethodsConfig { - restricted_jest_methods: restricted_jest_methods.clone(), - })) + Self(Box::new(Self::get_configuration(&value))) } fn run_once(&self, ctx: &LintContext) { for possible_jest_node in &collect_possible_jest_call_node(ctx) { - self.run(possible_jest_node, ctx); + self.run_test(possible_jest_node, ctx, true); } } } -impl NoRestrictedJestMethods { - fn contains(&self, key: &str) -> bool { - self.restricted_jest_methods.contains_key(key) - } - - fn get_message(&self, name: &str) -> Option { - self.restricted_jest_methods.get(name).cloned() - } - - 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; - }; - - if !is_type_of_jest_fn_call( - call_expr, - possible_jest_node, - ctx, - &[JestFnKind::General(JestGeneralFnKind::Jest)], - ) { - return; - } - - let Some(mem_expr) = call_expr.callee.as_member_expression() else { - return; - }; - let Some(property_name) = mem_expr.static_property_name() else { - return; - }; - let Some((span, _)) = mem_expr.static_property_info() else { - return; - }; - - if self.contains(property_name) { - self.get_message(property_name).map_or_else( - || { - ctx.diagnostic(restricted_jest_method(property_name, span)); - }, - |message| { - if message.trim() == "" { - ctx.diagnostic(restricted_jest_method(property_name, span)); - } else { - ctx.diagnostic(restricted_jest_method_with_message(&message, span)); - } - }, - ); - } - } - - #[allow(clippy::unnecessary_wraps)] - pub fn compile_restricted_jest_methods( - matchers: &serde_json::Map, - ) -> Option> { - Some( - matchers - .iter() - .map(|(key, value)| { - (String::from(key), String::from(value.as_str().unwrap_or_default())) - }) - .collect(), - ) - } -} - #[test] fn test() { use crate::tester::Tester; @@ -186,7 +90,5 @@ fn test() { ), ]; - Tester::new(NoRestrictedJestMethods::NAME, pass, fail) - .with_jest_plugin(true) - .test_and_snapshot(); + Tester::new(NoRestrictedJestMethods::NAME, pass, fail).test_and_snapshot(); } diff --git a/crates/oxc_linter/src/rules/vitest/no_restricted_vi_methods.rs b/crates/oxc_linter/src/rules/vitest/no_restricted_vi_methods.rs new file mode 100644 index 0000000000000..d926e61317447 --- /dev/null +++ b/crates/oxc_linter/src/rules/vitest/no_restricted_vi_methods.rs @@ -0,0 +1,92 @@ +use oxc_macros::declare_oxc_lint; +use rustc_hash::FxHashMap; + +use crate::{ + context::LintContext, + rule::Rule, + utils::{ + collect_possible_jest_call_node, NoRestrictedTestMethods, NoRestrictedTestMethodsConfig, + }, +}; + +#[derive(Debug, Default, Clone)] +pub struct NoRestrictedViMethods(Box); + +declare_oxc_lint!( + /// ### What it does + /// + /// Restrict the use of specific `vi` methods. + /// + /// ### Example + /// ```javascript + /// vi.useFakeTimers(); + /// it('calls the callback after 1 second via advanceTimersByTime', () => { + /// // ... + /// + /// vi.advanceTimersByTime(1000); + /// + /// // ... + /// }); + /// + /// test('plays video', () => { + /// const spy = vi.spyOn(video, 'play'); + /// + /// // ... + /// }); + /// + NoRestrictedViMethods, + style, +); + +impl NoRestrictedTestMethods for NoRestrictedViMethods { + fn restricted_test_methods(&self) -> &FxHashMap { + &self.0.restricted_test_methods + } +} + +impl Rule for NoRestrictedViMethods { + fn from_configuration(value: serde_json::Value) -> Self { + Self(Box::new(Self::get_configuration(&value))) + } + + fn run_once(&self, ctx: &LintContext) { + for possible_vitest_node in &collect_possible_jest_call_node(ctx) { + self.run_test(possible_vitest_node, ctx, false); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("vi", None), + ("vi()", None), + ("vi.mock()", None), + ("expect(a).rejects;", None), + ("expect(a);", None), + ( + " + import { vi } from 'vitest'; + vi; + ", + None, + ), // { "parserOptions": { "sourceType": "module" } } + ]; + + let fail = vec![ + ("vi.fn()", Some(serde_json::json!([{ "fn": null }]))), + ("vi.mock()", Some(serde_json::json!([{ "mock": "Do not use mocks" }]))), + ( + " + import { vi } from 'vitest'; + vi.advanceTimersByTime(); + ", + Some(serde_json::json!([{ "advanceTimersByTime": null }])), + ), // { "parserOptions": { "sourceType": "module" } }, + (r#"vi["fn"]()"#, Some(serde_json::json!([{ "fn": null }]))), + ]; + + Tester::new(NoRestrictedViMethods::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_restricted_vi_methods.snap b/crates/oxc_linter/src/snapshots/no_restricted_vi_methods.snap new file mode 100644 index 0000000000000..dfc93d0f54e4b --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_restricted_vi_methods.snap @@ -0,0 +1,32 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint-plugin-vitest(no-restricted-vi-methods): Disallow specific `vi.` methods + ╭─[no_restricted_vi_methods.tsx:1:4] + 1 │ vi.fn() + · ── + ╰──── + help: Use of `"fn"` is disallowed + + ⚠ eslint-plugin-vitest(no-restricted-vi-methods): Disallow specific `vi.` methods + ╭─[no_restricted_vi_methods.tsx:1:4] + 1 │ vi.mock() + · ──── + ╰──── + help: "Do not use mocks" + + ⚠ eslint-plugin-vitest(no-restricted-vi-methods): Disallow specific `vi.` methods + ╭─[no_restricted_vi_methods.tsx:3:12] + 2 │ import { vi } from 'vitest'; + 3 │ vi.advanceTimersByTime(); + · ─────────────────── + 4 │ + ╰──── + help: Use of `"advanceTimersByTime"` is disallowed + + ⚠ eslint-plugin-vitest(no-restricted-vi-methods): Disallow specific `vi.` methods + ╭─[no_restricted_vi_methods.tsx:1:4] + 1 │ vi["fn"]() + · ──── + ╰──── + help: Use of `"fn"` is disallowed diff --git a/crates/oxc_linter/src/utils/jest.rs b/crates/oxc_linter/src/utils/jest.rs index c4473c2d15161..d6fe78cfa1ebd 100644 --- a/crates/oxc_linter/src/utils/jest.rs +++ b/crates/oxc_linter/src/utils/jest.rs @@ -31,6 +31,7 @@ pub const JEST_METHOD_NAMES: phf::Set<&'static str> = phf_set![ "fit", "it", "jest", + "vi", "test", "xdescribe", "xit", @@ -51,6 +52,7 @@ impl JestFnKind { match name { "expect" => Self::Expect, "expectTypeOf" => Self::ExpectTypeOf, + "vi" => Self::General(JestGeneralFnKind::Vitest), "jest" => Self::General(JestGeneralFnKind::Jest), "describe" | "fdescribe" | "xdescribe" => Self::General(JestGeneralFnKind::Describe), "fit" | "it" | "test" | "xit" | "xtest" => Self::General(JestGeneralFnKind::Test), @@ -75,6 +77,7 @@ pub enum JestGeneralFnKind { Describe, Test, Jest, + Vitest, } /// 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 1af3509fc5a1d..70793e148b07e 100644 --- a/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs +++ b/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs @@ -88,7 +88,8 @@ pub fn parse_jest_fn_call<'a>( return None; } - if matches!(kind, JestFnKind::General(JestGeneralFnKind::Jest)) { + if matches!(kind, JestFnKind::General(JestGeneralFnKind::Jest | JestGeneralFnKind::Vitest)) + { return parse_jest_jest_fn_call(members, name, resolved.local); } @@ -244,12 +245,17 @@ fn parse_jest_jest_fn_call<'a>( name: &'a str, local: &'a str, ) -> Option> { - if !name.to_ascii_lowercase().eq_ignore_ascii_case("jest") { + let lowercase_name = name.to_ascii_lowercase(); + + if !(lowercase_name == "jest" || lowercase_name == "vi") { return None; } + let kind = + if lowercase_name == "jest" { JestGeneralFnKind::Jest } else { JestGeneralFnKind::Vitest }; + return Some(ParsedJestFnCall::GeneralJest(ParsedGeneralJestFnCall { - kind: JestFnKind::General(JestGeneralFnKind::Jest), + kind: JestFnKind::General(kind), members, name: Cow::Borrowed(name), local: Cow::Borrowed(local), diff --git a/crates/oxc_linter/src/utils/mod.rs b/crates/oxc_linter/src/utils/mod.rs index 5a9586f78015b..549cb438306ac 100644 --- a/crates/oxc_linter/src/utils/mod.rs +++ b/crates/oxc_linter/src/utils/mod.rs @@ -5,13 +5,14 @@ mod nextjs; mod promise; mod react; mod react_perf; +mod test; mod tree_shaking; mod unicorn; mod vitest; pub use self::{ - config::*, jest::*, jsdoc::*, nextjs::*, promise::*, react::*, react_perf::*, tree_shaking::*, - unicorn::*, vitest::*, + config::*, jest::*, jsdoc::*, nextjs::*, promise::*, react::*, react_perf::*, test::*, + tree_shaking::*, unicorn::*, vitest::*, }; /// Check if the Jest rule is adapted to Vitest. diff --git a/crates/oxc_linter/src/utils/test.rs b/crates/oxc_linter/src/utils/test.rs new file mode 100644 index 0000000000000..ae4af14d6c4db --- /dev/null +++ b/crates/oxc_linter/src/utils/test.rs @@ -0,0 +1,124 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_span::Span; +use rustc_hash::FxHashMap; + +use crate::LintContext; + +use super::{is_type_of_jest_fn_call, JestFnKind, JestGeneralFnKind, PossibleJestNode}; + +#[derive(Debug, Default, Clone)] +pub struct NoRestrictedTestMethodsConfig { + pub(crate) restricted_test_methods: FxHashMap, +} + +pub trait NoRestrictedTestMethods { + fn restricted_test_methods(&self) -> &FxHashMap; + + fn contains(&self, key: &str) -> bool { + self.restricted_test_methods().contains_key(key) + } + + fn get_message(&self, name: &str) -> Option { + self.restricted_test_methods().get(name).cloned() + } + + fn get_configuration(value: &serde_json::Value) -> NoRestrictedTestMethodsConfig { + let restricted_test_methods = value + .get(0) + .and_then(serde_json::Value::as_object) + .and_then(Self::compile_restricted_jest_methods) + .unwrap_or_default(); + + NoRestrictedTestMethodsConfig { restricted_test_methods: restricted_test_methods.clone() } + } + + fn run_test<'a>( + &self, + possible_test_node: &PossibleJestNode<'a, '_>, + ctx: &LintContext<'a>, + is_jest: bool, + ) { + let node = possible_test_node.node; + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + + let kind = if is_jest { JestGeneralFnKind::Jest } else { JestGeneralFnKind::Vitest }; + + if !is_type_of_jest_fn_call( + call_expr, + possible_test_node, + ctx, + &[JestFnKind::General(kind)], + ) { + return; + } + + let Some(mem_expr) = call_expr.callee.as_member_expression() else { + return; + }; + let Some(property_name) = mem_expr.static_property_name() else { + return; + }; + let Some((span, _)) = mem_expr.static_property_info() else { + return; + }; + + if self.contains(property_name) { + let method_name = if is_jest { "jest" } else { "vi" }; + self.get_message(property_name).map_or_else( + || { + ctx.diagnostic(Self::restricted_test_method(method_name, property_name, span)); + }, + |message| { + if message.trim() == "" { + ctx.diagnostic(Self::restricted_test_method( + method_name, + property_name, + span, + )); + } else { + ctx.diagnostic(Self::restricted_test_method_with_message( + method_name, + &message, + span, + )); + } + }, + ); + } + } + + #[inline] + fn compile_restricted_jest_methods( + matchers: &serde_json::Map, + ) -> Option> { + Some( + matchers + .iter() + .map(|(key, value)| { + (String::from(key), String::from(value.as_str().unwrap_or_default())) + }) + .collect(), + ) + } + + #[inline] + fn restricted_test_method(method_name: &str, x0: &str, span1: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("Disallow specific `{method_name}.` methods")) + .with_help(format!("Use of `{x0:?}` is disallowed")) + .with_label(span1) + } + + #[inline] + fn restricted_test_method_with_message( + method_name: &str, + x0: &str, + span1: Span, + ) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("Disallow specific `{method_name}.` methods")) + .with_help(format!("{x0:?}")) + .with_label(span1) + } +} From 99a7007b981eba57989fffdd0c5f751d9a909ede Mon Sep 17 00:00:00 2001 From: shulaoda Date: Wed, 21 Aug 2024 16:14:13 +0800 Subject: [PATCH 2/2] refactor: follow the suggestion --- crates/oxc_linter/src/rules.rs | 2 - .../rules/jest/no_restricted_jest_methods.rs | 167 ++++++++++++++++-- .../rules/vitest/no_restricted_vi_methods.rs | 92 ---------- .../snapshots/no_restricted_jest_methods.snap | 40 ++++- .../snapshots/no_restricted_vi_methods.snap | 32 ---- crates/oxc_linter/src/utils/mod.rs | 6 +- crates/oxc_linter/src/utils/test.rs | 124 ------------- 7 files changed, 191 insertions(+), 272 deletions(-) delete mode 100644 crates/oxc_linter/src/rules/vitest/no_restricted_vi_methods.rs delete mode 100644 crates/oxc_linter/src/snapshots/no_restricted_vi_methods.snap delete mode 100644 crates/oxc_linter/src/utils/test.rs diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 4126a04c68eb4..3db35d40aba20 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -456,7 +456,6 @@ mod promise { mod vitest { pub mod no_conditional_tests; pub mod no_import_node_test; - pub mod no_restricted_vi_methods; pub mod prefer_to_be_falsy; pub mod prefer_to_be_truthy; pub mod require_local_test_context_for_concurrent_snapshots; @@ -874,6 +873,5 @@ oxc_macros::declare_all_lint_rules! { vitest::prefer_to_be_falsy, vitest::prefer_to_be_truthy, vitest::no_conditional_tests, - vitest::no_restricted_vi_methods, vitest::require_local_test_context_for_concurrent_snapshots, } diff --git a/crates/oxc_linter/src/rules/jest/no_restricted_jest_methods.rs b/crates/oxc_linter/src/rules/jest/no_restricted_jest_methods.rs index 9b5e540f7eddd..de3a0360fefee 100644 --- a/crates/oxc_linter/src/rules/jest/no_restricted_jest_methods.rs +++ b/crates/oxc_linter/src/rules/jest/no_restricted_jest_methods.rs @@ -1,21 +1,50 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; +use oxc_span::Span; use rustc_hash::FxHashMap; use crate::{ context::LintContext, rule::Rule, utils::{ - collect_possible_jest_call_node, NoRestrictedTestMethods, NoRestrictedTestMethodsConfig, + collect_possible_jest_call_node, is_type_of_jest_fn_call, JestFnKind, JestGeneralFnKind, + PossibleJestNode, }, }; +fn restricted_jest_method(method_name: &str, x0: &str, span1: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("Disallow specific `{method_name}.` methods")) + .with_help(format!("Use of `{x0:?}` is disallowed")) + .with_label(span1) +} + +fn restricted_jest_method_with_message(method_name: &str, x0: &str, span1: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("Disallow specific `{method_name}.` methods")) + .with_help(format!("{x0:?}")) + .with_label(span1) +} + +#[derive(Debug, Default, Clone)] +pub struct NoRestrictedJestMethods(Box); + #[derive(Debug, Default, Clone)] -pub struct NoRestrictedJestMethods(Box); +pub struct NoRestrictedJestMethodsConfig { + restricted_jest_methods: FxHashMap, +} + +impl std::ops::Deref for NoRestrictedJestMethods { + type Target = NoRestrictedJestMethodsConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} declare_oxc_lint!( /// ### What it does /// - /// Restrict the use of specific `jest` methods. + /// Restrict the use of specific `jest` and `vi` methods. /// /// ### Example /// ```javascript @@ -38,29 +67,105 @@ declare_oxc_lint!( style, ); -impl NoRestrictedTestMethods for NoRestrictedJestMethods { - fn restricted_test_methods(&self) -> &FxHashMap { - &self.0.restricted_test_methods - } -} - impl Rule for NoRestrictedJestMethods { fn from_configuration(value: serde_json::Value) -> Self { - Self(Box::new(Self::get_configuration(&value))) + let restricted_jest_methods = &value + .get(0) + .and_then(serde_json::Value::as_object) + .and_then(Self::compile_restricted_jest_methods) + .unwrap_or_default(); + + Self(Box::new(NoRestrictedJestMethodsConfig { + restricted_jest_methods: restricted_jest_methods.clone(), + })) } fn run_once(&self, ctx: &LintContext) { for possible_jest_node in &collect_possible_jest_call_node(ctx) { - self.run_test(possible_jest_node, ctx, true); + self.run(possible_jest_node, ctx); + } + } +} + +impl NoRestrictedJestMethods { + fn contains(&self, key: &str) -> bool { + self.restricted_jest_methods.contains_key(key) + } + + fn get_message(&self, name: &str) -> Option { + self.restricted_jest_methods.get(name).cloned() + } + + 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; + }; + + if !is_type_of_jest_fn_call( + call_expr, + possible_jest_node, + ctx, + &[ + JestFnKind::General(JestGeneralFnKind::Jest), + JestFnKind::General(JestGeneralFnKind::Vitest), + ], + ) { + return; + } + + let Some(mem_expr) = call_expr.callee.as_member_expression() else { + return; + }; + let Some(property_name) = mem_expr.static_property_name() else { + return; + }; + let Some((span, _)) = mem_expr.static_property_info() else { + return; + }; + + if self.contains(property_name) { + let method_name = + mem_expr.object().get_identifier_reference().map_or("jest", |id| id.name.as_str()); + self.get_message(property_name).map_or_else( + || { + ctx.diagnostic(restricted_jest_method(method_name, property_name, span)); + }, + |message| { + if message.trim() == "" { + ctx.diagnostic(restricted_jest_method(method_name, property_name, span)); + } else { + ctx.diagnostic(restricted_jest_method_with_message( + method_name, + &message, + span, + )); + } + }, + ); } } + + #[allow(clippy::unnecessary_wraps)] + pub fn compile_restricted_jest_methods( + matchers: &serde_json::Map, + ) -> Option> { + Some( + matchers + .iter() + .map(|(key, value)| { + (String::from(key), String::from(value.as_str().unwrap_or_default())) + }) + .collect(), + ) + } } #[test] fn test() { use crate::tester::Tester; - let pass = vec![ + let mut pass = vec![ ("jest", None), ("jest()", None), ("jest.mock()", None), @@ -76,7 +181,7 @@ fn test() { ), ]; - let fail = vec![ + let mut fail = vec![ ("jest.fn()", Some(serde_json::json!([{ "fn": null }]))), ("jest[\"fn\"]()", Some(serde_json::json!([{ "fn": null }]))), ("jest.mock()", Some(serde_json::json!([{ "mock": "Do not use mocks" }]))), @@ -90,5 +195,39 @@ fn test() { ), ]; - Tester::new(NoRestrictedJestMethods::NAME, pass, fail).test_and_snapshot(); + let pass_vitest = vec![ + ("vi", None), + ("vi()", None), + ("vi.mock()", None), + ("expect(a).rejects;", None), + ("expect(a);", None), + ( + " + import { vi } from 'vitest'; + vi; + ", + None, + ), // { "parserOptions": { "sourceType": "module" } } + ]; + + let fail_vitest = vec![ + ("vi.fn()", Some(serde_json::json!([{ "fn": null }]))), + ("vi.mock()", Some(serde_json::json!([{ "mock": "Do not use mocks" }]))), + ( + " + import { vi } from 'vitest'; + vi.advanceTimersByTime(); + ", + Some(serde_json::json!([{ "advanceTimersByTime": null }])), + ), // { "parserOptions": { "sourceType": "module" } }, + (r#"vi["fn"]()"#, Some(serde_json::json!([{ "fn": null }]))), + ]; + + pass.extend(pass_vitest); + fail.extend(fail_vitest); + + Tester::new(NoRestrictedJestMethods::NAME, pass, fail) + .with_jest_plugin(true) + .with_vitest_plugin(true) + .test_and_snapshot(); } diff --git a/crates/oxc_linter/src/rules/vitest/no_restricted_vi_methods.rs b/crates/oxc_linter/src/rules/vitest/no_restricted_vi_methods.rs deleted file mode 100644 index d926e61317447..0000000000000 --- a/crates/oxc_linter/src/rules/vitest/no_restricted_vi_methods.rs +++ /dev/null @@ -1,92 +0,0 @@ -use oxc_macros::declare_oxc_lint; -use rustc_hash::FxHashMap; - -use crate::{ - context::LintContext, - rule::Rule, - utils::{ - collect_possible_jest_call_node, NoRestrictedTestMethods, NoRestrictedTestMethodsConfig, - }, -}; - -#[derive(Debug, Default, Clone)] -pub struct NoRestrictedViMethods(Box); - -declare_oxc_lint!( - /// ### What it does - /// - /// Restrict the use of specific `vi` methods. - /// - /// ### Example - /// ```javascript - /// vi.useFakeTimers(); - /// it('calls the callback after 1 second via advanceTimersByTime', () => { - /// // ... - /// - /// vi.advanceTimersByTime(1000); - /// - /// // ... - /// }); - /// - /// test('plays video', () => { - /// const spy = vi.spyOn(video, 'play'); - /// - /// // ... - /// }); - /// - NoRestrictedViMethods, - style, -); - -impl NoRestrictedTestMethods for NoRestrictedViMethods { - fn restricted_test_methods(&self) -> &FxHashMap { - &self.0.restricted_test_methods - } -} - -impl Rule for NoRestrictedViMethods { - fn from_configuration(value: serde_json::Value) -> Self { - Self(Box::new(Self::get_configuration(&value))) - } - - fn run_once(&self, ctx: &LintContext) { - for possible_vitest_node in &collect_possible_jest_call_node(ctx) { - self.run_test(possible_vitest_node, ctx, false); - } - } -} - -#[test] -fn test() { - use crate::tester::Tester; - - let pass = vec![ - ("vi", None), - ("vi()", None), - ("vi.mock()", None), - ("expect(a).rejects;", None), - ("expect(a);", None), - ( - " - import { vi } from 'vitest'; - vi; - ", - None, - ), // { "parserOptions": { "sourceType": "module" } } - ]; - - let fail = vec![ - ("vi.fn()", Some(serde_json::json!([{ "fn": null }]))), - ("vi.mock()", Some(serde_json::json!([{ "mock": "Do not use mocks" }]))), - ( - " - import { vi } from 'vitest'; - vi.advanceTimersByTime(); - ", - Some(serde_json::json!([{ "advanceTimersByTime": null }])), - ), // { "parserOptions": { "sourceType": "module" } }, - (r#"vi["fn"]()"#, Some(serde_json::json!([{ "fn": null }]))), - ]; - - Tester::new(NoRestrictedViMethods::NAME, pass, fail).test_and_snapshot(); -} diff --git a/crates/oxc_linter/src/snapshots/no_restricted_jest_methods.snap b/crates/oxc_linter/src/snapshots/no_restricted_jest_methods.snap index c6d2ecdef6ca8..504e30cc3140b 100644 --- a/crates/oxc_linter/src/snapshots/no_restricted_jest_methods.snap +++ b/crates/oxc_linter/src/snapshots/no_restricted_jest_methods.snap @@ -1,35 +1,35 @@ --- source: crates/oxc_linter/src/tester.rs --- - ⚠ eslint-plugin-jest(no-restricted-jest-methods): Disallow specific `jest.` methods + ⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `jest.` methods ╭─[no_restricted_jest_methods.tsx:1:6] 1 │ jest.fn() · ── ╰──── help: Use of `"fn"` is disallowed - ⚠ eslint-plugin-jest(no-restricted-jest-methods): Disallow specific `jest.` methods + ⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `jest.` methods ╭─[no_restricted_jest_methods.tsx:1:6] 1 │ jest["fn"]() · ──── ╰──── help: Use of `"fn"` is disallowed - ⚠ eslint-plugin-jest(no-restricted-jest-methods): Disallow specific `jest.` methods + ⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `jest.` methods ╭─[no_restricted_jest_methods.tsx:1:6] 1 │ jest.mock() · ──── ╰──── help: "Do not use mocks" - ⚠ eslint-plugin-jest(no-restricted-jest-methods): Disallow specific `jest.` methods + ⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `jest.` methods ╭─[no_restricted_jest_methods.tsx:1:6] 1 │ jest["mock"]() · ────── ╰──── help: "Do not use mocks" - ⚠ eslint-plugin-jest(no-restricted-jest-methods): Disallow specific `jest.` methods + ⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `jest.` methods ╭─[no_restricted_jest_methods.tsx:3:22] 2 │ import { jest } from '@jest/globals'; 3 │ jest.advanceTimersByTime(); @@ -37,3 +37,33 @@ source: crates/oxc_linter/src/tester.rs 4 │ ╰──── help: Use of `"advanceTimersByTime"` is disallowed + + ⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `vi.` methods + ╭─[no_restricted_jest_methods.tsx:1:4] + 1 │ vi.fn() + · ── + ╰──── + help: Use of `"fn"` is disallowed + + ⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `vi.` methods + ╭─[no_restricted_jest_methods.tsx:1:4] + 1 │ vi.mock() + · ──── + ╰──── + help: "Do not use mocks" + + ⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `vi.` methods + ╭─[no_restricted_jest_methods.tsx:3:12] + 2 │ import { vi } from 'vitest'; + 3 │ vi.advanceTimersByTime(); + · ─────────────────── + 4 │ + ╰──── + help: Use of `"advanceTimersByTime"` is disallowed + + ⚠ eslint-plugin-vitest(no-restricted-jest-methods): Disallow specific `vi.` methods + ╭─[no_restricted_jest_methods.tsx:1:4] + 1 │ vi["fn"]() + · ──── + ╰──── + help: Use of `"fn"` is disallowed diff --git a/crates/oxc_linter/src/snapshots/no_restricted_vi_methods.snap b/crates/oxc_linter/src/snapshots/no_restricted_vi_methods.snap deleted file mode 100644 index dfc93d0f54e4b..0000000000000 --- a/crates/oxc_linter/src/snapshots/no_restricted_vi_methods.snap +++ /dev/null @@ -1,32 +0,0 @@ ---- -source: crates/oxc_linter/src/tester.rs ---- - ⚠ eslint-plugin-vitest(no-restricted-vi-methods): Disallow specific `vi.` methods - ╭─[no_restricted_vi_methods.tsx:1:4] - 1 │ vi.fn() - · ── - ╰──── - help: Use of `"fn"` is disallowed - - ⚠ eslint-plugin-vitest(no-restricted-vi-methods): Disallow specific `vi.` methods - ╭─[no_restricted_vi_methods.tsx:1:4] - 1 │ vi.mock() - · ──── - ╰──── - help: "Do not use mocks" - - ⚠ eslint-plugin-vitest(no-restricted-vi-methods): Disallow specific `vi.` methods - ╭─[no_restricted_vi_methods.tsx:3:12] - 2 │ import { vi } from 'vitest'; - 3 │ vi.advanceTimersByTime(); - · ─────────────────── - 4 │ - ╰──── - help: Use of `"advanceTimersByTime"` is disallowed - - ⚠ eslint-plugin-vitest(no-restricted-vi-methods): Disallow specific `vi.` methods - ╭─[no_restricted_vi_methods.tsx:1:4] - 1 │ vi["fn"]() - · ──── - ╰──── - help: Use of `"fn"` is disallowed diff --git a/crates/oxc_linter/src/utils/mod.rs b/crates/oxc_linter/src/utils/mod.rs index 549cb438306ac..049ddcef23ea7 100644 --- a/crates/oxc_linter/src/utils/mod.rs +++ b/crates/oxc_linter/src/utils/mod.rs @@ -5,14 +5,13 @@ mod nextjs; mod promise; mod react; mod react_perf; -mod test; mod tree_shaking; mod unicorn; mod vitest; pub use self::{ - config::*, jest::*, jsdoc::*, nextjs::*, promise::*, react::*, react_perf::*, test::*, - tree_shaking::*, unicorn::*, vitest::*, + config::*, jest::*, jsdoc::*, nextjs::*, promise::*, react::*, react_perf::*, tree_shaking::*, + unicorn::*, vitest::*, }; /// Check if the Jest rule is adapted to Vitest. @@ -28,6 +27,7 @@ pub fn is_jest_rule_adapted_to_vitest(rule_name: &str) -> bool { "no-disabled-tests", "no-focused-tests", "no-identical-title", + "no-restricted-jest-methods", "no-test-prefixes", "prefer-hooks-in-order", "valid-describe-callback", diff --git a/crates/oxc_linter/src/utils/test.rs b/crates/oxc_linter/src/utils/test.rs deleted file mode 100644 index ae4af14d6c4db..0000000000000 --- a/crates/oxc_linter/src/utils/test.rs +++ /dev/null @@ -1,124 +0,0 @@ -use oxc_ast::AstKind; -use oxc_diagnostics::OxcDiagnostic; -use oxc_span::Span; -use rustc_hash::FxHashMap; - -use crate::LintContext; - -use super::{is_type_of_jest_fn_call, JestFnKind, JestGeneralFnKind, PossibleJestNode}; - -#[derive(Debug, Default, Clone)] -pub struct NoRestrictedTestMethodsConfig { - pub(crate) restricted_test_methods: FxHashMap, -} - -pub trait NoRestrictedTestMethods { - fn restricted_test_methods(&self) -> &FxHashMap; - - fn contains(&self, key: &str) -> bool { - self.restricted_test_methods().contains_key(key) - } - - fn get_message(&self, name: &str) -> Option { - self.restricted_test_methods().get(name).cloned() - } - - fn get_configuration(value: &serde_json::Value) -> NoRestrictedTestMethodsConfig { - let restricted_test_methods = value - .get(0) - .and_then(serde_json::Value::as_object) - .and_then(Self::compile_restricted_jest_methods) - .unwrap_or_default(); - - NoRestrictedTestMethodsConfig { restricted_test_methods: restricted_test_methods.clone() } - } - - fn run_test<'a>( - &self, - possible_test_node: &PossibleJestNode<'a, '_>, - ctx: &LintContext<'a>, - is_jest: bool, - ) { - let node = possible_test_node.node; - let AstKind::CallExpression(call_expr) = node.kind() else { - return; - }; - - let kind = if is_jest { JestGeneralFnKind::Jest } else { JestGeneralFnKind::Vitest }; - - if !is_type_of_jest_fn_call( - call_expr, - possible_test_node, - ctx, - &[JestFnKind::General(kind)], - ) { - return; - } - - let Some(mem_expr) = call_expr.callee.as_member_expression() else { - return; - }; - let Some(property_name) = mem_expr.static_property_name() else { - return; - }; - let Some((span, _)) = mem_expr.static_property_info() else { - return; - }; - - if self.contains(property_name) { - let method_name = if is_jest { "jest" } else { "vi" }; - self.get_message(property_name).map_or_else( - || { - ctx.diagnostic(Self::restricted_test_method(method_name, property_name, span)); - }, - |message| { - if message.trim() == "" { - ctx.diagnostic(Self::restricted_test_method( - method_name, - property_name, - span, - )); - } else { - ctx.diagnostic(Self::restricted_test_method_with_message( - method_name, - &message, - span, - )); - } - }, - ); - } - } - - #[inline] - fn compile_restricted_jest_methods( - matchers: &serde_json::Map, - ) -> Option> { - Some( - matchers - .iter() - .map(|(key, value)| { - (String::from(key), String::from(value.as_str().unwrap_or_default())) - }) - .collect(), - ) - } - - #[inline] - fn restricted_test_method(method_name: &str, x0: &str, span1: Span) -> OxcDiagnostic { - OxcDiagnostic::warn(format!("Disallow specific `{method_name}.` methods")) - .with_help(format!("Use of `{x0:?}` is disallowed")) - .with_label(span1) - } - - #[inline] - fn restricted_test_method_with_message( - method_name: &str, - x0: &str, - span1: Span, - ) -> OxcDiagnostic { - OxcDiagnostic::warn(format!("Disallow specific `{method_name}.` methods")) - .with_help(format!("{x0:?}")) - .with_label(span1) - } -}