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 oxc_diagnostics::{
miette::{self, Diagnostic},
miette::{self, miette, Diagnostic, LabeledSpan},
thiserror::{self, Error},
Severity,
};
use oxc_macros::declare_oxc_lint;
use oxc_semantic::ModuleRecord;
@ -13,9 +14,6 @@ use crate::{context::LintContext, rule::Rule};
#[derive(Debug, Error, Diagnostic)]
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}'")]
#[diagnostic(severity(warning))]
NoNamedExport(#[label] Span, CompactStr),
@ -43,55 +41,79 @@ impl Rule for Export {
fn run_once(&self, ctx: &LintContext<'_>) {
let module_record = ctx.semantic().module_record();
let named_export = &module_record.exported_bindings;
let mut duplicated_named_export = FxHashMap::default();
for export_entry in &module_record.star_export_entries {
let Some(module_request) = &export_entry.module_request else {
continue;
};
let mut all_export_names = FxHashMap::default();
let mut visited = FxHashSet::default();
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) =
module_record.loaded_modules.get(module_request.name())
else {
continue;
return;
};
let remote_module_record = remote_module_record_ref.value();
let mut all_export_names = FxHashSet::default();
let mut visited = FxHashSet::default();
walk_exported_recursive(remote_module_record, &mut all_export_names, &mut visited);
if all_export_names.is_empty() {
walk_exported_recursive(
remote_module_record_ref.value(),
&mut export_names,
&mut visited,
);
if export_names.is_empty() {
ctx.diagnostic(ExportDiagnostic::NoNamedExport(
module_request.span(),
module_request.name().clone(),
));
continue;
} else {
all_export_names.insert(star_export_entry.span, export_names);
}
});
for name in &all_export_names {
if let Some(span) = named_export.get(name) {
duplicated_named_export.entry(*span).or_insert_with(|| name.clone());
for (name, span) in named_export {
let mut spans = all_export_names
.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 {
ctx.diagnostic(ExportDiagnostic::MultipleNamedExport(span, name));
}
if !spans.is_empty() {
spans.push(*span);
let labels = spans.into_iter().map(LabeledSpan::underline).collect::<Vec<_>>();
for name_span in &module_record.exported_bindings_duplicated {
let name = name_span.name().clone();
if let Some(span) = module_record.exported_bindings.get(&name) {
ctx.diagnostic(ExportDiagnostic::MultipleNamedExport(*span, name.clone()));
ctx.diagnostic(miette!(
severity = Severity::Warning,
labels = labels,
"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() {
let mut spans = module_record.export_default_duplicated.clone();
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'.
╭─[index.ts:1:19]
1 │ let foo; export { foo }; export * from "./export-all"
· ───
· ─── ────────────────────────────
╰────
⚠ eslint-plugin-import(export): Multiple exports of name 'foo'.
╭─[index.ts:1:26]
1 │ let foo; export { foo as "foo" }; export * from "./export-all"
· ─────
· ───── ────────────────────────────
╰────
× Identifier `Foo` has already been declared
@ -99,17 +99,12 @@ expression: export
╰────
⚠ 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;
9 │ export {Baz as default};
· ───────
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;
╰────