mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter/import): improve multiple exports error message (#3160)
https://github.com/oxc-project/oxc/issues/3157#issuecomment-2094113518
This commit is contained in:
parent
a7940868c6
commit
f88f330426
2 changed files with 60 additions and 43 deletions
|
|
@ -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 {
|
||||||
for (span, name) in duplicated_named_export {
|
None
|
||||||
ctx.diagnostic(ExportDiagnostic::MultipleNamedExport(span, name));
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for name_span in &module_record.exported_bindings_duplicated {
|
for name_span in &module_record.exported_bindings_duplicated {
|
||||||
let name = name_span.name().clone();
|
if name == name_span.name() {
|
||||||
if let Some(span) = module_record.exported_bindings.get(&name) {
|
spans.push(name_span.span());
|
||||||
ctx.diagnostic(ExportDiagnostic::MultipleNamedExport(*span, name.clone()));
|
|
||||||
}
|
}
|
||||||
ctx.diagnostic(ExportDiagnostic::MultipleNamedExport(name_span.span(), name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !spans.is_empty() {
|
||||||
|
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 exports of name '{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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -98,18 +98,13 @@ expression: export
|
||||||
4 │ export namespace Foo { }
|
4 │ export namespace Foo { }
|
||||||
╰────
|
╰────
|
||||||
|
|
||||||
⚠ eslint-plugin-import(export): Multiple exports of name 'default'.
|
|
||||||
╭─[index.ts:9:32]
|
|
||||||
8 │ const Baz = 3;
|
|
||||||
9 │ export {Baz as default};
|
|
||||||
· ───────
|
|
||||||
10 │
|
|
||||||
╰────
|
|
||||||
|
|
||||||
⚠ eslint-plugin-import(export): Multiple exports of name 'default'.
|
⚠ eslint-plugin-import(export): Multiple exports of name 'default'.
|
||||||
╭─[index.ts:7:32]
|
╭─[index.ts:7:32]
|
||||||
6 │ const Bar = 2;
|
6 │ const Bar = 2;
|
||||||
7 │ export {Bar as default};
|
7 │ export {Bar as default};
|
||||||
· ───────
|
· ───────
|
||||||
8 │ const Baz = 3;
|
8 │ const Baz = 3;
|
||||||
|
9 │ export {Baz as default};
|
||||||
|
· ───────
|
||||||
|
10 │
|
||||||
╰────
|
╰────
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue