diff --git a/crates/oxc_linter/src/rules/import/no_cycle.rs b/crates/oxc_linter/src/rules/import/no_cycle.rs index f7c75fb273352..b677d09ac7d76 100644 --- a/crates/oxc_linter/src/rules/import/no_cycle.rs +++ b/crates/oxc_linter/src/rules/import/no_cycle.rs @@ -102,7 +102,7 @@ impl Rule for NoCycle { let mut state = State::default(); if self.detect_cycle(&mut state, module_record, needle) { let stack = &state.stack; - let span = module_record.requested_modules.get(&stack[0].0).unwrap()[0]; + let span = module_record.requested_modules.get(&stack[0].0).unwrap()[0].span(); let help = stack .iter() .map(|(specifier, path)| { diff --git a/crates/oxc_linter/src/rules/import/no_duplicates.rs b/crates/oxc_linter/src/rules/import/no_duplicates.rs index c89f1f7897414..370c9cdcd387f 100644 --- a/crates/oxc_linter/src/rules/import/no_duplicates.rs +++ b/crates/oxc_linter/src/rules/import/no_duplicates.rs @@ -1,8 +1,7 @@ use itertools::Itertools; use oxc_diagnostics::miette::{miette, LabeledSpan, Severity}; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; -use oxc_syntax::module_record::ImportImportName; +use oxc_syntax::module_record::{ImportImportName, RequestedModule}; use crate::{context::LintContext, rule::Rule}; @@ -36,20 +35,22 @@ impl Rule for NoDuplicates { let groups = module_record .requested_modules .iter() - .map(|(source, spans)| { + .map(|(source, requested_modules)| { let resolved_absolute_path = module_record.loaded_modules.get(source).map_or_else( || source.to_string(), |module| module.resolved_absolute_path.to_string_lossy().to_string(), ); - (resolved_absolute_path, spans) + (resolved_absolute_path, requested_modules) }) .group_by(|r| r.0.clone()); - let check_duplicates = |spans: Option<&Vec<&Span>>| { - if let Some(spans) = spans { - if spans.len() > 1 { - let labels = - spans.iter().map(|span| LabeledSpan::underline(**span)).collect::>(); + let check_duplicates = |requested_modules: Option<&Vec<&RequestedModule>>| { + if let Some(requested_modules) = requested_modules { + if requested_modules.len() > 1 { + let labels = requested_modules + .iter() + .map(|requested_module| LabeledSpan::underline(requested_module.span())) + .collect::>(); ctx.diagnostic(miette!( severity = Severity::Warning, labels = labels, @@ -63,14 +64,16 @@ impl Rule for NoDuplicates { let has_type_import = module_record.import_entries.iter().any(|entry| entry.is_type); // When prefer_inline is false, 0 is value, 1 is type named, 2 is type default or type namespace // When prefer_inline is true, 0 is value and type named, 2 is type default or type namespace - let import_entries_maps = - group.into_iter().flat_map(|(_path, spans)| spans).into_group_map_by(|span| { + let import_entries_maps = group + .into_iter() + .flat_map(|(_path, requested_modules)| requested_modules) + .into_group_map_by(|requested_module| { // We should early return if there is no type import if !has_type_import { return 0; }; for entry in &module_record.import_entries { - if entry.module_request.span() != **span { + if entry.module_request.span() != requested_module.span() { continue; } diff --git a/crates/oxc_linter/src/rules/import/no_self_import.rs b/crates/oxc_linter/src/rules/import/no_self_import.rs index f51c742c30f97..7de47cff613bc 100644 --- a/crates/oxc_linter/src/rules/import/no_self_import.rs +++ b/crates/oxc_linter/src/rules/import/no_self_import.rs @@ -35,13 +35,13 @@ impl Rule for NoSelfImport { fn run_once(&self, ctx: &LintContext<'_>) { let module_record = ctx.semantic().module_record(); let resolved_absolute_path = &module_record.resolved_absolute_path; - for (request, spans) in &module_record.requested_modules { + for (request, requested_modules) in &module_record.requested_modules { let Some(remote_module_record_ref) = module_record.loaded_modules.get(request) else { continue; }; if remote_module_record_ref.value().resolved_absolute_path == *resolved_absolute_path { - for span in spans { - ctx.diagnostic(NoSelfImportDiagnostic(*span)); + for requested_module in requested_modules { + ctx.diagnostic(NoSelfImportDiagnostic(requested_module.span())); } } } diff --git a/crates/oxc_linter/src/rules/import/no_unresolved.rs b/crates/oxc_linter/src/rules/import/no_unresolved.rs index ec7362a7a9ab4..eabe73a6eac7a 100644 --- a/crates/oxc_linter/src/rules/import/no_unresolved.rs +++ b/crates/oxc_linter/src/rules/import/no_unresolved.rs @@ -32,7 +32,7 @@ impl Rule for NoUnresolved { fn run_once(&self, ctx: &LintContext<'_>) { let module_record = ctx.semantic().module_record(); - for (specifier, spans) in &module_record.requested_modules { + for (specifier, requested_modules) in &module_record.requested_modules { if module_record.loaded_modules.contains_key(specifier) { continue; } @@ -53,8 +53,9 @@ impl Rule for NoUnresolved { { continue; } - for span in spans { - ctx.diagnostic(NoUnresolvedDiagnostic(*span)); + + for requested_module in requested_modules { + ctx.diagnostic(NoUnresolvedDiagnostic(requested_module.span())); } } } diff --git a/crates/oxc_semantic/src/module_record/builder.rs b/crates/oxc_semantic/src/module_record/builder.rs index 5939bfa37066a..454ba269a5df4 100644 --- a/crates/oxc_semantic/src/module_record/builder.rs +++ b/crates/oxc_semantic/src/module_record/builder.rs @@ -37,12 +37,12 @@ impl ModuleRecordBuilder { self.module_record } - fn add_module_request(&mut self, name_span: &NameSpan) { + fn add_module_request(&mut self, name_span: &NameSpan, is_type: bool) { self.module_record .requested_modules .entry(name_span.name().clone()) .or_default() - .push(name_span.span()); + .push(RequestedModule::new(name_span.span(), is_type)); } fn add_import_entry(&mut self, entry: ImportEntry) { @@ -202,7 +202,7 @@ impl ModuleRecordBuilder { }); } } - self.add_module_request(&module_request); + self.add_module_request(&module_request, decl.import_kind.is_type()); } fn visit_export_all_declaration(&mut self, decl: &ExportAllDeclaration) { @@ -226,7 +226,7 @@ impl ModuleRecordBuilder { if let Some(exported_name) = &decl.exported { self.add_export_binding(exported_name.name().to_compact_str(), exported_name.span()); } - self.add_module_request(&module_request); + self.add_module_request(&module_request, decl.export_kind.is_type()); } fn visit_export_default_declaration(&mut self, decl: &ExportDefaultDeclaration) { @@ -273,7 +273,7 @@ impl ModuleRecordBuilder { .map(|source| NameSpan::new(source.value.to_compact_str(), source.span)); if let Some(module_request) = &module_request { - self.add_module_request(module_request); + self.add_module_request(module_request, decl.export_kind.is_type()); } if let Some(decl) = &decl.declaration { diff --git a/crates/oxc_syntax/src/module_record.rs b/crates/oxc_syntax/src/module_record.rs index b6ed6638d3971..cb742c78f7631 100644 --- a/crates/oxc_syntax/src/module_record.rs +++ b/crates/oxc_syntax/src/module_record.rs @@ -32,7 +32,7 @@ pub struct ModuleRecord { /// import ModuleSpecifier /// export ExportFromClause FromClause /// Keyed by ModuleSpecifier, valued by all node occurrences - pub requested_modules: IndexMap, BuildHasherDefault>, + pub requested_modules: IndexMap, BuildHasherDefault>, /// `[[LoadedModules]]` /// @@ -267,6 +267,26 @@ pub struct FunctionMeta { pub deprecated: bool, } +#[derive(Debug, Clone)] +pub struct RequestedModule { + span: Span, + is_type: bool, +} + +impl RequestedModule { + pub fn new(span: Span, is_type: bool) -> Self { + Self { span, is_type } + } + + pub fn span(&self) -> Span { + self.span + } + + pub fn is_type(&self) -> bool { + self.is_type + } +} + #[cfg(test)] mod test { use super::{ExportExportName, ExportLocalName, ImportImportName, NameSpan};