diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 05923d61e72a4..f4b864bb2e9ae 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -3274,6 +3274,11 @@ impl RuleRunner for crate::rules::unicorn::prefer_event_target::PreferEventTarge const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } +impl RuleRunner for crate::rules::unicorn::prefer_export_from::PreferExportFrom { + const NODE_TYPES: Option<&AstTypesBitset> = None; + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnce; +} + impl RuleRunner for crate::rules::unicorn::prefer_global_this::PreferGlobalThis { const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[AstType::IdentifierReference])); diff --git a/crates/oxc_linter/src/generated/rules_enum.rs b/crates/oxc_linter/src/generated/rules_enum.rs index 5cedf9100a232..82d166cda76de 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -631,6 +631,7 @@ pub use crate::rules::unicorn::prefer_dom_node_dataset::PreferDomNodeDataset as pub use crate::rules::unicorn::prefer_dom_node_remove::PreferDomNodeRemove as UnicornPreferDomNodeRemove; pub use crate::rules::unicorn::prefer_dom_node_text_content::PreferDomNodeTextContent as UnicornPreferDomNodeTextContent; pub use crate::rules::unicorn::prefer_event_target::PreferEventTarget as UnicornPreferEventTarget; +pub use crate::rules::unicorn::prefer_export_from::PreferExportFrom as UnicornPreferExportFrom; pub use crate::rules::unicorn::prefer_global_this::PreferGlobalThis as UnicornPreferGlobalThis; pub use crate::rules::unicorn::prefer_includes::PreferIncludes as UnicornPreferIncludes; pub use crate::rules::unicorn::prefer_keyboard_event_key::PreferKeyboardEventKey as UnicornPreferKeyboardEventKey; @@ -1223,6 +1224,7 @@ pub enum RuleEnum { UnicornPreferDomNodeRemove(UnicornPreferDomNodeRemove), UnicornPreferDomNodeTextContent(UnicornPreferDomNodeTextContent), UnicornPreferEventTarget(UnicornPreferEventTarget), + UnicornPreferExportFrom(UnicornPreferExportFrom), UnicornPreferGlobalThis(UnicornPreferGlobalThis), UnicornPreferIncludes(UnicornPreferIncludes), UnicornPreferKeyboardEventKey(UnicornPreferKeyboardEventKey), @@ -1987,7 +1989,8 @@ const UNICORN_PREFER_DOM_NODE_DATASET_ID: usize = UNICORN_PREFER_DOM_NODE_APPEND const UNICORN_PREFER_DOM_NODE_REMOVE_ID: usize = UNICORN_PREFER_DOM_NODE_DATASET_ID + 1usize; const UNICORN_PREFER_DOM_NODE_TEXT_CONTENT_ID: usize = UNICORN_PREFER_DOM_NODE_REMOVE_ID + 1usize; const UNICORN_PREFER_EVENT_TARGET_ID: usize = UNICORN_PREFER_DOM_NODE_TEXT_CONTENT_ID + 1usize; -const UNICORN_PREFER_GLOBAL_THIS_ID: usize = UNICORN_PREFER_EVENT_TARGET_ID + 1usize; +const UNICORN_PREFER_EXPORT_FROM_ID: usize = UNICORN_PREFER_EVENT_TARGET_ID + 1usize; +const UNICORN_PREFER_GLOBAL_THIS_ID: usize = UNICORN_PREFER_EXPORT_FROM_ID + 1usize; const UNICORN_PREFER_INCLUDES_ID: usize = UNICORN_PREFER_GLOBAL_THIS_ID + 1usize; const UNICORN_PREFER_KEYBOARD_EVENT_KEY_ID: usize = UNICORN_PREFER_INCLUDES_ID + 1usize; const UNICORN_PREFER_LOGICAL_OPERATOR_OVER_TERNARY_ID: usize = @@ -2798,6 +2801,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(_) => UNICORN_PREFER_DOM_NODE_REMOVE_ID, Self::UnicornPreferDomNodeTextContent(_) => UNICORN_PREFER_DOM_NODE_TEXT_CONTENT_ID, Self::UnicornPreferEventTarget(_) => UNICORN_PREFER_EVENT_TARGET_ID, + Self::UnicornPreferExportFrom(_) => UNICORN_PREFER_EXPORT_FROM_ID, Self::UnicornPreferGlobalThis(_) => UNICORN_PREFER_GLOBAL_THIS_ID, Self::UnicornPreferIncludes(_) => UNICORN_PREFER_INCLUDES_ID, Self::UnicornPreferKeyboardEventKey(_) => UNICORN_PREFER_KEYBOARD_EVENT_KEY_ID, @@ -3596,6 +3600,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(_) => UnicornPreferDomNodeRemove::NAME, Self::UnicornPreferDomNodeTextContent(_) => UnicornPreferDomNodeTextContent::NAME, Self::UnicornPreferEventTarget(_) => UnicornPreferEventTarget::NAME, + Self::UnicornPreferExportFrom(_) => UnicornPreferExportFrom::NAME, Self::UnicornPreferGlobalThis(_) => UnicornPreferGlobalThis::NAME, Self::UnicornPreferIncludes(_) => UnicornPreferIncludes::NAME, Self::UnicornPreferKeyboardEventKey(_) => UnicornPreferKeyboardEventKey::NAME, @@ -4426,6 +4431,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(_) => UnicornPreferDomNodeRemove::CATEGORY, Self::UnicornPreferDomNodeTextContent(_) => UnicornPreferDomNodeTextContent::CATEGORY, Self::UnicornPreferEventTarget(_) => UnicornPreferEventTarget::CATEGORY, + Self::UnicornPreferExportFrom(_) => UnicornPreferExportFrom::CATEGORY, Self::UnicornPreferGlobalThis(_) => UnicornPreferGlobalThis::CATEGORY, Self::UnicornPreferIncludes(_) => UnicornPreferIncludes::CATEGORY, Self::UnicornPreferKeyboardEventKey(_) => UnicornPreferKeyboardEventKey::CATEGORY, @@ -5235,6 +5241,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(_) => UnicornPreferDomNodeRemove::FIX, Self::UnicornPreferDomNodeTextContent(_) => UnicornPreferDomNodeTextContent::FIX, Self::UnicornPreferEventTarget(_) => UnicornPreferEventTarget::FIX, + Self::UnicornPreferExportFrom(_) => UnicornPreferExportFrom::FIX, Self::UnicornPreferGlobalThis(_) => UnicornPreferGlobalThis::FIX, Self::UnicornPreferIncludes(_) => UnicornPreferIncludes::FIX, Self::UnicornPreferKeyboardEventKey(_) => UnicornPreferKeyboardEventKey::FIX, @@ -6176,6 +6183,7 @@ impl RuleEnum { UnicornPreferDomNodeTextContent::documentation() } Self::UnicornPreferEventTarget(_) => UnicornPreferEventTarget::documentation(), + Self::UnicornPreferExportFrom(_) => UnicornPreferExportFrom::documentation(), Self::UnicornPreferGlobalThis(_) => UnicornPreferGlobalThis::documentation(), Self::UnicornPreferIncludes(_) => UnicornPreferIncludes::documentation(), Self::UnicornPreferKeyboardEventKey(_) => { @@ -7890,6 +7898,8 @@ impl RuleEnum { } Self::UnicornPreferEventTarget(_) => UnicornPreferEventTarget::config_schema(generator) .or_else(|| UnicornPreferEventTarget::schema(generator)), + Self::UnicornPreferExportFrom(_) => UnicornPreferExportFrom::config_schema(generator) + .or_else(|| UnicornPreferExportFrom::schema(generator)), Self::UnicornPreferGlobalThis(_) => UnicornPreferGlobalThis::config_schema(generator) .or_else(|| UnicornPreferGlobalThis::schema(generator)), Self::UnicornPreferIncludes(_) => UnicornPreferIncludes::config_schema(generator) @@ -8937,6 +8947,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(_) => "unicorn", Self::UnicornPreferDomNodeTextContent(_) => "unicorn", Self::UnicornPreferEventTarget(_) => "unicorn", + Self::UnicornPreferExportFrom(_) => "unicorn", Self::UnicornPreferGlobalThis(_) => "unicorn", Self::UnicornPreferIncludes(_) => "unicorn", Self::UnicornPreferKeyboardEventKey(_) => "unicorn", @@ -10770,6 +10781,9 @@ impl RuleEnum { Self::UnicornPreferEventTarget(_) => Ok(Self::UnicornPreferEventTarget( UnicornPreferEventTarget::from_configuration(value)?, )), + Self::UnicornPreferExportFrom(_) => Ok(Self::UnicornPreferExportFrom( + UnicornPreferExportFrom::from_configuration(value)?, + )), Self::UnicornPreferGlobalThis(_) => Ok(Self::UnicornPreferGlobalThis( UnicornPreferGlobalThis::from_configuration(value)?, )), @@ -11884,6 +11898,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(rule) => rule.to_configuration(), Self::UnicornPreferDomNodeTextContent(rule) => rule.to_configuration(), Self::UnicornPreferEventTarget(rule) => rule.to_configuration(), + Self::UnicornPreferExportFrom(rule) => rule.to_configuration(), Self::UnicornPreferGlobalThis(rule) => rule.to_configuration(), Self::UnicornPreferIncludes(rule) => rule.to_configuration(), Self::UnicornPreferKeyboardEventKey(rule) => rule.to_configuration(), @@ -12588,6 +12603,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(rule) => rule.run(node, ctx), Self::UnicornPreferDomNodeTextContent(rule) => rule.run(node, ctx), Self::UnicornPreferEventTarget(rule) => rule.run(node, ctx), + Self::UnicornPreferExportFrom(rule) => rule.run(node, ctx), Self::UnicornPreferGlobalThis(rule) => rule.run(node, ctx), Self::UnicornPreferIncludes(rule) => rule.run(node, ctx), Self::UnicornPreferKeyboardEventKey(rule) => rule.run(node, ctx), @@ -13290,6 +13306,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(rule) => rule.run_once(ctx), Self::UnicornPreferDomNodeTextContent(rule) => rule.run_once(ctx), Self::UnicornPreferEventTarget(rule) => rule.run_once(ctx), + Self::UnicornPreferExportFrom(rule) => rule.run_once(ctx), Self::UnicornPreferGlobalThis(rule) => rule.run_once(ctx), Self::UnicornPreferIncludes(rule) => rule.run_once(ctx), Self::UnicornPreferKeyboardEventKey(rule) => rule.run_once(ctx), @@ -14076,6 +14093,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(rule) => rule.run_on_jest_node(jest_node, ctx), Self::UnicornPreferDomNodeTextContent(rule) => rule.run_on_jest_node(jest_node, ctx), Self::UnicornPreferEventTarget(rule) => rule.run_on_jest_node(jest_node, ctx), + Self::UnicornPreferExportFrom(rule) => rule.run_on_jest_node(jest_node, ctx), Self::UnicornPreferGlobalThis(rule) => rule.run_on_jest_node(jest_node, ctx), Self::UnicornPreferIncludes(rule) => rule.run_on_jest_node(jest_node, ctx), Self::UnicornPreferKeyboardEventKey(rule) => rule.run_on_jest_node(jest_node, ctx), @@ -14792,6 +14810,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(rule) => rule.should_run(ctx), Self::UnicornPreferDomNodeTextContent(rule) => rule.should_run(ctx), Self::UnicornPreferEventTarget(rule) => rule.should_run(ctx), + Self::UnicornPreferExportFrom(rule) => rule.should_run(ctx), Self::UnicornPreferGlobalThis(rule) => rule.should_run(ctx), Self::UnicornPreferIncludes(rule) => rule.should_run(ctx), Self::UnicornPreferKeyboardEventKey(rule) => rule.should_run(ctx), @@ -15718,6 +15737,7 @@ impl RuleEnum { UnicornPreferDomNodeTextContent::IS_TSGOLINT_RULE } Self::UnicornPreferEventTarget(_) => UnicornPreferEventTarget::IS_TSGOLINT_RULE, + Self::UnicornPreferExportFrom(_) => UnicornPreferExportFrom::IS_TSGOLINT_RULE, Self::UnicornPreferGlobalThis(_) => UnicornPreferGlobalThis::IS_TSGOLINT_RULE, Self::UnicornPreferIncludes(_) => UnicornPreferIncludes::IS_TSGOLINT_RULE, Self::UnicornPreferKeyboardEventKey(_) => { @@ -16629,6 +16649,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(_) => UnicornPreferDomNodeRemove::HAS_CONFIG, Self::UnicornPreferDomNodeTextContent(_) => UnicornPreferDomNodeTextContent::HAS_CONFIG, Self::UnicornPreferEventTarget(_) => UnicornPreferEventTarget::HAS_CONFIG, + Self::UnicornPreferExportFrom(_) => UnicornPreferExportFrom::HAS_CONFIG, Self::UnicornPreferGlobalThis(_) => UnicornPreferGlobalThis::HAS_CONFIG, Self::UnicornPreferIncludes(_) => UnicornPreferIncludes::HAS_CONFIG, Self::UnicornPreferKeyboardEventKey(_) => UnicornPreferKeyboardEventKey::HAS_CONFIG, @@ -17361,6 +17382,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(rule) => rule.types_info(), Self::UnicornPreferDomNodeTextContent(rule) => rule.types_info(), Self::UnicornPreferEventTarget(rule) => rule.types_info(), + Self::UnicornPreferExportFrom(rule) => rule.types_info(), Self::UnicornPreferGlobalThis(rule) => rule.types_info(), Self::UnicornPreferIncludes(rule) => rule.types_info(), Self::UnicornPreferKeyboardEventKey(rule) => rule.types_info(), @@ -18063,6 +18085,7 @@ impl RuleEnum { Self::UnicornPreferDomNodeRemove(rule) => rule.run_info(), Self::UnicornPreferDomNodeTextContent(rule) => rule.run_info(), Self::UnicornPreferEventTarget(rule) => rule.run_info(), + Self::UnicornPreferExportFrom(rule) => rule.run_info(), Self::UnicornPreferGlobalThis(rule) => rule.run_info(), Self::UnicornPreferIncludes(rule) => rule.run_info(), Self::UnicornPreferKeyboardEventKey(rule) => rule.run_info(), @@ -18867,6 +18890,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( RuleEnum::UnicornPreferDomNodeRemove(UnicornPreferDomNodeRemove::default()), RuleEnum::UnicornPreferDomNodeTextContent(UnicornPreferDomNodeTextContent::default()), RuleEnum::UnicornPreferEventTarget(UnicornPreferEventTarget::default()), + RuleEnum::UnicornPreferExportFrom(UnicornPreferExportFrom::default()), RuleEnum::UnicornPreferGlobalThis(UnicornPreferGlobalThis::default()), RuleEnum::UnicornPreferIncludes(UnicornPreferIncludes::default()), RuleEnum::UnicornPreferKeyboardEventKey(UnicornPreferKeyboardEventKey::default()), diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 3a5541ecb7ede..062474f2b195b 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -529,6 +529,7 @@ pub(crate) mod unicorn { pub mod prefer_dom_node_remove; pub mod prefer_dom_node_text_content; pub mod prefer_event_target; + pub mod prefer_export_from; pub mod prefer_global_this; pub mod prefer_includes; pub mod prefer_keyboard_event_key; diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_export_from.rs b/crates/oxc_linter/src/rules/unicorn/prefer_export_from.rs new file mode 100644 index 0000000000000..1795e98c9c645 --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/prefer_export_from.rs @@ -0,0 +1,820 @@ +use oxc_ast::{ + AstKind, + ast::{ + BindingPattern, ExportDefaultDeclarationKind, ModuleExportName, Statement, + VariableDeclarationKind, + }, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::{NodeId, ReferenceId, SymbolId}; +use oxc_span::{GetSpan, Span}; +use rustc_hash::{FxHashMap, FxHashSet}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::{ + context::LintContext, + rule::{DefaultRuleConfig, Rule}, +}; + +fn prefer_export_from_diagnostic(span: Span, exported: &str) -> OxcDiagnostic { + OxcDiagnostic::warn(format!("Use `export…from` to re-export `{exported}`.")).with_label(span) +} + +#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(default, rename_all = "camelCase", deny_unknown_fields)] +pub struct PreferExportFromOptions { + ignore_used_variables: bool, +} + +#[derive(Debug, Default, Clone)] +pub struct PreferExportFrom(PreferExportFromOptions); + +declare_oxc_lint!( + /// ### What it does + /// + /// Prefer `export…from` when re-exporting imported bindings. + /// + /// ### Why is this bad? + /// + /// `export … from` is shorter and makes re-exports clearer than importing + /// and then exporting in separate statements. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// import foo from "foo"; + /// export { foo }; + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// export { default as foo } from "foo"; + /// ``` + PreferExportFrom, + unicorn, + style, + config = PreferExportFromOptions +); + +impl Rule for PreferExportFrom { + fn from_configuration(value: serde_json::Value) -> Result { + serde_json::from_value::>(value) + .map(DefaultRuleConfig::into_inner) + .map(Self) + } + + fn run_once(&self, ctx: &LintContext<'_>) { + let import_bindings = collect_import_bindings(ctx); + if import_bindings.is_empty() { + return; + } + + let skipped_import_declarations = if self.0.ignore_used_variables { + collect_skipped_import_declarations(&import_bindings, ctx) + } else { + FxHashSet::default() + }; + + for export_usage in collect_export_usages(ctx) { + let Some(import_binding) = import_bindings.get(&export_usage.symbol_id) else { + continue; + }; + if skipped_import_declarations.contains(&import_binding.declaration_span) { + continue; + } + if import_binding.import_kind == ImportKind::Namespace && export_usage.is_default_export + { + continue; + } + + ctx.diagnostic(prefer_export_from_diagnostic(export_usage.span, &export_usage.text)); + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ImportKind { + Default, + Namespace, + Named, +} + +#[derive(Debug, Clone, Copy)] +struct ImportBinding { + symbol_id: SymbolId, + import_kind: ImportKind, + declaration_span: Span, +} + +#[derive(Debug, Clone)] +struct ExportUsage { + symbol_id: SymbolId, + span: Span, + text: String, + is_default_export: bool, +} + +fn collect_import_bindings(ctx: &LintContext<'_>) -> FxHashMap { + let mut local_name_counts = FxHashMap::::default(); + for statement in &ctx.nodes().program().body { + let Statement::ImportDeclaration(import_declaration) = statement else { + continue; + }; + let Some(specifiers) = &import_declaration.specifiers else { + continue; + }; + + for specifier in specifiers { + let local_name = match specifier { + oxc_ast::ast::ImportDeclarationSpecifier::ImportSpecifier(specifier) => { + specifier.local.name.as_str() + } + oxc_ast::ast::ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => { + specifier.local.name.as_str() + } + oxc_ast::ast::ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => { + specifier.local.name.as_str() + } + }; + *local_name_counts.entry(local_name.to_string()).or_default() += 1; + } + } + + let mut bindings = FxHashMap::::default(); + + for statement in &ctx.nodes().program().body { + let Statement::ImportDeclaration(import_declaration) = statement else { + continue; + }; + let Some(specifiers) = &import_declaration.specifiers else { + continue; + }; + if specifiers.is_empty() { + continue; + } + + let mut declaration_bindings = Vec::with_capacity(specifiers.len()); + let mut skip_declaration = false; + + for specifier in specifiers { + let (local_name, symbol_id, import_kind) = match specifier { + oxc_ast::ast::ImportDeclarationSpecifier::ImportSpecifier(specifier) => ( + specifier.local.name.as_str(), + specifier.local.symbol_id.get(), + ImportKind::Named, + ), + oxc_ast::ast::ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => ( + specifier.local.name.as_str(), + specifier.local.symbol_id.get(), + ImportKind::Default, + ), + oxc_ast::ast::ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => ( + specifier.local.name.as_str(), + specifier.local.symbol_id.get(), + ImportKind::Namespace, + ), + }; + + if local_name_counts.get(local_name).copied().unwrap_or_default() > 1 { + skip_declaration = true; + break; + } + + let Some(symbol_id) = symbol_id else { + skip_declaration = true; + break; + }; + + declaration_bindings.push((symbol_id, import_kind)); + } + + if skip_declaration { + continue; + } + + for (symbol_id, import_kind) in declaration_bindings { + bindings.insert( + symbol_id, + ImportBinding { symbol_id, import_kind, declaration_span: import_declaration.span }, + ); + } + } + + bindings +} + +fn collect_skipped_import_declarations( + import_bindings: &FxHashMap, + ctx: &LintContext<'_>, +) -> FxHashSet { + let mut skipped = FxHashSet::default(); + + for binding in import_bindings.values() { + if ctx + .scoping() + .get_resolved_references(binding.symbol_id) + .any(|reference| !is_reexport_reference(binding.import_kind, reference.node_id(), ctx)) + { + skipped.insert(binding.declaration_span); + } + } + + skipped +} + +fn is_reexport_reference( + import_kind: ImportKind, + reference_node_id: NodeId, + ctx: &LintContext<'_>, +) -> bool { + let node = ctx.nodes().get_node(reference_node_id); + let AstKind::IdentifierReference(_) = node.kind() else { + return false; + }; + + let parent = ctx.nodes().parent_node(node.id()); + match parent.kind() { + AstKind::ExportDefaultDeclaration(_) => import_kind != ImportKind::Namespace, + AstKind::ExportSpecifier(specifier) => { + !(import_kind == ImportKind::Namespace && is_default_export_name(&specifier.exported)) + } + AstKind::VariableDeclarator(_) => { + exported_usage_from_exported_const_assignment(reference_node_id, ctx).is_some() + } + _ => false, + } +} + +fn collect_export_usages(ctx: &LintContext<'_>) -> Vec { + let mut usages = Vec::new(); + + for statement in &ctx.nodes().program().body { + match statement { + Statement::ExportDefaultDeclaration(export_default) => { + let ExportDefaultDeclarationKind::Identifier(identifier_reference) = + &export_default.declaration + else { + continue; + }; + + let Some(symbol_id) = + symbol_id_of_reference(identifier_reference.reference_id(), ctx) + else { + continue; + }; + + usages.push(ExportUsage { + symbol_id, + span: export_default.span, + text: "default".to_string(), + is_default_export: true, + }); + } + Statement::ExportNamedDeclaration(export_named) => { + if export_named.source.is_some() { + continue; + } + + if export_named.declaration.is_none() { + for specifier in &export_named.specifiers { + let ModuleExportName::IdentifierReference(local_reference) = + &specifier.local + else { + continue; + }; + + let Some(symbol_id) = + symbol_id_of_reference(local_reference.reference_id(), ctx) + else { + continue; + }; + + usages.push(ExportUsage { + symbol_id, + span: specifier.span, + text: specifier + .exported + .span() + .source_text(ctx.source_text()) + .to_string(), + is_default_export: is_default_export_name(&specifier.exported), + }); + } + } + + if let Some(declaration) = &export_named.declaration + && let oxc_ast::ast::Declaration::VariableDeclaration(variable_declaration) = + declaration + && variable_declaration.kind == VariableDeclarationKind::Const + && variable_declaration.declarations.len() == 1 + && let Some(export_usage) = + exported_usage_from_exported_const_assignment_in_declaration( + export_named.span, + &variable_declaration.declarations[0], + ctx, + ) + { + usages.push(export_usage); + } + } + _ => {} + } + } + + usages +} + +fn exported_usage_from_exported_const_assignment( + reference_node_id: NodeId, + ctx: &LintContext<'_>, +) -> Option { + let reference_node = ctx.nodes().get_node(reference_node_id); + let AstKind::IdentifierReference(reference_ident) = reference_node.kind() else { + return None; + }; + + let variable_declarator_node = ctx.nodes().parent_node(reference_node.id()); + let AstKind::VariableDeclarator(variable_declarator) = variable_declarator_node.kind() else { + return None; + }; + + let variable_declaration_node = ctx.nodes().parent_node(variable_declarator_node.id()); + let AstKind::VariableDeclaration(variable_declaration) = variable_declaration_node.kind() + else { + return None; + }; + if variable_declaration.kind != VariableDeclarationKind::Const + || variable_declaration.declarations.len() != 1 + { + return None; + } + + let export_named_node = ctx.nodes().parent_node(variable_declaration_node.id()); + let AstKind::ExportNamedDeclaration(export_named_declaration) = export_named_node.kind() else { + return None; + }; + if export_named_declaration.source.is_some() + || !matches!( + export_named_declaration.declaration.as_ref(), + Some(oxc_ast::ast::Declaration::VariableDeclaration(_)) + ) + { + return None; + } + + let imported_symbol_id = symbol_id_of_reference(reference_ident.reference_id(), ctx)?; + + exported_usage_from_exported_const_assignment_in_declaration( + export_named_declaration.span, + variable_declarator, + ctx, + ) + .filter(|usage| usage.symbol_id == imported_symbol_id) +} + +fn exported_usage_from_exported_const_assignment_in_declaration( + export_span: Span, + variable_declarator: &oxc_ast::ast::VariableDeclarator<'_>, + ctx: &LintContext<'_>, +) -> Option { + let init = variable_declarator.init.as_ref()?; + let oxc_ast::ast::Expression::Identifier(identifier_reference) = init.get_inner_expression() + else { + return None; + }; + + let imported_symbol_id = symbol_id_of_reference(identifier_reference.reference_id(), ctx)?; + + if variable_declarator.type_annotation.is_some() { + return None; + } + + let BindingPattern::BindingIdentifier(binding_identifier) = &variable_declarator.id else { + return None; + }; + + let exported_symbol_id = binding_identifier.symbol_id.get()?; + if ctx.scoping().get_resolved_references(exported_symbol_id).next().is_some() { + return None; + } + + Some(ExportUsage { + symbol_id: imported_symbol_id, + span: export_span, + text: binding_identifier.name.as_str().to_string(), + is_default_export: false, + }) +} + +fn symbol_id_of_reference(reference_id: ReferenceId, ctx: &LintContext<'_>) -> Option { + ctx.scoping().get_reference(reference_id).symbol_id() +} + +fn is_default_export_name(module_export_name: &ModuleExportName<'_>) -> bool { + match module_export_name { + ModuleExportName::IdentifierName(identifier_name) => identifier_name.name == "default", + ModuleExportName::IdentifierReference(identifier_reference) => { + identifier_reference.name == "default" + } + ModuleExportName::StringLiteral(string_literal) => string_literal.value == "default", + } +} +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + r#"import "foo";"#, + r#"import {} from "foo";"#, + r#"import * as namespace from "foo";"#, + r#"import defaultExport from "foo";"#, + r#"import {named} from "foo";"#, + "const named = import(foo); + export {named};", + r#"export * from "foo";"#, + r#"export {default} from "foo";"#, + r#"export {named} from "foo";"#, + "const defaultExport = require('foo'); + export default defaultExport;", + "import defaultExport from 'foo'; + export var variable = defaultExport;", + "import defaultExport from 'foo'; + export let variable = defaultExport;", + "import defaultExport from 'foo'; + export const variable = defaultExport; + use(variable);", + "import defaultExport from 'foo'; + export let variable = defaultExport; + variable = 1;", + "import * as namespace from 'foo'; + export default namespace;", + "import * as namespace from 'foo'; + export {namespace as default};", + "import defaultExport from 'foo'; + const variable = defaultExport; + export {variable}", + "import defaultExport from 'foo'; + export const {variable} = {variable: defaultExport};", + "import {useDispatch as reduxUseDispatch} from 'react-redux' + type MyDispatchType = Dispatch + export const useDispatch: () => DispatchAllActions = reduxUseDispatch", + r#"export type { bar, foo } from "foo";"#, + ]; + + let fail = vec![ + "import defaultExport from 'foo'; + export default defaultExport;", + "import defaultExport from 'foo'; + export {defaultExport as default};", + "import defaultExport from 'foo'; + export {defaultExport as named};", + "import defaultExport from 'foo'; + export const variable = defaultExport;", + "import {default as defaultExport} from 'foo'; + export default defaultExport;", + "import {default as defaultExport} from 'foo'; + export {defaultExport as default};", + "import {default as defaultExport} from 'foo'; + export {defaultExport as named};", + "import defaultExport from 'foo'; + export const variable = defaultExport;", + "import defaultExport from 'foo'; + defaultExport.bar = 1; + export {defaultExport as named}; + export {defaultExport as default}; + export const variable = defaultExport;", + "import {named} from 'foo'; + export default named;", + "import {named} from 'foo'; + export {named as default};", + "import {named} from 'foo'; + export {named as named};", + "import {named} from 'foo'; + export {named as renamed};", + "import {named} from 'foo'; + export const variable = named;", + "import {named} from 'foo'; + named.bar = 1; + export {named as named}; + export {named as default}; + export const variable = named;", + "import * as namespace from 'foo'; + export {namespace as namespace};", + "import * as namespace from 'foo'; + export {namespace as renamed};", + "import * as namespace from 'foo'; + export const variable = namespace;", + "import * as namespace from 'foo'; + namespace.bar = 1; + export {namespace as named}; + export {namespace as default}; + export const variable = namespace;", + "import {named1, named2} from 'foo'; + export {named1};", + "import defaultExport, {named} from 'foo'; + export {defaultExport};", + "import defaultExport, {named} from 'foo'; + export {named};", + "import defaultExport, * as namespace from 'foo'; + export {defaultExport};", + "import * as foo from 'foo'; + export {foo}; + export * as bar from 'foo';", + "import * as foo from 'foo'; + export {foo}; + export {bar} from 'foo';", + "import * as foo from 'foo'; + export {foo}; + export {} from 'foo';", + "import * as foo from 'foo'; + export {foo}; + export * from 'foo';", + "import foo from 'foo'; + export {foo}; + export * as bar from 'foo';", + "import foo from 'foo'; + export {foo}; + export {bar} from 'foo';", + "import foo from 'foo'; + export {foo}; + export {bar,} from 'foo';", + "import foo from 'foo'; + export {foo}; + export {} from 'foo';", + "import foo from 'foo'; + export {foo}; + export * from 'foo';", + "import {named1, named2} from 'foo'; + export {named1, named2};", + "import {named} from 'foo'; + export {named as default, named};", + "import {named, named as renamed} from 'foo'; + export {named, renamed};", + "import defaultExport, {named1, named2} from 'foo'; + export {named1 as default}; + export {named2}; + export {defaultExport};", + "import * as foo from 'foo'; + import * as bar from 'foo'; + export {foo, bar};", + "import * as foo from 'foo'; + export {foo, foo as bar};", + "import defaultExport from 'foo'; + export * from 'foo'; + export default defaultExport;", + "import defaultExport from 'foo'; + export {named} from 'foo'; + export * from 'foo'; + export default defaultExport;", + "import defaultExport from './foo.js'; + export {named} from './foo.js'; + export default defaultExport;", + "import defaultExport from './foo.js'; + export {named} from './foo.js?query'; + export default defaultExport;", + "import * as namespace from 'foo'; + export default namespace; + export {namespace};", + "import * as namespace from 'foo'; + export {namespace}; + export default namespace;", + "import {'foo' as foo} from 'foo'; + export default foo;", + "import {'foo' as foo} from 'foo'; + export {foo};", + "import {'foo' as foo} from 'foo'; + export const bar = foo;", + "import {'foo' as foo} from 'foo'; + export {foo as 'foo'};", + r#"import {'foo' as foo} from 'foo'; + export {foo as "foo"};"#, + r#"import {'qu\\\\u{20}x' as foo} from 'foo'; + export {foo as "qu x"};"#, + r#"import {'qu\\\\nx' as foo} from 'foo'; + export {foo as "qu\\\\u000ax"};"#, + "import {'default' as foo} from 'foo'; + export {foo};", + "import {'default' as foo} from 'foo'; + export default foo;", + "import {'*' as foo} from 'foo'; + export {foo};", + r#"import { foo } from "foo"; + export { foo }; + export type { bar } from "foo";"#, + r#"import { foo } from "foo"; + export { foo }; + export { type bar } from "foo";"#, + r#"import { foo } from 'foo'; + export { foo }; + export type { bar } from "foo"; + export { baz } from "foo";"#, + r#"import { foo } from 'foo'; + export { foo }; + export { type bar } from "foo"; + export { baz } from "foo";"#, + r#"import type { foo } from "foo"; + export type { foo }; + export type { bar } from "foo";"#, + r#"import { foo } from 'foo'; + export { foo }; + export { baz } from "foo"; + export { type bar } from "foo";"#, + r#"import type { foo } from "foo"; + export type { foo }; + export { type bar } from "foo";"#, + r#"import type { foo } from 'foo'; + export type { foo }; + export type { bar } from "foo"; + export { baz } from "foo";"#, + r#"import type { foo } from 'foo'; + export type { foo }; + export { baz } from "foo"; + export type { bar } from "foo";"#, + r#"import type { foo } from 'foo'; + export type { foo }; + export { type bar } from "foo"; + export { baz } from "foo";"#, + r#"import type { foo } from 'foo'; + export type { foo }; + export { baz } from "foo"; + export { type bar } from "foo";"#, + "import { type foo } from 'foo'; + export type { foo };", + "import { foo } from 'foo'; + export type { foo };", + "import type { foo } from 'foo'; + export { type foo };", + r#"import type foo from "foo"; + export default foo"#, + "import {type foo} from 'foo'; + export {type foo as bar};", + "import {type foo} from 'foo'; + export {type foo as bar}; + export {type bar} from 'foo';", + "import {type foo as bar} from 'foo'; + export {type bar as baz};", + "import {type foo as foo} from 'foo'; + export {type foo as bar};", + "import {type foo as bar} from 'foo'; + export {type bar as bar};", + "import {type foo as bar} from 'foo'; + export {type bar as foo};", + "import json from './foo.json' assert { type: 'json' }; + export default json;", + "import * as json from './foo.json' assert { type: 'json' }; + export {json};", + "import {foo} from './foo.json' assert { type: 'unknown' }; + export {foo}; + export {bar} from './foo.json';", + "import {foo} from './foo.json'; + export {foo}; + export {bar} from './foo.json' assert { type: 'unknown' };", + "import json from './foo.json' with { type: 'json' }; + export default json;", + "import * as json from './foo.json' with { type: 'json' }; + export {json};", + "import {foo} from './foo.json' with { type: 'unknown' }; + export {foo}; + export {bar} from './foo.json';", + "import {foo} from './foo.json'; + export {foo}; + export {bar} from './foo.json' with { type: 'unknown' };", + ]; + + Tester::new(PreferExportFrom::NAME, PreferExportFrom::PLUGIN, pass, fail).test_and_snapshot(); +} + +#[test] +fn test_ignore_used_variables_option() { + use crate::tester::Tester; + + let pass = vec![ + ( + "import defaultExport from 'foo'; + use(defaultExport); + export default defaultExport;", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import defaultExport from 'foo'; + use(defaultExport); + export {defaultExport};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import {named} from 'foo'; + use(named); + export {named};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import {named} from 'foo'; + use(named); + export default named;", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import * as namespace from 'foo'; + use(namespace); + export {namespace};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import * as namespace from 'foo'; + use(namespace); + export default namespace;", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import * as namespace from 'foo'; + export {namespace as default}; + export {namespace as named};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import * as namespace from 'foo'; + export default namespace; + export {namespace as named};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import defaultExport, {named} from 'foo'; + use(defaultExport); + export {named};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import defaultExport, {named} from 'foo'; + use(named); + export {defaultExport};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import {named1, named2} from 'foo'; + use(named1); + export {named2};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import defaultExport, {named1, named2} from 'foo'; + use(defaultExport); + export {named1, named2};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import defaultExport, {named1, named2} from 'foo'; + use(named1); + export {defaultExport, named2};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ]; + + let fail = vec![ + ( + "import defaultExport from 'foo'; + export {defaultExport as default}; + export {defaultExport as named};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import {named} from 'foo'; + export {named as default}; + export {named as named};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import {named} from 'foo'; + export default named; + export {named as named};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import defaultExport, {named} from 'foo'; + export default defaultExport; + export {named};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import defaultExport, {named} from 'foo'; + export {defaultExport as default, named};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import defaultExport from 'foo'; + export const variable = defaultExport;", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ( + "import {notUsedNotExported, exported} from 'foo'; + export {exported};", + Some(serde_json::json!([{ "ignoreUsedVariables": true }])), + ), + ]; + + Tester::new(PreferExportFrom::NAME, PreferExportFrom::PLUGIN, pass, fail) + .with_snapshot_suffix("ignore_used_variables") + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/unicorn_prefer_export_from.snap b/crates/oxc_linter/src/snapshots/unicorn_prefer_export_from.snap new file mode 100644 index 0000000000000..98b0545bcbe2e --- /dev/null +++ b/crates/oxc_linter/src/snapshots/unicorn_prefer_export_from.snap @@ -0,0 +1,701 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import defaultExport from 'foo'; + 2 │ export default defaultExport; + · ───────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import defaultExport from 'foo'; + 2 │ export {defaultExport as default}; + · ──────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import defaultExport from 'foo'; + 2 │ export {defaultExport as named}; + · ────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `variable`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import defaultExport from 'foo'; + 2 │ export const variable = defaultExport; + · ────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import {default as defaultExport} from 'foo'; + 2 │ export default defaultExport; + · ───────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {default as defaultExport} from 'foo'; + 2 │ export {defaultExport as default}; + · ──────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {default as defaultExport} from 'foo'; + 2 │ export {defaultExport as named}; + · ────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `variable`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import defaultExport from 'foo'; + 2 │ export const variable = defaultExport; + · ────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:3:21] + 2 │ defaultExport.bar = 1; + 3 │ export {defaultExport as named}; + · ────────────────────── + 4 │ export {defaultExport as default}; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:4:21] + 3 │ export {defaultExport as named}; + 4 │ export {defaultExport as default}; + · ──────────────────────── + 5 │ export const variable = defaultExport; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `variable`. + ╭─[prefer_export_from.tsx:5:13] + 4 │ export {defaultExport as default}; + 5 │ export const variable = defaultExport; + · ────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import {named} from 'foo'; + 2 │ export default named; + · ───────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {named} from 'foo'; + 2 │ export {named as default}; + · ──────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {named} from 'foo'; + 2 │ export {named as named}; + · ────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `renamed`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {named} from 'foo'; + 2 │ export {named as renamed}; + · ──────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `variable`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import {named} from 'foo'; + 2 │ export const variable = named; + · ────────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:3:21] + 2 │ named.bar = 1; + 3 │ export {named as named}; + · ────────────── + 4 │ export {named as default}; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:4:21] + 3 │ export {named as named}; + 4 │ export {named as default}; + · ──────────────── + 5 │ export const variable = named; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `variable`. + ╭─[prefer_export_from.tsx:5:13] + 4 │ export {named as default}; + 5 │ export const variable = named; + · ────────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `namespace`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import * as namespace from 'foo'; + 2 │ export {namespace as namespace}; + · ────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `renamed`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import * as namespace from 'foo'; + 2 │ export {namespace as renamed}; + · ──────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `variable`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import * as namespace from 'foo'; + 2 │ export const variable = namespace; + · ────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:3:21] + 2 │ namespace.bar = 1; + 3 │ export {namespace as named}; + · ────────────────── + 4 │ export {namespace as default}; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `variable`. + ╭─[prefer_export_from.tsx:5:13] + 4 │ export {namespace as default}; + 5 │ export const variable = namespace; + · ────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named1`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {named1, named2} from 'foo'; + 2 │ export {named1}; + · ────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `defaultExport`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import defaultExport, {named} from 'foo'; + 2 │ export {defaultExport}; + · ───────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import defaultExport, {named} from 'foo'; + 2 │ export {named}; + · ───── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `defaultExport`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import defaultExport, * as namespace from 'foo'; + 2 │ export {defaultExport}; + · ───────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import * as foo from 'foo'; + 2 │ export {foo}; + · ─── + 3 │ export * as bar from 'foo'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import * as foo from 'foo'; + 2 │ export {foo}; + · ─── + 3 │ export {bar} from 'foo'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import * as foo from 'foo'; + 2 │ export {foo}; + · ─── + 3 │ export {} from 'foo'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import * as foo from 'foo'; + 2 │ export {foo}; + · ─── + 3 │ export * from 'foo'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import foo from 'foo'; + 2 │ export {foo}; + · ─── + 3 │ export * as bar from 'foo'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import foo from 'foo'; + 2 │ export {foo}; + · ─── + 3 │ export {bar} from 'foo'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import foo from 'foo'; + 2 │ export {foo}; + · ─── + 3 │ export {bar,} from 'foo'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import foo from 'foo'; + 2 │ export {foo}; + · ─── + 3 │ export {} from 'foo'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import foo from 'foo'; + 2 │ export {foo}; + · ─── + 3 │ export * from 'foo'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named1`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {named1, named2} from 'foo'; + 2 │ export {named1, named2}; + · ────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named2`. + ╭─[prefer_export_from.tsx:2:29] + 1 │ import {named1, named2} from 'foo'; + 2 │ export {named1, named2}; + · ────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {named} from 'foo'; + 2 │ export {named as default, named}; + · ──────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:2:39] + 1 │ import {named} from 'foo'; + 2 │ export {named as default, named}; + · ───── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {named, named as renamed} from 'foo'; + 2 │ export {named, renamed}; + · ───── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `renamed`. + ╭─[prefer_export_from.tsx:2:28] + 1 │ import {named, named as renamed} from 'foo'; + 2 │ export {named, renamed}; + · ─────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import defaultExport, {named1, named2} from 'foo'; + 2 │ export {named1 as default}; + · ───────────────── + 3 │ export {named2}; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named2`. + ╭─[prefer_export_from.tsx:3:21] + 2 │ export {named1 as default}; + 3 │ export {named2}; + · ────── + 4 │ export {defaultExport}; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `defaultExport`. + ╭─[prefer_export_from.tsx:4:21] + 3 │ export {named2}; + 4 │ export {defaultExport}; + · ───────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:3:21] + 2 │ import * as bar from 'foo'; + 3 │ export {foo, bar}; + · ─── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `bar`. + ╭─[prefer_export_from.tsx:3:26] + 2 │ import * as bar from 'foo'; + 3 │ export {foo, bar}; + · ─── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import * as foo from 'foo'; + 2 │ export {foo, foo as bar}; + · ─── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `bar`. + ╭─[prefer_export_from.tsx:2:26] + 1 │ import * as foo from 'foo'; + 2 │ export {foo, foo as bar}; + · ────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:3:13] + 2 │ export * from 'foo'; + 3 │ export default defaultExport; + · ───────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:4:13] + 3 │ export * from 'foo'; + 4 │ export default defaultExport; + · ───────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:3:13] + 2 │ export {named} from './foo.js'; + 3 │ export default defaultExport; + · ───────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:3:13] + 2 │ export {named} from './foo.js?query'; + 3 │ export default defaultExport; + · ───────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `namespace`. + ╭─[prefer_export_from.tsx:3:21] + 2 │ export default namespace; + 3 │ export {namespace}; + · ───────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `namespace`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import * as namespace from 'foo'; + 2 │ export {namespace}; + · ───────── + 3 │ export default namespace; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import {'foo' as foo} from 'foo'; + 2 │ export default foo; + · ─────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {'foo' as foo} from 'foo'; + 2 │ export {foo}; + · ─── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `bar`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import {'foo' as foo} from 'foo'; + 2 │ export const bar = foo; + · ─────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `'foo'`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {'foo' as foo} from 'foo'; + 2 │ export {foo as 'foo'}; + · ──────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `"foo"`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {'foo' as foo} from 'foo'; + 2 │ export {foo as "foo"}; + · ──────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `"qu x"`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {'qu\\\\u{20}x' as foo} from 'foo'; + 2 │ export {foo as "qu x"}; + · ───────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `"qu\\\\u000ax"`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {'qu\\\\nx' as foo} from 'foo'; + 2 │ export {foo as "qu\\\\u000ax"}; + · ───────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {'default' as foo} from 'foo'; + 2 │ export {foo}; + · ─── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import {'default' as foo} from 'foo'; + 2 │ export default foo; + · ─────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {'*' as foo} from 'foo'; + 2 │ export {foo}; + · ─── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:22] + 1 │ import { foo } from "foo"; + 2 │ export { foo }; + · ─── + 3 │ export type { bar } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:22] + 1 │ import { foo } from "foo"; + 2 │ export { foo }; + · ─── + 3 │ export { type bar } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:22] + 1 │ import { foo } from 'foo'; + 2 │ export { foo }; + · ─── + 3 │ export type { bar } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:22] + 1 │ import { foo } from 'foo'; + 2 │ export { foo }; + · ─── + 3 │ export { type bar } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:27] + 1 │ import type { foo } from "foo"; + 2 │ export type { foo }; + · ─── + 3 │ export type { bar } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:22] + 1 │ import { foo } from 'foo'; + 2 │ export { foo }; + · ─── + 3 │ export { baz } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:27] + 1 │ import type { foo } from "foo"; + 2 │ export type { foo }; + · ─── + 3 │ export { type bar } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:27] + 1 │ import type { foo } from 'foo'; + 2 │ export type { foo }; + · ─── + 3 │ export type { bar } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:27] + 1 │ import type { foo } from 'foo'; + 2 │ export type { foo }; + · ─── + 3 │ export { baz } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:27] + 1 │ import type { foo } from 'foo'; + 2 │ export type { foo }; + · ─── + 3 │ export { type bar } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:27] + 1 │ import type { foo } from 'foo'; + 2 │ export type { foo }; + · ─── + 3 │ export { baz } from "foo"; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:27] + 1 │ import { type foo } from 'foo'; + 2 │ export type { foo }; + · ─── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:27] + 1 │ import { foo } from 'foo'; + 2 │ export type { foo }; + · ─── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:22] + 1 │ import type { foo } from 'foo'; + 2 │ export { type foo }; + · ──────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import type foo from "foo"; + 2 │ export default foo + · ────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `bar`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {type foo} from 'foo'; + 2 │ export {type foo as bar}; + · ─────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `bar`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {type foo} from 'foo'; + 2 │ export {type foo as bar}; + · ─────────────── + 3 │ export {type bar} from 'foo'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `baz`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {type foo as bar} from 'foo'; + 2 │ export {type bar as baz}; + · ─────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `bar`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {type foo as foo} from 'foo'; + 2 │ export {type foo as bar}; + · ─────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `bar`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {type foo as bar} from 'foo'; + 2 │ export {type bar as bar}; + · ─────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {type foo as bar} from 'foo'; + 2 │ export {type bar as foo}; + · ─────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import json from './foo.json' assert { type: 'json' }; + 2 │ export default json; + · ──────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `json`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import * as json from './foo.json' assert { type: 'json' }; + 2 │ export {json}; + · ──── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {foo} from './foo.json' assert { type: 'unknown' }; + 2 │ export {foo}; + · ─── + 3 │ export {bar} from './foo.json'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {foo} from './foo.json'; + 2 │ export {foo}; + · ─── + 3 │ export {bar} from './foo.json' assert { type: 'unknown' }; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import json from './foo.json' with { type: 'json' }; + 2 │ export default json; + · ──────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `json`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import * as json from './foo.json' with { type: 'json' }; + 2 │ export {json}; + · ──── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {foo} from './foo.json' with { type: 'unknown' }; + 2 │ export {foo}; + · ─── + 3 │ export {bar} from './foo.json'; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `foo`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {foo} from './foo.json'; + 2 │ export {foo}; + · ─── + 3 │ export {bar} from './foo.json' with { type: 'unknown' }; + ╰──── diff --git a/crates/oxc_linter/src/snapshots/unicorn_prefer_export_from@ignore_used_variables.snap b/crates/oxc_linter/src/snapshots/unicorn_prefer_export_from@ignore_used_variables.snap new file mode 100644 index 0000000000000..c330d5a988d1d --- /dev/null +++ b/crates/oxc_linter/src/snapshots/unicorn_prefer_export_from@ignore_used_variables.snap @@ -0,0 +1,91 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import defaultExport from 'foo'; + 2 │ export {defaultExport as default}; + · ──────────────────────── + 3 │ export {defaultExport as named}; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:3:21] + 2 │ export {defaultExport as default}; + 3 │ export {defaultExport as named}; + · ────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {named} from 'foo'; + 2 │ export {named as default}; + · ──────────────── + 3 │ export {named as named}; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:3:21] + 2 │ export {named as default}; + 3 │ export {named as named}; + · ────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import {named} from 'foo'; + 2 │ export default named; + · ───────────────────── + 3 │ export {named as named}; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:3:21] + 2 │ export default named; + 3 │ export {named as named}; + · ────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import defaultExport, {named} from 'foo'; + 2 │ export default defaultExport; + · ───────────────────────────── + 3 │ export {named}; + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:3:21] + 2 │ export default defaultExport; + 3 │ export {named}; + · ───── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `default`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import defaultExport, {named} from 'foo'; + 2 │ export {defaultExport as default, named}; + · ──────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `named`. + ╭─[prefer_export_from.tsx:2:47] + 1 │ import defaultExport, {named} from 'foo'; + 2 │ export {defaultExport as default, named}; + · ───── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `variable`. + ╭─[prefer_export_from.tsx:2:13] + 1 │ import defaultExport from 'foo'; + 2 │ export const variable = defaultExport; + · ────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-unicorn(prefer-export-from): Use `export…from` to re-export `exported`. + ╭─[prefer_export_from.tsx:2:21] + 1 │ import {notUsedNotExported, exported} from 'foo'; + 2 │ export {exported}; + · ──────── + ╰────