perf(linter/unicorn/prefer-export-from): narrow ExportFromDeclaration lookup (#25381)

## Summary

- Avoid a flattened-AST scan for every import handled by `unicorn/prefer-export-from`.
- Locate a matching direct re-export from top-level program statements only after symbol-reference analysis finds a reportable re-export.
- Cover multiple imports with existing direct re-exports.
This commit is contained in:
camc314 committed 2026-08-07 22:13:53 +00:00
1 parent 6846a9aa85
commit 6fb7f47605
2 files changed
+72 -21

No files matched your search

@@ -9,7 +9,8 @@ use oxc_ast::{
ast::{
BindingPattern, ExportFromDeclaration, ExportSpecifier, ImportAttributeKey,
ImportDeclaration, ImportDeclarationSpecifier, ImportOrExportKind, ModuleExportName,
Statement, VariableDeclarationKind, VariableDeclarator, WithClause, WithClauseKeyword,
Program, Statement, VariableDeclarationKind, VariableDeclarator, WithClause,
WithClauseKeyword,
},
};
use oxc_diagnostics::OxcDiagnostic;
@@ -104,16 +105,8 @@ impl Rule for PreferExportFrom {
return;
}
let corresponding_export: Option<&ExportFromDeclaration> =
find_corresponding_export(ctx, import_decl);
let symbol_to_specifier_specs = Self::get_symbol_to_specifier(import_decl);
self.check_re_export(
ctx,
&symbol_to_specifier_specs,
import_decl,
corresponding_export,
);
self.check_re_export(ctx, &symbol_to_specifier_specs, import_decl);
}
}
}
@@ -144,7 +137,6 @@ impl PreferExportFrom {
ctx: &LintContext<'a>,
symbol_to_specifier: &FxIndexMap<SymbolId, SpecifierSpec<'a>>,
import_decl: &'a ImportDeclaration<'a>,
re_export_decl: Option<&'a ExportFromDeclaration<'a>>,
) {
let (locally_used_specifiers, violations) =
self.analyze_import_usage(ctx, symbol_to_specifier, import_decl);
@@ -153,6 +145,8 @@ impl PreferExportFrom {
return;
}
let re_export_decl = find_corresponding_export(ctx, import_decl);
let source = import_decl.source.value.as_str();
let with_clause = import_decl.with_clause.as_ref().map(|with_clause| {
let keyword = match with_clause.keyword {
@@ -1155,6 +1149,7 @@ fn find_corresponding_export<'a>(
import_decl: &'a ImportDeclaration<'a>,
) -> Option<&'a ExportFromDeclaration<'a>> {
let source = import_decl.source.value.as_str();
let program = ctx.nodes().program();
for requested_module in ctx.module_record().requested_modules.get(source)? {
if requested_module.is_import {
@@ -1162,7 +1157,7 @@ fn find_corresponding_export<'a>(
}
let Some(export_decl) =
find_export_named_declaration_by_span(ctx, requested_module.statement_span)
find_export_named_declaration_by_span(program, requested_module.statement_span)
else {
continue;
};
@@ -1182,17 +1177,14 @@ fn find_corresponding_export<'a>(
}
fn find_export_named_declaration_by_span<'a>(
ctx: &LintContext<'a>,
program: &'a Program<'a>,
span: Span,
) -> Option<&'a ExportFromDeclaration<'a>> {
ctx.nodes().iter().find_map(|node| {
if let AstKind::ExportFromDeclaration(export_decl) = node.kind()
&& export_decl.span() == span
{
Some(export_decl)
} else {
None
}
program.body.iter().find_map(|statement| {
let Statement::ExportFromDeclaration(export_decl) = statement else {
return None;
};
(export_decl.span() == span).then_some(export_decl.as_ref())
})
}
@@ -1416,6 +1408,16 @@ fn test() {
r#"import { foo } from "foo";
export { foo };
export type { bar } from "foo";"#,
// Multiple imports find their corresponding direct re-export.
r#"import { a } from "a";
import { b } from "b";
import { c } from "c";
export { existingA } from "a";
export { existingB } from "b";
export { existingC } from "c";
export { a };
export { b };
export { c };"#,
r#"import { foo } from "foo";
export { foo };
export { type bar } from "foo";"#,
@@ -777,6 +777,55 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: use `export ... from ...;`
⚠ unicorn(prefer-export-from): Prefer re-exporting directly from the source module.
╭─[prefer_export_from.tsx:1:1]
1 │ import { a } from "a";
· ───────────┬──────────
· ╰── Imported here.
2 │ import { b } from "b";
╰────
╭─[prefer_export_from.tsx:7:22]
6 │ export { existingC } from "c";
7 │ export { a };
· ┬
· ╰── Re-exported here.
8 │ export { b };
╰────
help: use `export ... from ...;`
⚠ unicorn(prefer-export-from): Prefer re-exporting directly from the source module.
╭─[prefer_export_from.tsx:2:13]
1 │ import { a } from "a";
2 │ import { b } from "b";
· ───────────┬──────────
· ╰── Imported here.
3 │ import { c } from "c";
╰────
╭─[prefer_export_from.tsx:8:22]
7 │ export { a };
8 │ export { b };
· ┬
· ╰── Re-exported here.
9 │ export { c };
╰────
help: use `export ... from ...;`
⚠ unicorn(prefer-export-from): Prefer re-exporting directly from the source module.
╭─[prefer_export_from.tsx:3:13]
2 │ import { b } from "b";
3 │ import { c } from "c";
· ───────────┬──────────
· ╰── Imported here.
4 │ export { existingA } from "a";
╰────
╭─[prefer_export_from.tsx:9:22]
8 │ export { b };
9 │ export { c };
· ┬
· ╰── Re-exported here.
╰────
help: use `export ... from ...;`
⚠ unicorn(prefer-export-from): Prefer re-exporting directly from the source module.
╭─[prefer_export_from.tsx:1:1]
1 │ import { foo } from "foo";