diff --git a/crates/oxc_linter/src/options.rs b/crates/oxc_linter/src/options.rs index 4954c239a65d9..bd2b6860b5bc0 100644 --- a/crates/oxc_linter/src/options.rs +++ b/crates/oxc_linter/src/options.rs @@ -362,6 +362,7 @@ impl LintOptions { } false } + "vitest" => self.vitest_plugin, "jsx_a11y" => self.jsx_a11y_plugin, "nextjs" => self.nextjs_plugin, "react_perf" => self.react_perf_plugin, diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 3fa61dbbff3de..acc46c04f88ed 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -441,6 +441,10 @@ mod promise { pub mod param_names; } +mod vitest { + pub mod no_import_node_test; +} + oxc_macros::declare_all_lint_rules! { eslint::array_callback_return, eslint::constructor_super, @@ -837,4 +841,5 @@ oxc_macros::declare_all_lint_rules! { promise::avoid_new, promise::no_new_statics, promise::param_names, + vitest::no_import_node_test, } diff --git a/crates/oxc_linter/src/rules/vitest/no_import_node_test.rs b/crates/oxc_linter/src/rules/vitest/no_import_node_test.rs new file mode 100644 index 0000000000000..b7a1f55fbcf60 --- /dev/null +++ b/crates/oxc_linter/src/rules/vitest/no_import_node_test.rs @@ -0,0 +1,79 @@ +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule}; + +fn no_import_node_test(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("disallow importing `node:test`".to_string()) + .with_help("Import from `vitest` instead of `node:test`") + .with_label(span0) +} + +#[derive(Debug, Default, Clone)] +pub struct NoImportNodeTest; + +declare_oxc_lint!( + /// ### What it does + /// + /// This rule warns when `node:test` is imported (usually accidentally). With `--fix`, it will replace the import with `vitest`. + /// + /// ### Examples + /// + /// ```javascript + /// // invalid + /// import { test } from 'node:test' + /// import { expect } from 'vitest' + /// + /// test('foo', () => { + /// expect(1).toBe(1) + /// }) + /// ``` + /// + /// ```javascript + /// // valid + /// import { test, expect } from 'vitest' + /// + /// test('foo', () => { + /// expect(1).toBe(1) + /// }) + /// ``` + NoImportNodeTest, + style, +); + +impl Rule for NoImportNodeTest { + fn run_once(&self, ctx: &LintContext<'_>) { + let module_record = ctx.module_record(); + + if let Some(node_test_module) = module_record.requested_modules.get("node:test") { + if let Some(requested_module) = node_test_module.first() { + ctx.diagnostic_with_fix(no_import_node_test(requested_module.span()), |fixer| { + fixer.replace(requested_module.span(), "\"vitest\"") + }); + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![(r#"import { test } from "vitest""#, None)]; + + let fail = vec![ + (r#"import { test } from "node:test""#, None), + ("import * as foo from 'node:test'", None), + ]; + + let fix = vec![ + (r#"import { test } from "node:test""#, r#"import { test } from "vitest""#, None), + (r#"import * as foo from "node:test""#, r#"import * as foo from "vitest""#, None), + ]; + + Tester::new(NoImportNodeTest::NAME, pass, fail) + .with_vitest_plugin(true) + .expect_fix(fix) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_import_node_test.snap b/crates/oxc_linter/src/snapshots/no_import_node_test.snap new file mode 100644 index 0000000000000..6756046c3271b --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_import_node_test.snap @@ -0,0 +1,17 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 216 +--- + ⚠ eslint-plugin-vitest(no-import-node-test): disallow importing `node:test` + ╭─[no_import_node_test.tsx:1:22] + 1 │ import { test } from "node:test" + · ─────────── + ╰──── + help: Import from `vitest` instead of `node:test` + + ⚠ eslint-plugin-vitest(no-import-node-test): disallow importing `node:test` + ╭─[no_import_node_test.tsx:1:22] + 1 │ import * as foo from 'node:test' + · ─────────── + ╰──── + help: Import from `vitest` instead of `node:test` diff --git a/tasks/benchmark/benches/linter.rs b/tasks/benchmark/benches/linter.rs index 77f844322e7d1..901d5fbe858f7 100644 --- a/tasks/benchmark/benches/linter.rs +++ b/tasks/benchmark/benches/linter.rs @@ -48,7 +48,8 @@ fn bench_linter(criterion: &mut Criterion) { .with_jest_plugin(true) .with_jsx_a11y_plugin(true) .with_nextjs_plugin(true) - .with_react_perf_plugin(true); + .with_react_perf_plugin(true) + .with_vitest_plugin(true); let linter = Linter::from_options(lint_options).unwrap(); let semantic = Rc::new(semantic_ret.semantic); b.iter(|| linter.run(Path::new(std::ffi::OsStr::new("")), Rc::clone(&semantic)));