use rustc_hash::{FxHashMap, FxHashSet};
use fallow_types::discover::FileId;
use fallow_types::extract::{ExportName, VisibilityTag};
use crate::graph::types::{ExportSymbol, ModuleNode, ReferenceKind, SymbolReference};
use crate::graph::{Edge, ImportedName};
pub(in crate::graph) fn propagate_star_re_export(
modules: &mut [ModuleNode],
edges: &[Edge],
edges_by_target: &rustc_hash::FxHashMap<FileId, Vec<usize>>,
barrel_id: FileId,
barrel_idx: usize,
source_idx: usize,
entry_star_targets: &FxHashSet<FileId>,
) -> bool {
if modules[barrel_idx].is_entry_point()
|| entry_star_targets.contains(&modules[barrel_idx].file_id)
{
return propagate_entry_point_star(modules, barrel_id, source_idx);
}
let barrel_file_id = modules[barrel_idx].file_id;
let named_refs: Vec<(String, SymbolReference)> = edges_by_target
.get(&barrel_file_id)
.map(|indices| {
indices
.iter()
.flat_map(|&idx| {
let edge = &edges[idx];
edge.symbols.iter().filter_map(move |sym| {
if let ImportedName::Named(name) = &sym.imported_name {
Some((
name.clone(),
SymbolReference {
from_file: edge.source,
kind: ReferenceKind::NamedImport,
import_span: sym.import_span,
},
))
} else {
None
}
})
})
.collect()
})
.unwrap_or_default();
let barrel_refs: Vec<(String, Vec<SymbolReference>)> = modules[barrel_idx]
.exports
.iter()
.filter(|e| !e.references.is_empty())
.map(|e| (e.name.to_string(), e.references.clone()))
.collect();
let source_has_star_re_exports = modules[source_idx]
.re_exports
.iter()
.any(|re| re.exported_name == "*");
let mut refs_by_name: FxHashMap<String, Vec<SymbolReference>> = FxHashMap::default();
for (name, ref_item) in named_refs {
refs_by_name.entry(name).or_default().push(ref_item);
}
for (name, refs) in barrel_refs {
refs_by_name.entry(name).or_default().extend(refs);
}
let mut changed = false;
let mut existing_files: FxHashSet<FileId> = FxHashSet::default();
let source = &mut modules[source_idx];
for (name, refs) in &refs_by_name {
let export_name = if name == "default" {
ExportName::Default
} else {
ExportName::Named(name.clone())
};
if let Some(export) = source.exports.iter_mut().find(|e| e.name == export_name) {
existing_files.clear();
existing_files.extend(export.references.iter().map(|r| r.from_file));
for ref_item in refs {
if existing_files.insert(ref_item.from_file) {
export.references.push(*ref_item);
changed = true;
}
}
} else if source_has_star_re_exports {
source.exports.push(ExportSymbol {
name: export_name,
is_type_only: false,
visibility: VisibilityTag::None,
span: oxc_span::Span::new(0, 0),
references: refs.clone(),
members: Vec::new(),
});
changed = true;
}
}
changed
}
fn propagate_entry_point_star(
modules: &mut [ModuleNode],
barrel_id: FileId,
source_idx: usize,
) -> bool {
let mut changed = false;
let source = &mut modules[source_idx];
for export in &mut source.exports {
if matches!(export.name, ExportName::Default) {
continue;
}
if export.references.iter().all(|r| r.from_file != barrel_id) {
export.references.push(SymbolReference {
from_file: barrel_id,
kind: ReferenceKind::ReExport,
import_span: oxc_span::Span::new(0, 0),
});
changed = true;
}
}
changed
}
pub(in crate::graph) fn propagate_named_re_export(
modules: &mut [ModuleNode],
barrel_id: FileId,
barrel_idx: usize,
source_idx: usize,
imported_name: &str,
exported_name: &str,
existing_refs: &mut FxHashSet<FileId>,
) -> bool {
let refs_on_barrel: Vec<SymbolReference> = modules[barrel_idx]
.exports
.iter()
.filter(|e| e.name.matches_str(exported_name))
.flat_map(|e| e.references.iter().copied())
.collect();
if refs_on_barrel.is_empty() {
if modules[barrel_idx].is_entry_point() {
return propagate_entry_point_named(modules, barrel_id, source_idx, imported_name);
}
return false;
}
let mut changed = false;
let source = &mut modules[source_idx];
let target_exports: Vec<usize> = source
.exports
.iter()
.enumerate()
.filter(|(_, e)| e.name.matches_str(imported_name))
.map(|(i, _)| i)
.collect();
for export_idx in target_exports {
existing_refs.clear();
existing_refs.extend(
source.exports[export_idx]
.references
.iter()
.map(|r| r.from_file),
);
for ref_item in &refs_on_barrel {
if !existing_refs.contains(&ref_item.from_file) {
source.exports[export_idx].references.push(*ref_item);
changed = true;
}
}
}
changed
}
fn propagate_entry_point_named(
modules: &mut [ModuleNode],
barrel_id: FileId,
source_idx: usize,
imported_name: &str,
) -> bool {
let synthetic_ref = SymbolReference {
from_file: barrel_id,
kind: ReferenceKind::ReExport,
import_span: oxc_span::Span::new(0, 0),
};
let mut changed = false;
let source = &mut modules[source_idx];
let target_exports: Vec<usize> = source
.exports
.iter()
.enumerate()
.filter(|(_, e)| e.name.matches_str(imported_name))
.map(|(i, _)| i)
.collect();
for export_idx in target_exports {
if source.exports[export_idx]
.references
.iter()
.all(|r| r.from_file != barrel_id)
{
source.exports[export_idx].references.push(synthetic_ref);
changed = true;
}
}
changed
}