From f5c6acc0a425d03aa417b31769b3bfb48070c174 Mon Sep 17 00:00:00 2001 From: Sysix <3897725+Sysix@users.noreply.github.com> Date: Mon, 6 Oct 2025 08:21:14 +0000 Subject: [PATCH] feat(linter): add `vue/no-export-in-script-setup` rule (#14307) the original rule iterates over all the nodes, and will detect empty `ExportNamedDeclaration` (`export {}`). I tried to use the `ModuleRecord` to simplify the implementation, but it results into different spans / results. https://github.com/vuejs/eslint-plugin-vue/blob/553abe61c4d7a8964fb154069ea6a82d14b2b3b6/tests/lib/rules/no-export-in-script-setup.js#L220-L226 https://eslint.vuejs.org/rules/no-export-in-script-setup.html related #11440 --- .../src/generated/rule_runner_impls.rs | 4 + crates/oxc_linter/src/rules.rs | 2 + .../rules/vue/no_export_in_script_setup.rs | 170 ++++++++++++++++++ .../vue_no_export_in_script_setup.snap | 114 ++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 crates/oxc_linter/src/rules/vue/no_export_in_script_setup.rs create mode 100644 crates/oxc_linter/src/snapshots/vue_no_export_in_script_setup.snap diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 61a0b78bd94f4..8f13d71bd4022 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -2915,6 +2915,10 @@ impl RuleRunner for crate::rules::vue::max_props::MaxProps { const NODE_TYPES: Option<&AstTypesBitset> = None; } +impl RuleRunner for crate::rules::vue::no_export_in_script_setup::NoExportInScriptSetup { + const NODE_TYPES: Option<&AstTypesBitset> = None; +} + impl RuleRunner for crate::rules::vue::no_multiple_slot_args::NoMultipleSlotArgs { const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[AstType::CallExpression])); diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index b1be375beca36..b2433eb67558a 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -645,6 +645,7 @@ pub(crate) mod vue { pub mod define_props_declaration; pub mod define_props_destructuring; pub mod max_props; + pub mod no_export_in_script_setup; pub mod no_multiple_slot_args; pub mod no_required_prop_with_default; pub mod prefer_import_from_vue; @@ -1249,6 +1250,7 @@ oxc_macros::declare_all_lint_rules! { vue::define_emits_declaration, vue::define_props_declaration, vue::max_props, + vue::no_export_in_script_setup, vue::no_multiple_slot_args, vue::no_required_prop_with_default, vue::prefer_import_from_vue, diff --git a/crates/oxc_linter/src/rules/vue/no_export_in_script_setup.rs b/crates/oxc_linter/src/rules/vue/no_export_in_script_setup.rs new file mode 100644 index 0000000000000..2c9282e0758c7 --- /dev/null +++ b/crates/oxc_linter/src/rules/vue/no_export_in_script_setup.rs @@ -0,0 +1,170 @@ +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + context::{ContextHost, LintContext}, + frameworks::FrameworkOptions, + rule::Rule, +}; + +fn no_export_in_script_setup_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn(" + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```vue + /// + /// ``` + NoExportInScriptSetup, + vue, + correctness, +); + +impl Rule for NoExportInScriptSetup { + fn run_once(&self, ctx: &LintContext) { + let modules = ctx.module_record(); + + for entry in &modules.local_export_entries { + if entry.is_type { + continue; + } + + ctx.diagnostic(no_export_in_script_setup_diagnostic(entry.span)); + } + + for entry in &modules.indirect_export_entries { + if entry.is_type { + continue; + } + + ctx.diagnostic(no_export_in_script_setup_diagnostic(entry.span)); + } + + for entry in &modules.star_export_entries { + if entry.is_type { + continue; + } + ctx.diagnostic(no_export_in_script_setup_diagnostic(entry.span)); + } + + if let Some(span) = modules.export_default { + ctx.diagnostic(no_export_in_script_setup_diagnostic(span)); + } + } + + fn should_run(&self, ctx: &ContextHost) -> bool { + ctx.frameworks_options() == FrameworkOptions::VueSetup + } +} + +#[test] +fn test() { + use crate::tester::Tester; + use std::path::PathBuf; + + let pass = vec![ + ( + " + + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + " + + + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ]; + + let fail = vec![ + ( + " + + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + " + + + ", + None, + None, + Some(PathBuf::from("test.vue")), + ), + ( + r#" + + "#, + None, + None, + Some(PathBuf::from("test.vue")), + ), // { "parser": require("vue-eslint-parser"), "parserOptions": { "parser": require.resolve("@typescript-eslint/parser") } } + ]; + + Tester::new(NoExportInScriptSetup::NAME, NoExportInScriptSetup::PLUGIN, pass, fail) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/vue_no_export_in_script_setup.snap b/crates/oxc_linter/src/snapshots/vue_no_export_in_script_setup.snap new file mode 100644 index 0000000000000..f3d3c977dc8dd --- /dev/null +++ b/crates/oxc_linter/src/snapshots/vue_no_export_in_script_setup.snap @@ -0,0 +1,114 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint-plugin-vue(no-export-in-script-setup): + ╰──── + + ⚠ eslint-plugin-vue(no-export-in-script-setup): + ╰──── + + ⚠ eslint-plugin-vue(no-export-in-script-setup):