From b248c15243ab02b5351b484ff48ff729073fb4e2 Mon Sep 17 00:00:00 2001 From: Said Atrahouch Date: Fri, 26 Dec 2025 14:57:45 +0100 Subject: [PATCH 1/9] feat(linter/eslint-plugin-vitest): Implement consistent-vitest-vi rule --- ...rc_vitest_replace__foo.test.js@oxlint.snap | 2 +- .../src/generated/rule_runner_impls.rs | 6 + crates/oxc_linter/src/rules.rs | 2 + .../src/rules/vitest/consistent_vitest_vi.rs | 274 ++++++++++++++++++ .../vitest_consistent_vitest_vi.snap | 48 +++ crates/oxc_linter/src/utils/jest.rs | 2 +- .../src/utils/jest/parse_jest_fn.rs | 2 +- 7 files changed, 333 insertions(+), 3 deletions(-) create mode 100644 crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs create mode 100644 crates/oxc_linter/src/snapshots/vitest_consistent_vitest_vi.snap diff --git a/apps/oxlint/src/snapshots/_--vitest-plugin -c fixtures__eslintrc_vitest_replace__eslintrc.json fixtures__eslintrc_vitest_replace__foo.test.js@oxlint.snap b/apps/oxlint/src/snapshots/_--vitest-plugin -c fixtures__eslintrc_vitest_replace__eslintrc.json fixtures__eslintrc_vitest_replace__foo.test.js@oxlint.snap index 2e676b3e1bc8e..a0b38242647d6 100644 --- a/apps/oxlint/src/snapshots/_--vitest-plugin -c fixtures__eslintrc_vitest_replace__eslintrc.json fixtures__eslintrc_vitest_replace__foo.test.js@oxlint.snap +++ b/apps/oxlint/src/snapshots/_--vitest-plugin -c fixtures__eslintrc_vitest_replace__eslintrc.json fixtures__eslintrc_vitest_replace__foo.test.js@oxlint.snap @@ -23,7 +23,7 @@ working directory: help: Remove the appending `.skip` Found 1 warning and 1 error. -Finished in ms on 1 file with 102 rules using 1 threads. +Finished in ms on 1 file with 103 rules using 1 threads. ---------- CLI result: LintFoundErrors ---------- diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 22cc2d7791d2a..d8b8a83f09ffd 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -3982,6 +3982,12 @@ impl RuleRunner for crate::rules::vitest::consistent_test_filename::ConsistentTe const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; } +impl RuleRunner for crate::rules::vitest::consistent_vitest_vi::ConsistentVitestVi { + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::CallExpression, AstType::ImportDeclaration])); + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; +} + impl RuleRunner for crate::rules::vitest::no_conditional_tests::NoConditionalTests { const NODE_TYPES: Option<&AstTypesBitset> = None; const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnJestNode; diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 9217d61935025..2b21d8a096078 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -664,6 +664,7 @@ pub(crate) mod promise { pub(crate) mod vitest { pub mod consistent_test_filename; + pub mod consistent_vitest_vi; pub mod no_conditional_tests; pub mod no_import_node_test; pub mod prefer_called_times; @@ -1322,6 +1323,7 @@ oxc_macros::declare_all_lint_rules! { unicorn::text_encoding_identifier_case, unicorn::throw_new_error, vitest::consistent_test_filename, + vitest::consistent_vitest_vi, vitest::no_conditional_tests, vitest::no_import_node_test, vitest::prefer_called_times, diff --git a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs new file mode 100644 index 0000000000000..d6563d868241e --- /dev/null +++ b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs @@ -0,0 +1,274 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::{CompactStr, GetSpan, Span}; +use schemars::JsonSchema; +use serde::Deserialize; + +use crate::{ + AstNode, + context::LintContext, + rule::Rule, + utils::{JestFnKind, JestGeneralFnKind, PossibleJestNode, parse_general_jest_fn_call}, +}; + +fn consistent_vitest_vi_diagnostic(span: Span, fn_value: &VitestFnName) -> OxcDiagnostic { + let message = format!( + "Prefer using `{}` instead of `{}`.", + fn_value.get_string(), + fn_value.get_opposite_accessor() + ); + + OxcDiagnostic::warn("The vitest function accessor used is not allowed") + .with_help(message) + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct ConsistentVitestVi(Box); + +impl std::ops::Deref for ConsistentVitestVi { + type Target = ConsistentVitestConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default, JsonSchema, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum VitestFnName { + #[default] + Vi, + Vitest, +} + +impl VitestFnName { + fn get_opposite_accessor(&self) -> CompactStr { + match self { + VitestFnName::Vi => CompactStr::new("vitest"), + VitestFnName::Vitest => CompactStr::new("vi"), + } + } + + fn get_string(&self) -> CompactStr { + match self { + VitestFnName::Vi => CompactStr::new("vi"), + VitestFnName::Vitest => CompactStr::new("vitest"), + } + } +} + +#[derive(Debug, Default, Clone, JsonSchema, Deserialize)] +#[serde(rename_all = "lowercase")] +pub struct ConsistentVitestConfig { + /// Decides whether to prefer vitest function accessor + #[serde(rename = "fn", default)] + function: VitestFnName, +} + +// See for documentation details. +declare_oxc_lint!( + /// ### What it does + /// + /// This rule triggers an error when a not expected vitest accessor is used. + /// + /// ### Why is this bad? + /// + /// Not having a consistent vitest accessor can lead to confusion on why + /// on some contexts `vi` is used, and on other `vitest` is used. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// vitest.mock('./src/calculator.ts', { spy: true }) + /// + /// vi.stubEnv('NODE_ENV', 'production') + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// vi.mock('./src/calculator.ts', { spy: true }) + /// + /// vi.stubEnv('NODE_ENV', 'production') + /// ``` + ConsistentVitestVi, + vitest, + correctness, + fix, + config = ConsistentVitestConfig, +); + +impl Rule for ConsistentVitestVi { + fn from_configuration(value: serde_json::Value) -> Self { + let config: ConsistentVitestConfig = + value.get(0).and_then(|v| serde_json::from_value(v.clone()).ok()).unwrap_or_default(); + + Self(Box::new(config)) + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::ImportDeclaration(import) => { + if import.source.value != "vitest" { + return; + } + + let Some(vitest_import) = import.specifiers.as_ref().and_then(|specs| { + specs.iter().find(|spec| spec.name() == self.function.get_opposite_accessor()) + }) else { + return; + }; + + ctx.diagnostic_with_fix( + consistent_vitest_vi_diagnostic(vitest_import.span(), &self.function), + |fixer| { + let mut specifiers_without_opposite_accessor = import + .specifiers + .as_ref() + .map(|specs| { + specs + .iter() + .map(|spec| CompactStr::from(spec.name())) + .filter(|spec_name| { + *spec_name != self.function.get_opposite_accessor() + }) + .collect::>() + }) + .unwrap_or(vec![]); + + if specifiers_without_opposite_accessor.is_empty() { + match self.function { + VitestFnName::Vi => fixer.replace(vitest_import.local().span, "vi"), + VitestFnName::Vitest => { + fixer.replace(vitest_import.local().span, "vitest") + } + } + } else { + if !specifiers_without_opposite_accessor + .contains(&self.function.get_string()) + { + specifiers_without_opposite_accessor + .push(self.function.get_string()); + } + + let import_text = specifiers_without_opposite_accessor.join(", "); + + let Some(first_specifier) = + import.specifiers.as_ref().and_then(|specs| specs.first()) + else { + return fixer.noop(); + }; + + let Some(last_specifier) = + import.specifiers.as_ref().and_then(|specs| specs.last()) + else { + return fixer.noop(); + }; + + let specifiers_span = Span::new( + first_specifier.local().span.start, + last_specifier.local().span.end, + ); + + fixer.replace(specifiers_span, import_text) + } + }, + ); + } + AstKind::CallExpression(call_expr) => { + let Some(vitest_fn) = parse_general_jest_fn_call( + call_expr, + &PossibleJestNode { node, original: None }, + ctx, + ) else { + return; + }; + + if vitest_fn.kind != JestFnKind::General(JestGeneralFnKind::Vitest) { + return; + } + + if vitest_fn.name == self.function.get_opposite_accessor() { + let Some(member_expression) = call_expr.callee.as_member_expression() else { + return; + }; + + ctx.diagnostic_with_fix( + consistent_vitest_vi_diagnostic( + member_expression.object().span(), + &self.function, + ), + |fixer| { + fixer.replace( + member_expression.object().span(), + self.function.get_string(), + ) + }, + ); + } + } + _ => {} + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + (r#"import { expect, it } from "vitest";"#, None), + (r#"import { vi } from "vitest";"#, None), + (r#"import { vitest } from "vitest";"#, Some(serde_json::json!([{ "fn": "vitest" }]))), + ( + r#"import { vi } from "vitest"; + vi.stubEnv("NODE_ENV", "production");"#, + None, + ), + (r#"vi.stubEnv("NODE_ENV", "production");"#, None), + ]; + + let fail = vec![ + (r#"import { vitest } from "vitest";"#, None), + (r#"import { expect, vi, vitest } from "vitest";"#, None), + ( + r#"import { vitest } from "vitest"; + vitest.stubEnv("NODE_ENV", "production");"#, + None, + ), + ( + r#"vi.stubEnv("NODE_ENV", "production"); + vi.clearAllMocks();"#, + Some(serde_json::json!([{ "fn": "vitest" }])), + ), + ]; + + let fix = vec![ + (r#"import { vitest } from "vitest";"#, r#"import { vi } from "vitest";"#, None), // WORKING + ( + r#"import { expect, vi, vitest } from "vitest";"#, + r#"import { expect, vi } from "vitest";"#, + None, + ), + ( + r#"import { vitest } from "vitest"; + vitest.stubEnv("NODE_ENV", "production");"#, + r#"import { vi } from "vitest"; + vi.stubEnv("NODE_ENV", "production");"#, + None, + ), + ( + r#"vi.stubEnv("NODE_ENV", "production"); + vi.clearAllMocks();"#, + r#"vitest.stubEnv("NODE_ENV", "production"); + vitest.clearAllMocks();"#, + Some(serde_json::json!([{ "fn": "vitest" }])), + ), + ]; + Tester::new(ConsistentVitestVi::NAME, ConsistentVitestVi::PLUGIN, pass, fail) + .expect_fix(fix) + .with_vitest_plugin(true) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/vitest_consistent_vitest_vi.snap b/crates/oxc_linter/src/snapshots/vitest_consistent_vitest_vi.snap new file mode 100644 index 0000000000000..5ab9d29e279df --- /dev/null +++ b/crates/oxc_linter/src/snapshots/vitest_consistent_vitest_vi.snap @@ -0,0 +1,48 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint-plugin-vitest(consistent-vitest-vi): The vitest function accessor used is not allowed + ╭─[consistent_vitest_vi.tsx:1:10] + 1 │ import { vitest } from "vitest"; + · ────── + ╰──── + help: Prefer using `vi` instead of `vitest`. + + ⚠ eslint-plugin-vitest(consistent-vitest-vi): The vitest function accessor used is not allowed + ╭─[consistent_vitest_vi.tsx:1:22] + 1 │ import { expect, vi, vitest } from "vitest"; + · ────── + ╰──── + help: Prefer using `vi` instead of `vitest`. + + ⚠ eslint-plugin-vitest(consistent-vitest-vi): The vitest function accessor used is not allowed + ╭─[consistent_vitest_vi.tsx:1:10] + 1 │ import { vitest } from "vitest"; + · ────── + 2 │ vitest.stubEnv("NODE_ENV", "production"); + ╰──── + help: Prefer using `vi` instead of `vitest`. + + ⚠ eslint-plugin-vitest(consistent-vitest-vi): The vitest function accessor used is not allowed + ╭─[consistent_vitest_vi.tsx:2:4] + 1 │ import { vitest } from "vitest"; + 2 │ vitest.stubEnv("NODE_ENV", "production"); + · ────── + ╰──── + help: Prefer using `vi` instead of `vitest`. + + ⚠ eslint-plugin-vitest(consistent-vitest-vi): The vitest function accessor used is not allowed + ╭─[consistent_vitest_vi.tsx:1:1] + 1 │ vi.stubEnv("NODE_ENV", "production"); + · ── + 2 │ vi.clearAllMocks(); + ╰──── + help: Prefer using `vitest` instead of `vi`. + + ⚠ eslint-plugin-vitest(consistent-vitest-vi): The vitest function accessor used is not allowed + ╭─[consistent_vitest_vi.tsx:2:4] + 1 │ vi.stubEnv("NODE_ENV", "production"); + 2 │ vi.clearAllMocks(); + · ── + ╰──── + help: Prefer using `vitest` instead of `vi`. diff --git a/crates/oxc_linter/src/utils/jest.rs b/crates/oxc_linter/src/utils/jest.rs index 2834abef172e7..af2f7e603414f 100644 --- a/crates/oxc_linter/src/utils/jest.rs +++ b/crates/oxc_linter/src/utils/jest.rs @@ -54,7 +54,7 @@ impl JestFnKind { match name { "expect" => Self::Expect, "expectTypeOf" => Self::ExpectTypeOf, - "vi" => Self::General(JestGeneralFnKind::Vitest), + "vi" | "vitest" => Self::General(JestGeneralFnKind::Vitest), "bench" => Self::General(JestGeneralFnKind::Bench), "jest" => Self::General(JestGeneralFnKind::Jest), "describe" | "fdescribe" | "xdescribe" => Self::General(JestGeneralFnKind::Describe), 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 39513c9d94154..264174ff3208b 100644 --- a/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs +++ b/crates/oxc_linter/src/utils/jest/parse_jest_fn.rs @@ -274,7 +274,7 @@ fn parse_jest_jest_fn_call<'a>( ) -> Option> { let lowercase_name = name.cow_to_ascii_lowercase(); - if !(lowercase_name == "jest" || lowercase_name == "vi") { + if !(lowercase_name == "jest" || lowercase_name == "vi" || lowercase_name == "vitest") { return None; } From 7e8232defad8bb79b4c204260c588a7769d7673e Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Mon, 29 Dec 2025 11:44:47 +0000 Subject: [PATCH 2/9] u --- .../src/rules/vitest/consistent_vitest_vi.rs | 71 +++++++++---------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs index d6563d868241e..51849105faa15 100644 --- a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs +++ b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs @@ -1,7 +1,9 @@ +use std::borrow::Cow; + use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::{CompactStr, GetSpan, Span}; +use oxc_span::{GetSpan, Span}; use schemars::JsonSchema; use serde::Deserialize; @@ -13,14 +15,12 @@ use crate::{ }; fn consistent_vitest_vi_diagnostic(span: Span, fn_value: &VitestFnName) -> OxcDiagnostic { - let message = format!( - "Prefer using `{}` instead of `{}`.", - fn_value.get_string(), - fn_value.get_opposite_accessor() - ); - OxcDiagnostic::warn("The vitest function accessor used is not allowed") - .with_help(message) + .with_help(format!( + "Prefer using `{}` instead of `{}`.", + fn_value.as_str(), + fn_value.not().as_str() + )) .with_label(span) } @@ -44,17 +44,17 @@ pub enum VitestFnName { } impl VitestFnName { - fn get_opposite_accessor(&self) -> CompactStr { + fn not(&self) -> Self { match self { - VitestFnName::Vi => CompactStr::new("vitest"), - VitestFnName::Vitest => CompactStr::new("vi"), + VitestFnName::Vi => VitestFnName::Vitest, + VitestFnName::Vitest => VitestFnName::Vi, } } - fn get_string(&self) -> CompactStr { + fn as_str(&self) -> &'static str { match self { - VitestFnName::Vi => CompactStr::new("vi"), - VitestFnName::Vitest => CompactStr::new("vitest"), + VitestFnName::Vi => "vi", + VitestFnName::Vitest => "vitest", } } } @@ -67,7 +67,6 @@ pub struct ConsistentVitestConfig { function: VitestFnName, } -// See for documentation details. declare_oxc_lint!( /// ### What it does /// @@ -95,7 +94,7 @@ declare_oxc_lint!( /// ``` ConsistentVitestVi, vitest, - correctness, + style, fix, config = ConsistentVitestConfig, ); @@ -115,42 +114,39 @@ impl Rule for ConsistentVitestVi { return; } - let Some(vitest_import) = import.specifiers.as_ref().and_then(|specs| { - specs.iter().find(|spec| spec.name() == self.function.get_opposite_accessor()) - }) else { + let opposite = self.function.not(); + let Some(vitest_import) = import + .specifiers + .as_ref() + .and_then(|specs| specs.iter().find(|spec| spec.name() == opposite.as_str())) + else { return; }; ctx.diagnostic_with_fix( consistent_vitest_vi_diagnostic(vitest_import.span(), &self.function), |fixer| { - let mut specifiers_without_opposite_accessor = import + let mut specifiers_without_opposite_accessor: Vec> = import .specifiers .as_ref() .map(|specs| { specs .iter() - .map(|spec| CompactStr::from(spec.name())) - .filter(|spec_name| { - *spec_name != self.function.get_opposite_accessor() - }) - .collect::>() + .filter(|spec| spec.name() != opposite.as_str()) + .map(|spec| spec.name()) + .collect() }) - .unwrap_or(vec![]); + .unwrap_or_default(); if specifiers_without_opposite_accessor.is_empty() { - match self.function { - VitestFnName::Vi => fixer.replace(vitest_import.local().span, "vi"), - VitestFnName::Vitest => { - fixer.replace(vitest_import.local().span, "vitest") - } - } + fixer.replace(vitest_import.local().span, self.function.as_str()) } else { if !specifiers_without_opposite_accessor - .contains(&self.function.get_string()) + .iter() + .any(|s| s.as_ref() == self.function.as_str()) { specifiers_without_opposite_accessor - .push(self.function.get_string()); + .push(self.function.as_str().into()); } let import_text = specifiers_without_opposite_accessor.join(", "); @@ -190,7 +186,7 @@ impl Rule for ConsistentVitestVi { return; } - if vitest_fn.name == self.function.get_opposite_accessor() { + if vitest_fn.name == self.function.not().as_str() { let Some(member_expression) = call_expr.callee.as_member_expression() else { return; }; @@ -201,10 +197,7 @@ impl Rule for ConsistentVitestVi { &self.function, ), |fixer| { - fixer.replace( - member_expression.object().span(), - self.function.get_string(), - ) + fixer.replace(member_expression.object().span(), self.function.as_str()) }, ); } From 1d35ecd7f1790a4124a6c438bfb8dc597f359773 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Mon, 29 Dec 2025 11:48:51 +0000 Subject: [PATCH 3/9] u --- .../src/rules/vitest/consistent_vitest_vi.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs index 51849105faa15..f06ba62c6f085 100644 --- a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs +++ b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs @@ -1,16 +1,16 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use std::borrow::Cow; use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; -use schemars::JsonSchema; -use serde::Deserialize; use crate::{ AstNode, context::LintContext, - rule::Rule, + rule::{DefaultRuleConfig, Rule}, utils::{JestFnKind, JestGeneralFnKind, PossibleJestNode, parse_general_jest_fn_call}, }; @@ -24,7 +24,7 @@ fn consistent_vitest_vi_diagnostic(span: Span, fn_value: &VitestFnName) -> OxcDi .with_label(span) } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Deserialize)] pub struct ConsistentVitestVi(Box); impl std::ops::Deref for ConsistentVitestVi { @@ -35,7 +35,7 @@ impl std::ops::Deref for ConsistentVitestVi { } } -#[derive(Debug, Clone, PartialEq, Eq, Default, JsonSchema, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Default, JsonSchema, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum VitestFnName { #[default] @@ -101,10 +101,9 @@ declare_oxc_lint!( impl Rule for ConsistentVitestVi { fn from_configuration(value: serde_json::Value) -> Self { - let config: ConsistentVitestConfig = - value.get(0).and_then(|v| serde_json::from_value(v.clone()).ok()).unwrap_or_default(); - - Self(Box::new(config)) + serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner() } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { @@ -133,7 +132,7 @@ impl Rule for ConsistentVitestVi { specs .iter() .filter(|spec| spec.name() != opposite.as_str()) - .map(|spec| spec.name()) + .map(ImportDeclarationSpecifier::name) .collect() }) .unwrap_or_default(); From 4e0f90694fc95da71eedef821ce21df1812a8a93 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Mon, 29 Dec 2025 11:49:09 +0000 Subject: [PATCH 4/9] u --- crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs index f06ba62c6f085..5ce5dc7ec6670 100644 --- a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs +++ b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs @@ -2,7 +2,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::borrow::Cow; -use oxc_ast::AstKind; +use oxc_ast::{AstKind, ast::ImportDeclarationSpecifier}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; From 593c56da26afb15d519a8811aa5864c0405cf456 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Mon, 29 Dec 2025 11:55:35 +0000 Subject: [PATCH 5/9] u --- ...n fixtures__eslintrc_vitest_replace__foo.test.js@oxlint.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/oxlint/src/snapshots/_--vitest-plugin -c fixtures__eslintrc_vitest_replace__eslintrc.json fixtures__eslintrc_vitest_replace__foo.test.js@oxlint.snap b/apps/oxlint/src/snapshots/_--vitest-plugin -c fixtures__eslintrc_vitest_replace__eslintrc.json fixtures__eslintrc_vitest_replace__foo.test.js@oxlint.snap index a0b38242647d6..2e676b3e1bc8e 100644 --- a/apps/oxlint/src/snapshots/_--vitest-plugin -c fixtures__eslintrc_vitest_replace__eslintrc.json fixtures__eslintrc_vitest_replace__foo.test.js@oxlint.snap +++ b/apps/oxlint/src/snapshots/_--vitest-plugin -c fixtures__eslintrc_vitest_replace__eslintrc.json fixtures__eslintrc_vitest_replace__foo.test.js@oxlint.snap @@ -23,7 +23,7 @@ working directory: help: Remove the appending `.skip` Found 1 warning and 1 error. -Finished in ms on 1 file with 103 rules using 1 threads. +Finished in ms on 1 file with 102 rules using 1 threads. ---------- CLI result: LintFoundErrors ---------- From 8851db52dfa2fdee4db55b23c4d9ebec9673c56c Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Tue, 30 Dec 2025 09:36:07 -0700 Subject: [PATCH 6/9] Apply suggestion from @connorshea Signed-off-by: Connor Shea --- crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs index 5ce5dc7ec6670..023875c63698e 100644 --- a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs +++ b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs @@ -101,7 +101,7 @@ declare_oxc_lint!( impl Rule for ConsistentVitestVi { fn from_configuration(value: serde_json::Value) -> Self { - serde_json::from_value::>(value) + serde_json::from_value::>(value) .unwrap_or_default() .into_inner() } From a8a0f5a46d7be975f62c0cb94c3327bb0b5de195 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 16:37:08 +0000 Subject: [PATCH 7/9] [autofix.ci] apply automated fixes --- crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs index 023875c63698e..5c75a56c69041 100644 --- a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs +++ b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs @@ -101,9 +101,7 @@ declare_oxc_lint!( impl Rule for ConsistentVitestVi { fn from_configuration(value: serde_json::Value) -> Self { - serde_json::from_value::>(value) - .unwrap_or_default() - .into_inner() + serde_json::from_value::>(value).unwrap_or_default().into_inner() } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { From 0eea4b45461c7f16c6520e49bb4947c5e9c82600 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Tue, 30 Dec 2025 09:37:43 -0700 Subject: [PATCH 8/9] Apply suggestion from @connorshea Signed-off-by: Connor Shea --- crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs index 5c75a56c69041..f698b00f965a5 100644 --- a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs +++ b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs @@ -70,12 +70,12 @@ pub struct ConsistentVitestConfig { declare_oxc_lint!( /// ### What it does /// - /// This rule triggers an error when a not expected vitest accessor is used. + /// This rule triggers an error when an unexpected vitest accessor is used. /// /// ### Why is this bad? /// - /// Not having a consistent vitest accessor can lead to confusion on why - /// on some contexts `vi` is used, and on other `vitest` is used. + /// Not having a consistent vitest accessor can lead to confusion + /// when `vi` and `vitest` are used interchangeably. /// /// ### Examples /// From b9f0bd9aa59cadf251a49868b5b56b68086f3a5b Mon Sep 17 00:00:00 2001 From: Said Atrahouch Date: Wed, 31 Dec 2025 06:51:30 +0100 Subject: [PATCH 9/9] feat(linter/eslint-plugin-vitest): Update with new `from_configuration` API --- crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs index f698b00f965a5..5b99eb5c0e1e3 100644 --- a/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs +++ b/crates/oxc_linter/src/rules/vitest/consistent_vitest_vi.rs @@ -100,8 +100,10 @@ declare_oxc_lint!( ); impl Rule for ConsistentVitestVi { - fn from_configuration(value: serde_json::Value) -> Self { - serde_json::from_value::>(value).unwrap_or_default().into_inner() + fn from_configuration(value: serde_json::Value) -> Result { + Ok(serde_json::from_value::>(value) + .unwrap_or_default() + .into_inner()) } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {