use rustc_hash::FxHashSet;
use fallow_types::discover::FileId;
use fallow_types::extract::ExportName;
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],
barrel_id: FileId,
barrel_idx: usize,
source_idx: usize,
) -> bool {
if modules[barrel_idx].is_entry_point {
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
.iter()
.filter(|edge| edge.target == barrel_file_id)
.flat_map(|edge| {
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();
let barrel_export_refs: Vec<(String, SymbolReference)> = modules[barrel_idx]
.exports
.iter()
.flat_map(|e| {
e.references
.iter()
.map(move |r| (e.name.to_string(), r.clone()))
})
.collect();
let source_has_star_re_exports = modules[source_idx]
.re_exports
.iter()
.any(|re| re.exported_name == "*");
let mut changed = false;
let source = &mut modules[source_idx];
for (name, ref_item) in named_refs.iter().chain(barrel_export_refs.iter()) {
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) {
if export
.references
.iter()
.all(|r| r.from_file != ref_item.from_file)
{
export.references.push(ref_item.clone());
changed = true;
}
} else if source_has_star_re_exports {
source.exports.push(ExportSymbol {
name: export_name,
is_type_only: false,
is_public: false,
span: oxc_span::Span::new(0, 0),
references: vec![ref_item.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().cloned())
.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.clone());
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.clone());
changed = true;
}
}
changed
}