feat(linter/import): improve multiple exports error message (#3160)

https://github.com/oxc-project/oxc/issues/3157#issuecomment-2094113518
This commit is contained in:
Dunqing 2024-05-04 22:14:26 +08:00 committed by GitHub
parent a7940868c6
commit f88f330426
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 60 additions and 43 deletions

View file

@ -1,8 +1,9 @@
use std::path::PathBuf; use std::path::PathBuf;
use oxc_diagnostics::{ use oxc_diagnostics::{
miette::{self, Diagnostic}, miette::{self, miette, Diagnostic, LabeledSpan},
thiserror::{self, Error}, thiserror::{self, Error},
Severity,
}; };
use oxc_macros::declare_oxc_lint; use oxc_macros::declare_oxc_lint;
use oxc_semantic::ModuleRecord; use oxc_semantic::ModuleRecord;
@ -13,9 +14,6 @@ use crate::{context::LintContext, rule::Rule};
#[derive(Debug, Error, Diagnostic)] #[derive(Debug, Error, Diagnostic)]
enum ExportDiagnostic { enum ExportDiagnostic {
#[error("eslint-plugin-import(export): Multiple exports of name '{1}'.")]
#[diagnostic(severity(warning))]
MultipleNamedExport(#[label] Span, CompactStr),
#[error("eslint-plugin-import(export): No named exports found in module '{1}'")] #[error("eslint-plugin-import(export): No named exports found in module '{1}'")]
#[diagnostic(severity(warning))] #[diagnostic(severity(warning))]
NoNamedExport(#[label] Span, CompactStr), NoNamedExport(#[label] Span, CompactStr),
@ -43,55 +41,79 @@ impl Rule for Export {
fn run_once(&self, ctx: &LintContext<'_>) { fn run_once(&self, ctx: &LintContext<'_>) {
let module_record = ctx.semantic().module_record(); let module_record = ctx.semantic().module_record();
let named_export = &module_record.exported_bindings; let named_export = &module_record.exported_bindings;
let mut duplicated_named_export = FxHashMap::default();
for export_entry in &module_record.star_export_entries { let mut all_export_names = FxHashMap::default();
let Some(module_request) = &export_entry.module_request else { let mut visited = FxHashSet::default();
continue;
}; module_record.star_export_entries.iter().for_each(|star_export_entry| {
let mut export_names = FxHashSet::default();
let Some(module_request) = &star_export_entry.module_request else { return };
let Some(remote_module_record_ref) = let Some(remote_module_record_ref) =
module_record.loaded_modules.get(module_request.name()) module_record.loaded_modules.get(module_request.name())
else { else {
continue; return;
}; };
let remote_module_record = remote_module_record_ref.value(); walk_exported_recursive(
let mut all_export_names = FxHashSet::default(); remote_module_record_ref.value(),
let mut visited = FxHashSet::default(); &mut export_names,
walk_exported_recursive(remote_module_record, &mut all_export_names, &mut visited); &mut visited,
if all_export_names.is_empty() { );
if export_names.is_empty() {
ctx.diagnostic(ExportDiagnostic::NoNamedExport( ctx.diagnostic(ExportDiagnostic::NoNamedExport(
module_request.span(), module_request.span(),
module_request.name().clone(), module_request.name().clone(),
)); ));
continue; } else {
all_export_names.insert(star_export_entry.span, export_names);
} }
});
for name in &all_export_names { for (name, span) in named_export {
if let Some(span) = named_export.get(name) { let mut spans = all_export_names
duplicated_named_export.entry(*span).or_insert_with(|| name.clone()); .iter()
.filter_map(|(star_export_entry_span, export_names)| {
if export_names.contains(name) {
Some(*star_export_entry_span)
} else {
None
}
})
.collect::<Vec<_>>();
for name_span in &module_record.exported_bindings_duplicated {
if name == name_span.name() {
spans.push(name_span.span());
} }
} }
}
for (span, name) in duplicated_named_export { if !spans.is_empty() {
ctx.diagnostic(ExportDiagnostic::MultipleNamedExport(span, name)); spans.push(*span);
} let labels = spans.into_iter().map(LabeledSpan::underline).collect::<Vec<_>>();
for name_span in &module_record.exported_bindings_duplicated { ctx.diagnostic(miette!(
let name = name_span.name().clone(); severity = Severity::Warning,
if let Some(span) = module_record.exported_bindings.get(&name) { labels = labels,
ctx.diagnostic(ExportDiagnostic::MultipleNamedExport(*span, name.clone())); "eslint-plugin-import(export): Multiple exports of name '{name}'."
));
} }
ctx.diagnostic(ExportDiagnostic::MultipleNamedExport(name_span.span(), name));
} }
if !module_record.export_default_duplicated.is_empty() { if !module_record.export_default_duplicated.is_empty() {
let mut spans = module_record.export_default_duplicated.clone();
if let Some(span) = module_record.export_default { if let Some(span) = module_record.export_default {
ctx.diagnostic(ExportDiagnostic::MultipleNamedExport(span, "default".into())); spans.push(span);
let labels = spans.into_iter().map(LabeledSpan::underline).collect::<Vec<_>>();
ctx.diagnostic(miette!(
severity = Severity::Warning,
labels = labels,
"eslint-plugin-import(export): Multiple default exports."
));
} }
} }
for span in &module_record.export_default_duplicated {
ctx.diagnostic(ExportDiagnostic::MultipleNamedExport(*span, "default".into()));
}
} }
} }

View file

@ -5,13 +5,13 @@ expression: export
⚠ eslint-plugin-import(export): Multiple exports of name 'foo'. ⚠ eslint-plugin-import(export): Multiple exports of name 'foo'.
╭─[index.ts:1:19] ╭─[index.ts:1:19]
1 │ let foo; export { foo }; export * from "./export-all" 1 │ let foo; export { foo }; export * from "./export-all"
· ─── · ─── ────────────────────────────
╰──── ╰────
⚠ eslint-plugin-import(export): Multiple exports of name 'foo'. ⚠ eslint-plugin-import(export): Multiple exports of name 'foo'.
╭─[index.ts:1:26] ╭─[index.ts:1:26]
1 │ let foo; export { foo as "foo" }; export * from "./export-all" 1 │ let foo; export { foo as "foo" }; export * from "./export-all"
· ───── · ───── ────────────────────────────
╰──── ╰────
× Identifier `Foo` has already been declared × Identifier `Foo` has already been declared
@ -99,17 +99,12 @@ expression: export
╰──── ╰────
⚠ eslint-plugin-import(export): Multiple exports of name 'default'. ⚠ eslint-plugin-import(export): Multiple exports of name 'default'.
╭─[index.ts:9:32] ╭─[index.ts:7:32]
6 │ const Bar = 2;
7 │ export {Bar as default};
· ───────
8 │ const Baz = 3; 8 │ const Baz = 3;
9 │ export {Baz as default}; 9 │ export {Baz as default};
· ─────── · ───────
10 │ 10 │
╰──── ╰────
⚠ eslint-plugin-import(export): Multiple exports of name 'default'.
╭─[index.ts:7:32]
6 │ const Bar = 2;
7 │ export {Bar as default};
· ───────
8 │ const Baz = 3;
╰────