use std::sync::Arc;
use rspack_util::atom::Atom;
use rustc_hash::FxHashSet as HashSet;
use crate::{
DependencyId, ExportInfo, ExportInfoData, ExportsInfo, ExportsInfoArtifact, ModuleGraph,
ModuleIdentifier,
};
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
pub enum TerminalBinding {
ExportInfo(ExportInfo),
ExportsInfo(ExportsInfo),
}
#[derive(Debug, Clone)]
pub struct UnResolvedExportInfoTarget {
pub dependency: Option<DependencyId>,
pub export: Option<Vec<Atom>>,
}
pub type ResolveFilterFnTy<'a> = dyn Fn(&ResolvedExportInfoTarget) -> bool + 'a;
#[derive(Debug)]
pub enum GetTargetResult {
Target(ResolvedExportInfoTarget),
Circular,
}
#[derive(Clone, Debug, Eq)]
pub struct ResolvedExportInfoTarget {
pub module: ModuleIdentifier,
pub export: Option<Vec<Atom>>,
pub dependency: DependencyId,
}
impl PartialEq for ResolvedExportInfoTarget {
fn eq(&self, other: &Self) -> bool {
self.module == other.module && self.export == other.export
}
}
#[derive(Clone, Debug)]
pub enum FindTargetResult {
NoTarget,
InvalidTarget(FindTargetResultItem),
ValidTarget(FindTargetResultItem),
}
#[derive(Clone, Debug)]
pub struct FindTargetResultItem {
pub module: ModuleIdentifier,
pub export: Option<Vec<Atom>>,
pub defer: bool,
}
pub fn get_terminal_binding(
export_info: &ExportInfoData,
mg: &ModuleGraph,
exports_info_artifact: &ExportsInfoArtifact,
) -> Option<TerminalBinding> {
if export_info.terminal_binding() {
return Some(TerminalBinding::ExportInfo(export_info.id()));
}
let Some(GetTargetResult::Target(target)) = get_target(
export_info,
mg,
exports_info_artifact,
&|_| true,
&mut Default::default(),
) else {
return None;
};
let exports_info = exports_info_artifact.get_exports_info(&target.module);
let Some(export) = target.export else {
return Some(TerminalBinding::ExportsInfo(exports_info));
};
exports_info
.as_data(exports_info_artifact)
.get_read_only_export_info_recursive(exports_info_artifact, &export)
.map(|data| TerminalBinding::ExportInfo(data.id()))
}
pub fn find_target(
export_info: &ExportInfoData,
mg: &ModuleGraph,
exports_info_artifact: &ExportsInfoArtifact,
valid_target_module_filter: Arc<impl Fn(&ModuleIdentifier) -> bool>,
visited: &mut HashSet<ExportInfo>,
) -> FindTargetResult {
if !export_info.target_is_set() || export_info.target().is_empty() {
return FindTargetResult::NoTarget;
}
let max_target = export_info.get_max_target();
let Some(raw_target) = max_target.values().next() else {
return FindTargetResult::NoTarget;
};
let mut target = FindTargetResultItem {
module: *raw_target
.dependency
.and_then(|dep_id| mg.connection_by_dependency_id(&dep_id))
.expect("should have connection")
.module_identifier(),
export: raw_target.export.clone(),
defer: raw_target
.dependency
.as_ref()
.map(|dep| {
let dependency = mg.dependency_by_id(dep);
dependency.get_phase().is_defer()
})
.unwrap_or_default(),
};
loop {
if valid_target_module_filter(&target.module) {
return FindTargetResult::ValidTarget(target);
}
let name = &target.export.as_ref().expect("should have export")[0];
let exports_info = exports_info_artifact.get_exports_info_data(&target.module);
let export_info = exports_info.get_export_info_without_mut_module_graph(name);
let export_info_id = export_info.id();
if !visited.insert(export_info_id) {
return FindTargetResult::NoTarget;
}
let new_target = find_target(
&export_info,
mg,
exports_info_artifact,
valid_target_module_filter.clone(),
visited,
);
let new_target = match new_target {
FindTargetResult::NoTarget => return FindTargetResult::InvalidTarget(target),
FindTargetResult::InvalidTarget(module) => return FindTargetResult::InvalidTarget(module),
FindTargetResult::ValidTarget(target) => target,
};
if target.export.as_ref().map(|item| item.len()) == Some(1) {
target = new_target;
} else {
target = FindTargetResultItem {
module: new_target.module,
export: if let Some(export) = new_target.export {
Some(
[
export,
target
.export
.as_ref()
.and_then(|export| export.get(1..).map(|slice| slice.to_vec()))
.unwrap_or_default(),
]
.concat(),
)
} else {
target
.export
.and_then(|export| export.get(1..).map(|slice| slice.to_vec()))
},
defer: new_target.defer,
}
}
}
}
pub fn get_target(
export_info: &ExportInfoData,
mg: &ModuleGraph,
exports_info_artifact: &ExportsInfoArtifact,
resolve_filter: &ResolveFilterFnTy<'_>,
already_visited: &mut HashSet<ExportInfo>,
) -> Option<GetTargetResult> {
if !export_info.target_is_set() || export_info.target().is_empty() {
return None;
}
if !already_visited.insert(export_info.id()) {
return Some(GetTargetResult::Circular);
}
let max_target = export_info.get_max_target();
let mut values = max_target.values().map(|item| UnResolvedExportInfoTarget {
dependency: item.dependency,
export: item.export.clone(),
});
let target = resolve_target(
values.next()?,
already_visited,
resolve_filter,
mg,
exports_info_artifact,
);
if let Some(GetTargetResult::Target(target)) = &target {
for val in values {
let resolved_target = resolve_target(
val,
already_visited,
resolve_filter,
mg,
exports_info_artifact,
);
let Some(GetTargetResult::Target(resolved_target)) = &resolved_target else {
return resolved_target;
};
if resolved_target != target {
return None;
}
}
}
target
}
fn resolve_target(
input_target: UnResolvedExportInfoTarget,
already_visited: &mut HashSet<ExportInfo>,
resolve_filter: &ResolveFilterFnTy<'_>,
mg: &ModuleGraph,
exports_info_artifact: &ExportsInfoArtifact,
) -> Option<GetTargetResult> {
let mut target = ResolvedExportInfoTarget {
module: *input_target
.dependency
.and_then(|dep_id| mg.connection_by_dependency_id(&dep_id))
.expect("should have connection")
.module_identifier(),
export: input_target.export,
dependency: input_target.dependency.expect("should have dependency"),
};
if target.export.is_none() {
return Some(GetTargetResult::Target(target));
}
if !resolve_filter(&target) {
return Some(GetTargetResult::Target(target));
}
loop {
let Some(name) = target.export.as_ref().and_then(|exports| exports.first()) else {
return Some(GetTargetResult::Target(target));
};
let exports_info = exports_info_artifact.get_exports_info_data(&target.module);
let maybe_export_info = exports_info.get_export_info_without_mut_module_graph(name);
let maybe_export_info_id = maybe_export_info.id();
if already_visited.contains(&maybe_export_info_id) {
return Some(GetTargetResult::Circular);
}
let new_target = get_target(
&maybe_export_info,
mg,
exports_info_artifact,
resolve_filter,
already_visited,
);
match new_target {
Some(GetTargetResult::Circular) => {
return Some(GetTargetResult::Circular);
}
None => return Some(GetTargetResult::Target(target)),
Some(GetTargetResult::Target(t)) => {
let target_exports = target.export.as_ref().expect("should have exports");
if target_exports.len() == 1 {
target = t;
if target.export.is_none() {
return Some(GetTargetResult::Target(target));
}
} else {
target.module = t.module;
target.dependency = t.dependency;
target.export = if let Some(mut exports) = t.export {
exports.extend_from_slice(&target_exports[1..]);
Some(exports)
} else {
Some(target_exports[1..].to_vec())
}
}
}
}
if !resolve_filter(&target) {
return Some(GetTargetResult::Target(target));
}
already_visited.insert(maybe_export_info_id);
}
}
pub fn can_move_target(
export_info: &ExportInfoData,
mg: &ModuleGraph,
exports_info_artifact: &ExportsInfoArtifact,
resolve_filter: &ResolveFilterFnTy<'_>,
) -> Option<ResolvedExportInfoTarget> {
let Some(GetTargetResult::Target(target)) = get_target(
export_info,
mg,
exports_info_artifact,
resolve_filter,
&mut Default::default(),
) else {
return None;
};
let max_target = export_info.get_max_target();
let original_target = max_target
.values()
.next()
.expect("should have export info target"); if original_target.dependency.as_ref() == Some(&target.dependency)
&& original_target.export == target.export
{
return None;
}
Some(target)
}