diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index d5d4406ae5ca6..2697612c08a48 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -4215,6 +4215,14 @@ impl RuleRunner for crate::rules::vitest::no_import_node_test::NoImportNodeTest const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; } +impl RuleRunner for crate::rules::vitest::no_importing_vitest_globals::NoImportingVitestGlobals { + const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ + AstType::ImportDeclaration, + AstType::VariableDeclaration, + ])); + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; +} + impl RuleRunner for crate::rules::vitest::prefer_called_once::PreferCalledOnce { const NODE_TYPES: Option<&AstTypesBitset> = None; const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnJestNode; diff --git a/crates/oxc_linter/src/generated/rules_enum.rs b/crates/oxc_linter/src/generated/rules_enum.rs index 881dd09b97f0d..5568235edcc1f 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -672,6 +672,7 @@ pub use crate::rules::vitest::consistent_vitest_vi::ConsistentVitestVi as Vitest pub use crate::rules::vitest::hoisted_apis_on_top::HoistedApisOnTop as VitestHoistedApisOnTop; pub use crate::rules::vitest::no_conditional_tests::NoConditionalTests as VitestNoConditionalTests; pub use crate::rules::vitest::no_import_node_test::NoImportNodeTest as VitestNoImportNodeTest; +pub use crate::rules::vitest::no_importing_vitest_globals::NoImportingVitestGlobals as VitestNoImportingVitestGlobals; pub use crate::rules::vitest::prefer_called_once::PreferCalledOnce as VitestPreferCalledOnce; pub use crate::rules::vitest::prefer_called_times::PreferCalledTimes as VitestPreferCalledTimes; pub use crate::rules::vitest::prefer_describe_function_title::PreferDescribeFunctionTitle as VitestPreferDescribeFunctionTitle; @@ -1369,6 +1370,7 @@ pub enum RuleEnum { VitestHoistedApisOnTop(VitestHoistedApisOnTop), VitestNoConditionalTests(VitestNoConditionalTests), VitestNoImportNodeTest(VitestNoImportNodeTest), + VitestNoImportingVitestGlobals(VitestNoImportingVitestGlobals), VitestPreferCalledOnce(VitestPreferCalledOnce), VitestPreferCalledTimes(VitestPreferCalledTimes), VitestPreferDescribeFunctionTitle(VitestPreferDescribeFunctionTitle), @@ -2145,7 +2147,8 @@ const VITEST_CONSISTENT_VITEST_VI_ID: usize = VITEST_CONSISTENT_TEST_FILENAME_ID const VITEST_HOISTED_APIS_ON_TOP_ID: usize = VITEST_CONSISTENT_VITEST_VI_ID + 1usize; const VITEST_NO_CONDITIONAL_TESTS_ID: usize = VITEST_HOISTED_APIS_ON_TOP_ID + 1usize; const VITEST_NO_IMPORT_NODE_TEST_ID: usize = VITEST_NO_CONDITIONAL_TESTS_ID + 1usize; -const VITEST_PREFER_CALLED_ONCE_ID: usize = VITEST_NO_IMPORT_NODE_TEST_ID + 1usize; +const VITEST_NO_IMPORTING_VITEST_GLOBALS_ID: usize = VITEST_NO_IMPORT_NODE_TEST_ID + 1usize; +const VITEST_PREFER_CALLED_ONCE_ID: usize = VITEST_NO_IMPORTING_VITEST_GLOBALS_ID + 1usize; const VITEST_PREFER_CALLED_TIMES_ID: usize = VITEST_PREFER_CALLED_ONCE_ID + 1usize; const VITEST_PREFER_DESCRIBE_FUNCTION_TITLE_ID: usize = VITEST_PREFER_CALLED_TIMES_ID + 1usize; const VITEST_PREFER_EXPECT_TYPE_OF_ID: usize = VITEST_PREFER_DESCRIBE_FUNCTION_TITLE_ID + 1usize; @@ -2945,6 +2948,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(_) => VITEST_HOISTED_APIS_ON_TOP_ID, Self::VitestNoConditionalTests(_) => VITEST_NO_CONDITIONAL_TESTS_ID, Self::VitestNoImportNodeTest(_) => VITEST_NO_IMPORT_NODE_TEST_ID, + Self::VitestNoImportingVitestGlobals(_) => VITEST_NO_IMPORTING_VITEST_GLOBALS_ID, Self::VitestPreferCalledOnce(_) => VITEST_PREFER_CALLED_ONCE_ID, Self::VitestPreferCalledTimes(_) => VITEST_PREFER_CALLED_TIMES_ID, Self::VitestPreferDescribeFunctionTitle(_) => VITEST_PREFER_DESCRIBE_FUNCTION_TITLE_ID, @@ -3734,6 +3738,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(_) => VitestHoistedApisOnTop::NAME, Self::VitestNoConditionalTests(_) => VitestNoConditionalTests::NAME, Self::VitestNoImportNodeTest(_) => VitestNoImportNodeTest::NAME, + Self::VitestNoImportingVitestGlobals(_) => VitestNoImportingVitestGlobals::NAME, Self::VitestPreferCalledOnce(_) => VitestPreferCalledOnce::NAME, Self::VitestPreferCalledTimes(_) => VitestPreferCalledTimes::NAME, Self::VitestPreferDescribeFunctionTitle(_) => VitestPreferDescribeFunctionTitle::NAME, @@ -4565,6 +4570,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(_) => VitestHoistedApisOnTop::CATEGORY, Self::VitestNoConditionalTests(_) => VitestNoConditionalTests::CATEGORY, Self::VitestNoImportNodeTest(_) => VitestNoImportNodeTest::CATEGORY, + Self::VitestNoImportingVitestGlobals(_) => VitestNoImportingVitestGlobals::CATEGORY, Self::VitestPreferCalledOnce(_) => VitestPreferCalledOnce::CATEGORY, Self::VitestPreferCalledTimes(_) => VitestPreferCalledTimes::CATEGORY, Self::VitestPreferDescribeFunctionTitle(_) => { @@ -5359,6 +5365,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(_) => VitestHoistedApisOnTop::FIX, Self::VitestNoConditionalTests(_) => VitestNoConditionalTests::FIX, Self::VitestNoImportNodeTest(_) => VitestNoImportNodeTest::FIX, + Self::VitestNoImportingVitestGlobals(_) => VitestNoImportingVitestGlobals::FIX, Self::VitestPreferCalledOnce(_) => VitestPreferCalledOnce::FIX, Self::VitestPreferCalledTimes(_) => VitestPreferCalledTimes::FIX, Self::VitestPreferDescribeFunctionTitle(_) => VitestPreferDescribeFunctionTitle::FIX, @@ -6341,6 +6348,9 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(_) => VitestHoistedApisOnTop::documentation(), Self::VitestNoConditionalTests(_) => VitestNoConditionalTests::documentation(), Self::VitestNoImportNodeTest(_) => VitestNoImportNodeTest::documentation(), + Self::VitestNoImportingVitestGlobals(_) => { + VitestNoImportingVitestGlobals::documentation() + } Self::VitestPreferCalledOnce(_) => VitestPreferCalledOnce::documentation(), Self::VitestPreferCalledTimes(_) => VitestPreferCalledTimes::documentation(), Self::VitestPreferDescribeFunctionTitle(_) => { @@ -8259,6 +8269,10 @@ impl RuleEnum { .or_else(|| VitestNoConditionalTests::schema(generator)), Self::VitestNoImportNodeTest(_) => VitestNoImportNodeTest::config_schema(generator) .or_else(|| VitestNoImportNodeTest::schema(generator)), + Self::VitestNoImportingVitestGlobals(_) => { + VitestNoImportingVitestGlobals::config_schema(generator) + .or_else(|| VitestNoImportingVitestGlobals::schema(generator)) + } Self::VitestPreferCalledOnce(_) => VitestPreferCalledOnce::config_schema(generator) .or_else(|| VitestPreferCalledOnce::schema(generator)), Self::VitestPreferCalledTimes(_) => VitestPreferCalledTimes::config_schema(generator) @@ -9009,6 +9023,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(_) => "vitest", Self::VitestNoConditionalTests(_) => "vitest", Self::VitestNoImportNodeTest(_) => "vitest", + Self::VitestNoImportingVitestGlobals(_) => "vitest", Self::VitestPreferCalledOnce(_) => "vitest", Self::VitestPreferCalledTimes(_) => "vitest", Self::VitestPreferDescribeFunctionTitle(_) => "vitest", @@ -11163,6 +11178,9 @@ impl RuleEnum { Self::VitestNoImportNodeTest(_) => { Ok(Self::VitestNoImportNodeTest(VitestNoImportNodeTest::from_configuration(value)?)) } + Self::VitestNoImportingVitestGlobals(_) => Ok(Self::VitestNoImportingVitestGlobals( + VitestNoImportingVitestGlobals::from_configuration(value)?, + )), Self::VitestPreferCalledOnce(_) => { Ok(Self::VitestPreferCalledOnce(VitestPreferCalledOnce::from_configuration(value)?)) } @@ -11928,6 +11946,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(rule) => rule.to_configuration(), Self::VitestNoConditionalTests(rule) => rule.to_configuration(), Self::VitestNoImportNodeTest(rule) => rule.to_configuration(), + Self::VitestNoImportingVitestGlobals(rule) => rule.to_configuration(), Self::VitestPreferCalledOnce(rule) => rule.to_configuration(), Self::VitestPreferCalledTimes(rule) => rule.to_configuration(), Self::VitestPreferDescribeFunctionTitle(rule) => rule.to_configuration(), @@ -12625,6 +12644,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(rule) => rule.run(node, ctx), Self::VitestNoConditionalTests(rule) => rule.run(node, ctx), Self::VitestNoImportNodeTest(rule) => rule.run(node, ctx), + Self::VitestNoImportingVitestGlobals(rule) => rule.run(node, ctx), Self::VitestPreferCalledOnce(rule) => rule.run(node, ctx), Self::VitestPreferCalledTimes(rule) => rule.run(node, ctx), Self::VitestPreferDescribeFunctionTitle(rule) => rule.run(node, ctx), @@ -13320,6 +13340,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(rule) => rule.run_once(ctx), Self::VitestNoConditionalTests(rule) => rule.run_once(ctx), Self::VitestNoImportNodeTest(rule) => rule.run_once(ctx), + Self::VitestNoImportingVitestGlobals(rule) => rule.run_once(ctx), Self::VitestPreferCalledOnce(rule) => rule.run_once(ctx), Self::VitestPreferCalledTimes(rule) => rule.run_once(ctx), Self::VitestPreferDescribeFunctionTitle(rule) => rule.run_once(ctx), @@ -14111,6 +14132,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(rule) => rule.run_on_jest_node(jest_node, ctx), Self::VitestNoConditionalTests(rule) => rule.run_on_jest_node(jest_node, ctx), Self::VitestNoImportNodeTest(rule) => rule.run_on_jest_node(jest_node, ctx), + Self::VitestNoImportingVitestGlobals(rule) => rule.run_on_jest_node(jest_node, ctx), Self::VitestPreferCalledOnce(rule) => rule.run_on_jest_node(jest_node, ctx), Self::VitestPreferCalledTimes(rule) => rule.run_on_jest_node(jest_node, ctx), Self::VitestPreferDescribeFunctionTitle(rule) => rule.run_on_jest_node(jest_node, ctx), @@ -14808,6 +14830,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(rule) => rule.should_run(ctx), Self::VitestNoConditionalTests(rule) => rule.should_run(ctx), Self::VitestNoImportNodeTest(rule) => rule.should_run(ctx), + Self::VitestNoImportingVitestGlobals(rule) => rule.should_run(ctx), Self::VitestPreferCalledOnce(rule) => rule.should_run(ctx), Self::VitestPreferCalledTimes(rule) => rule.should_run(ctx), Self::VitestPreferDescribeFunctionTitle(rule) => rule.should_run(ctx), @@ -15787,6 +15810,9 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(_) => VitestHoistedApisOnTop::IS_TSGOLINT_RULE, Self::VitestNoConditionalTests(_) => VitestNoConditionalTests::IS_TSGOLINT_RULE, Self::VitestNoImportNodeTest(_) => VitestNoImportNodeTest::IS_TSGOLINT_RULE, + Self::VitestNoImportingVitestGlobals(_) => { + VitestNoImportingVitestGlobals::IS_TSGOLINT_RULE + } Self::VitestPreferCalledOnce(_) => VitestPreferCalledOnce::IS_TSGOLINT_RULE, Self::VitestPreferCalledTimes(_) => VitestPreferCalledTimes::IS_TSGOLINT_RULE, Self::VitestPreferDescribeFunctionTitle(_) => { @@ -16649,6 +16675,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(_) => VitestHoistedApisOnTop::HAS_CONFIG, Self::VitestNoConditionalTests(_) => VitestNoConditionalTests::HAS_CONFIG, Self::VitestNoImportNodeTest(_) => VitestNoImportNodeTest::HAS_CONFIG, + Self::VitestNoImportingVitestGlobals(_) => VitestNoImportingVitestGlobals::HAS_CONFIG, Self::VitestPreferCalledOnce(_) => VitestPreferCalledOnce::HAS_CONFIG, Self::VitestPreferCalledTimes(_) => VitestPreferCalledTimes::HAS_CONFIG, Self::VitestPreferDescribeFunctionTitle(_) => { @@ -17350,6 +17377,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(rule) => rule.types_info(), Self::VitestNoConditionalTests(rule) => rule.types_info(), Self::VitestNoImportNodeTest(rule) => rule.types_info(), + Self::VitestNoImportingVitestGlobals(rule) => rule.types_info(), Self::VitestPreferCalledOnce(rule) => rule.types_info(), Self::VitestPreferCalledTimes(rule) => rule.types_info(), Self::VitestPreferDescribeFunctionTitle(rule) => rule.types_info(), @@ -18045,6 +18073,7 @@ impl RuleEnum { Self::VitestHoistedApisOnTop(rule) => rule.run_info(), Self::VitestNoConditionalTests(rule) => rule.run_info(), Self::VitestNoImportNodeTest(rule) => rule.run_info(), + Self::VitestNoImportingVitestGlobals(rule) => rule.run_info(), Self::VitestPreferCalledOnce(rule) => rule.run_info(), Self::VitestPreferCalledTimes(rule) => rule.run_info(), Self::VitestPreferDescribeFunctionTitle(rule) => rule.run_info(), @@ -18854,6 +18883,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( RuleEnum::VitestHoistedApisOnTop(VitestHoistedApisOnTop::default()), RuleEnum::VitestNoConditionalTests(VitestNoConditionalTests::default()), RuleEnum::VitestNoImportNodeTest(VitestNoImportNodeTest::default()), + RuleEnum::VitestNoImportingVitestGlobals(VitestNoImportingVitestGlobals::default()), RuleEnum::VitestPreferCalledOnce(VitestPreferCalledOnce::default()), RuleEnum::VitestPreferCalledTimes(VitestPreferCalledTimes::default()), RuleEnum::VitestPreferDescribeFunctionTitle(VitestPreferDescribeFunctionTitle::default()), diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index c16dec8f25dcd..59621d65d50ec 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -701,6 +701,7 @@ pub(crate) mod vitest { pub mod hoisted_apis_on_top; pub mod no_conditional_tests; pub mod no_import_node_test; + pub mod no_importing_vitest_globals; pub mod prefer_called_once; pub mod prefer_called_times; pub mod prefer_describe_function_title; diff --git a/crates/oxc_linter/src/rules/vitest/no_importing_vitest_globals.rs b/crates/oxc_linter/src/rules/vitest/no_importing_vitest_globals.rs new file mode 100644 index 0000000000000..9f832baa23dce --- /dev/null +++ b/crates/oxc_linter/src/rules/vitest/no_importing_vitest_globals.rs @@ -0,0 +1,403 @@ +use itertools::Itertools; +use oxc_ast::{ + AstKind, + ast::{ + Argument, BindingPattern, Expression, ImportDeclarationSpecifier, ImportOrExportKind, + VariableDeclarationKind, VariableDeclarator, + }, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::AstNode; +use oxc_span::{GetSpan, Span}; + +use crate::{context::LintContext, rule::Rule}; + +fn no_importing_vitest_globals_diagnostic(spans: &[Span]) -> OxcDiagnostic { + let help = format!("You can import anything except `{}`.", VITEST_GLOBALS.join(", ")); + + OxcDiagnostic::warn("Do not import/require global functions from 'vitest'.") + .with_help(help) + .with_labels(spans.iter().map(|span| span.label("Remove this global vitest import"))) +} + +#[derive(Debug, Default, Clone)] +pub struct NoImportingVitestGlobals; + +declare_oxc_lint!( + /// ### What it does + /// + /// The rule disallows import any vitest global function. + /// + /// ### Why is this bad? + /// + /// If the project is configured to use globals from vitest, the rule ensure + /// that never imports the globals from `import` or `require`. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// import { test, expect } from 'vitest' + /// + /// test('foo', () => { + /// expect(1).toBe(1) + /// }) + /// ``` + /// + /// ```js + /// const { test, expect } = require('vitest') + /// + /// test('foo', () => { + /// expect(1).toBe(1) + /// }) + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// test('foo', () => { + /// expect(1).toBe(1) + /// }) + /// ``` + NoImportingVitestGlobals, + vitest, + style, + fix, +); + +impl Rule for NoImportingVitestGlobals { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::VariableDeclaration(variable_declarations) => { + let declaration_processed = variable_declarations + .declarations + .iter() + .map(|declaration| { + if !is_vitest_require_declaration(declaration) { + return DeclarationRenderType::NoVitest(declaration.span); + } + + process_declaration(declaration, ctx) + }) + .collect::>(); + + let Some(DeclarationRenderType::Vitest(vitest_require)) = &declaration_processed + .iter() + .find(|value| matches!(value, DeclarationRenderType::Vitest(_))) + else { + return; + }; + + ctx.diagnostic_with_fix( + no_importing_vitest_globals_diagnostic(&vitest_require.global_vitest_spans), + |fixer| { + let variable_modifier = match variable_declarations.kind { + VariableDeclarationKind::Const => "const", + VariableDeclarationKind::Let => "let", + VariableDeclarationKind::Var => "var", + _ => return fixer.noop(), + }; + + let declarations = declaration_processed + .iter() + .filter_map(|declaration| match declaration { + DeclarationRenderType::NoVitest(span) => { + let source_declaration = ctx.source_range(*span); + Some(source_declaration.to_string()) + } + DeclarationRenderType::Vitest(vitest_require) => { + if vitest_require.remove_fully { + return None; + } + + let new_vitest_declaration = format!( + "{{ {} }} = require('vitest')", + vitest_require.non_global_imports.join(", ") + ); + Some(new_vitest_declaration) + } + }) + .join(", "); + + if declarations.is_empty() { + return fixer.delete(node); + } + + let new_declaration = format!("{variable_modifier} {declarations};"); + + fixer.replace(variable_declarations.span, new_declaration) + }, + ); + } + AstKind::ImportDeclaration(import_decl) => { + if import_decl.source.value.as_str() != "vitest" { + return; + } + + let Some(specifiers) = &import_decl.specifiers else { + return; + }; + + let Some(span_start) = + specifiers.first().map(|specifier| GetSpan::span(specifier).start) + else { + return; + }; + + let Some(span_end) = + specifiers.last().map(|specifier| GetSpan::span(specifier).end) + else { + return; + }; + + let mut spans: Vec = vec![]; + let mut new_imports: Vec = vec![]; + + for import_specifier in specifiers { + match import_specifier { + ImportDeclarationSpecifier::ImportDefaultSpecifier(_) + | ImportDeclarationSpecifier::ImportNamespaceSpecifier(_) => {} + ImportDeclarationSpecifier::ImportSpecifier(specifier) => { + if specifier.import_kind == ImportOrExportKind::Type { + new_imports.push(ctx.source_range(specifier.span).to_string()); + continue; + } + + if !specifier.imported.is_identifier() { + continue; + } + + if VITEST_GLOBALS.contains(&specifier.local.name.as_ref()) { + spans.push(specifier.span); + } else { + new_imports.push(ctx.source_range(specifier.span).to_string()); + } + } + } + } + + if !spans.is_empty() { + ctx.diagnostic_with_fix( + no_importing_vitest_globals_diagnostic(spans.as_ref()), + |fixer| { + if spans.len() == specifiers.len() { + return fixer.delete(node); + } + + let import_text = new_imports.join(", "); + + let specifiers_span = Span::new(span_start, span_end); + + fixer.replace(specifiers_span, import_text) + }, + ); + } + } + _ => {} + } + } +} + +fn is_vitest_require_declaration(declaration: &VariableDeclarator<'_>) -> bool { + let Some(Expression::CallExpression(call_expr)) = &declaration.init else { + return false; + }; + + if !call_expr.is_require_call() { + return false; + } + + let Some(Argument::StringLiteral(require_import)) = call_expr.arguments.first() else { + return false; + }; + + if require_import.value.as_str() != "vitest" { + return false; + } + + if declaration.id.is_binding_identifier() { + return false; + } + + true +} + +fn process_declaration( + declaration: &VariableDeclarator<'_>, + ctx: &LintContext<'_>, +) -> DeclarationRenderType { + let BindingPattern::ObjectPattern(obj) = &declaration.id else { + return DeclarationRenderType::NoVitest(declaration.span); + }; + + if obj.rest.is_some() { + return DeclarationRenderType::NoVitest(declaration.span); + } + + if obj.properties.iter().any(|property| property.key.is_specific_static_name("default")) { + return DeclarationRenderType::NoVitest(declaration.span); + } + + let mut global_vitest_spans: Vec = vec![]; + let mut non_global_imports: Vec = vec![]; + + for property in &obj.properties { + let Some(property_name) = property.key.static_name() else { + continue; + }; + + if VITEST_GLOBALS.contains(&property_name.as_ref()) { + global_vitest_spans.push(property.span); + } else { + non_global_imports.push(ctx.source_range(property.span).to_string()); + } + } + + DeclarationRenderType::Vitest(VitestImport { + remove_fully: non_global_imports.is_empty(), + global_vitest_spans, + non_global_imports, + }) +} + +const VITEST_GLOBALS: [&str; 17] = [ + "suite", + "test", + "chai", + "describe", + "it", + "expectTypeOf", + "assertType", + "expect", + "assert", + "vitest", + "vi", + "beforeAll", + "afterAll", + "beforeEach", + "afterEach", + "onTestFailed", + "onTestFinished", +]; + +#[derive(Debug, PartialEq, Eq)] +struct VitestImport { + remove_fully: bool, + global_vitest_spans: Vec, + non_global_imports: Vec, +} + +#[derive(Debug, PartialEq, Eq)] +enum DeclarationRenderType { + NoVitest(Span), + Vitest(VitestImport), +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "import { describe } from 'jest';", + "import vitest from 'vitest';", + "import * as vitest from 'vitest';", + r#"import { "default" as vitest } from 'vitest';"#, + "import { BenchFactory } from 'vitest';", + "import type { TestArtifactBase, TestAttachment } from 'vitest'", + "let x;", + "let x = 1;", + "const x = console.log('hello');", + "const x = print('hello');", + "const x = require('something', 'wrong');", + "const x = require(a_variable);", + "const x = require('jest');", + "const x = require('vitest');", + "const { ...rest } = require('vitest');", + r#"const { "default": vitest } = require('vitest');"#, + ]; + + let fail = vec![ + "import { describe } from 'vitest';", + "import { describe, it } from 'vitest';", + "import { describe, BenchFactory } from 'vitest';", + "import { BenchFactory, describe } from 'vitest';", + "import { describe, BenchFactory, it } from 'vitest';", + "import { BenchTask, describe, BenchFactory, it } from 'vitest';", + "import type { TestArtifactBase, TestAttachment } from 'vitest' + import { it, describe } from 'vitest'", + "import { type TestArtifactBase, BenchTask, describe, type TestAttachment, BenchFactory, it } from 'vitest';", + "const x = 1, { describe } = require('vitest');", + "const x = 1, { describe } = require('vitest'), y = 2;", + "const { describe, it } = require('vitest');", + "const { describe, BenchFactory } = require('vitest');", + "const { BenchFactory, describe } = require('vitest');", + "const { describe, BenchFactory, it } = require('vitest');", + "const { BenchTask, describe, BenchFactory, it } = require('vitest');", + ]; + + let fix = vec![ + ("import { describe } from 'vitest';", "", None), + ("import { describe, it } from 'vitest';", "", None), + ( + "import { describe, BenchFactory } from 'vitest';", + "import { BenchFactory } from 'vitest';", + None, + ), + ( + "import { BenchFactory, describe } from 'vitest';", + "import { BenchFactory } from 'vitest';", + None, + ), + ( + "import { describe, BenchFactory, it } from 'vitest';", + "import { BenchFactory } from 'vitest';", + None, + ), + ( + "import { BenchTask, describe, BenchFactory, it } from 'vitest';", + "import { BenchTask, BenchFactory } from 'vitest';", + None, + ), + ( + "import type { TestArtifactBase, TestAttachment } from 'vitest' +import { it, describe } from 'vitest'", + "import type { TestArtifactBase, TestAttachment } from 'vitest' +", + None, + ), + ( + "import { type TestArtifactBase, BenchTask, describe, type TestAttachment, BenchFactory, it } from 'vitest';", + "import { type TestArtifactBase, BenchTask, type TestAttachment, BenchFactory } from 'vitest';", + None, + ), + ("const { describe } = require('vitest');", "", None), + ("const { describe } = require('vitest'), x = 1;", "const x = 1;", None), + ("const x = 1, { describe } = require('vitest');", "const x = 1;", None), + ("const x = 1, { describe } = require('vitest'), y = 2;", "const x = 1, y = 2;", None), + ("const { describe, it } = require('vitest');", "", None), + ( + "const { describe, BenchFactory } = require('vitest');", + "const { BenchFactory } = require('vitest');", + None, + ), + ( + "const { BenchFactory, describe } = require('vitest');", + "const { BenchFactory } = require('vitest');", + None, + ), + ( + "const { describe, BenchFactory, it } = require('vitest');", + "const { BenchFactory } = require('vitest');", + None, + ), + ( + "const { BenchTask, describe, BenchFactory, it } = require('vitest');", + "const { BenchTask, BenchFactory } = require('vitest');", + None, + ), + ]; + Tester::new(NoImportingVitestGlobals::NAME, NoImportingVitestGlobals::PLUGIN, pass, fail) + .expect_fix(fix) + .with_vitest_plugin(true) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/vitest_no_importing_vitest_globals.snap b/crates/oxc_linter/src/snapshots/vitest_no_importing_vitest_globals.snap new file mode 100644 index 0000000000000..51f92852389e8 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/vitest_no_importing_vitest_globals.snap @@ -0,0 +1,132 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:10] + 1 │ import { describe } from 'vitest'; + · ────┬─── + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:10] + 1 │ import { describe, it } from 'vitest'; + · ────┬─── ─┬ + · │ ╰── Remove this global vitest import + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:10] + 1 │ import { describe, BenchFactory } from 'vitest'; + · ────┬─── + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:24] + 1 │ import { BenchFactory, describe } from 'vitest'; + · ────┬─── + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:10] + 1 │ import { describe, BenchFactory, it } from 'vitest'; + · ────┬─── ─┬ + · │ ╰── Remove this global vitest import + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:21] + 1 │ import { BenchTask, describe, BenchFactory, it } from 'vitest'; + · ────┬─── ─┬ + · │ ╰── Remove this global vitest import + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:2:19] + 1 │ import type { TestArtifactBase, TestAttachment } from 'vitest' + 2 │ import { it, describe } from 'vitest' + · ─┬ ────┬─── + · │ ╰── Remove this global vitest import + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:44] + 1 │ import { type TestArtifactBase, BenchTask, describe, type TestAttachment, BenchFactory, it } from 'vitest'; + · ────┬─── ─┬ + · │ ╰── Remove this global vitest import + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:16] + 1 │ const x = 1, { describe } = require('vitest'); + · ────┬─── + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:16] + 1 │ const x = 1, { describe } = require('vitest'), y = 2; + · ────┬─── + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:9] + 1 │ const { describe, it } = require('vitest'); + · ────┬─── ─┬ + · │ ╰── Remove this global vitest import + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:9] + 1 │ const { describe, BenchFactory } = require('vitest'); + · ────┬─── + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:23] + 1 │ const { BenchFactory, describe } = require('vitest'); + · ────┬─── + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:9] + 1 │ const { describe, BenchFactory, it } = require('vitest'); + · ────┬─── ─┬ + · │ ╰── Remove this global vitest import + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`. + + ⚠ eslint-plugin-vitest(no-importing-vitest-globals): Do not import/require global functions from 'vitest'. + ╭─[no_importing_vitest_globals.tsx:1:20] + 1 │ const { BenchTask, describe, BenchFactory, it } = require('vitest'); + · ────┬─── ─┬ + · │ ╰── Remove this global vitest import + · ╰── Remove this global vitest import + ╰──── + help: You can import anything except `suite, test, chai, describe, it, expectTypeOf, assertType, expect, assert, vitest, vi, beforeAll, afterAll, beforeEach, afterEach, onTestFailed, onTestFinished`.