1use std::sync::Arc;
2
3use rspack_util::atom::Atom;
4use rustc_hash::FxHashSet as HashSet;
5
6use crate::{
7 DependencyId, ExportInfo, ExportInfoData, ExportsInfo, ExportsInfoArtifact, ModuleGraph,
8 ModuleIdentifier,
9};
10
11#[derive(Debug, Hash, Clone, PartialEq, Eq)]
12pub enum TerminalBinding {
13 ExportInfo(ExportInfo),
14 ExportsInfo(ExportsInfo),
15}
16
17#[derive(Debug, Clone)]
18pub struct UnResolvedExportInfoTarget {
19 pub dependency: Option<DependencyId>,
20 pub export: Option<Vec<Atom>>,
21}
22
23pub type ResolveFilterFnTy<'a> = dyn Fn(&ResolvedExportInfoTarget) -> bool + 'a;
24
25#[derive(Debug)]
26pub enum GetTargetResult {
27 Target(ResolvedExportInfoTarget),
28 Circular,
29}
30
31#[derive(Clone, Debug, Eq)]
32pub struct ResolvedExportInfoTarget {
33 pub module: ModuleIdentifier,
34 pub export: Option<Vec<Atom>>,
35 pub dependency: DependencyId,
37}
38
39impl PartialEq for ResolvedExportInfoTarget {
40 fn eq(&self, other: &Self) -> bool {
41 self.module == other.module && self.export == other.export
42 }
43}
44
45#[derive(Clone, Debug)]
46pub enum FindTargetResult {
47 NoTarget,
48 InvalidTarget(FindTargetResultItem),
49 ValidTarget(FindTargetResultItem),
50}
51
52#[derive(Clone, Debug)]
53pub struct FindTargetResultItem {
54 pub module: ModuleIdentifier,
55 pub export: Option<Vec<Atom>>,
56 pub defer: bool,
57}
58
59pub fn get_terminal_binding(
60 export_info: &ExportInfoData,
61 mg: &ModuleGraph,
62 exports_info_artifact: &ExportsInfoArtifact,
63) -> Option<TerminalBinding> {
64 if export_info.terminal_binding() {
65 return Some(TerminalBinding::ExportInfo(export_info.id()));
66 }
67 let Some(GetTargetResult::Target(target)) = get_target(
68 export_info,
69 mg,
70 exports_info_artifact,
71 &|_| true,
72 &mut Default::default(),
73 ) else {
74 return None;
75 };
76 let exports_info = exports_info_artifact.get_exports_info(&target.module);
77 let Some(export) = target.export else {
78 return Some(TerminalBinding::ExportsInfo(exports_info));
79 };
80 exports_info
81 .as_data(exports_info_artifact)
82 .get_read_only_export_info_recursive(exports_info_artifact, &export)
83 .map(|data| TerminalBinding::ExportInfo(data.id()))
84}
85
86pub fn find_target(
87 export_info: &ExportInfoData,
88 mg: &ModuleGraph,
89 exports_info_artifact: &ExportsInfoArtifact,
90 valid_target_module_filter: Arc<impl Fn(&ModuleIdentifier) -> bool>,
91 visited: &mut HashSet<ExportInfo>,
92) -> FindTargetResult {
93 if !export_info.target_is_set() || export_info.target().is_empty() {
94 return FindTargetResult::NoTarget;
95 }
96 let max_target = export_info.get_max_target();
97 let Some(raw_target) = max_target.values().next() else {
98 return FindTargetResult::NoTarget;
99 };
100 let mut target = FindTargetResultItem {
101 module: *raw_target
102 .dependency
103 .and_then(|dep_id| mg.connection_by_dependency_id(&dep_id))
104 .expect("should have connection")
105 .module_identifier(),
106 export: raw_target.export.clone(),
107 defer: raw_target
108 .dependency
109 .as_ref()
110 .map(|dep| {
111 let dependency = mg.dependency_by_id(dep);
112 dependency.get_phase().is_defer()
113 })
114 .unwrap_or_default(),
115 };
116 loop {
117 if valid_target_module_filter(&target.module) {
118 return FindTargetResult::ValidTarget(target);
119 }
120 let name = &target.export.as_ref().expect("should have export")[0];
121 let exports_info = exports_info_artifact.get_exports_info_data(&target.module);
122 let export_info = exports_info.get_export_info_without_mut_module_graph(name);
123 let export_info_id = export_info.id();
124 if !visited.insert(export_info_id) {
125 return FindTargetResult::NoTarget;
126 }
127 let new_target = find_target(
128 &export_info,
129 mg,
130 exports_info_artifact,
131 valid_target_module_filter.clone(),
132 visited,
133 );
134 let new_target = match new_target {
135 FindTargetResult::NoTarget => return FindTargetResult::InvalidTarget(target),
136 FindTargetResult::InvalidTarget(module) => return FindTargetResult::InvalidTarget(module),
137 FindTargetResult::ValidTarget(target) => target,
138 };
139 if target.export.as_ref().map(|item| item.len()) == Some(1) {
140 target = new_target;
141 } else {
142 target = FindTargetResultItem {
143 module: new_target.module,
144 export: if let Some(export) = new_target.export {
145 Some(
146 [
147 export,
148 target
149 .export
150 .as_ref()
151 .and_then(|export| export.get(1..).map(|slice| slice.to_vec()))
152 .unwrap_or_default(),
153 ]
154 .concat(),
155 )
156 } else {
157 target
158 .export
159 .and_then(|export| export.get(1..).map(|slice| slice.to_vec()))
160 },
161 defer: new_target.defer,
162 }
163 }
164 }
165}
166
167pub fn get_target(
168 export_info: &ExportInfoData,
169 mg: &ModuleGraph,
170 exports_info_artifact: &ExportsInfoArtifact,
171 resolve_filter: &ResolveFilterFnTy<'_>,
172 already_visited: &mut HashSet<ExportInfo>,
173) -> Option<GetTargetResult> {
174 if !export_info.target_is_set() || export_info.target().is_empty() {
175 return None;
176 }
177 if !already_visited.insert(export_info.id()) {
178 return Some(GetTargetResult::Circular);
179 }
180
181 let max_target = export_info.get_max_target();
182 let mut values = max_target.values().map(|item| UnResolvedExportInfoTarget {
183 dependency: item.dependency,
184 export: item.export.clone(),
185 });
186 let target = resolve_target(
187 values.next()?,
188 already_visited,
189 resolve_filter,
190 mg,
191 exports_info_artifact,
192 );
193
194 if let Some(GetTargetResult::Target(target)) = &target {
195 for val in values {
196 let resolved_target = resolve_target(
197 val,
198 already_visited,
199 resolve_filter,
200 mg,
201 exports_info_artifact,
202 );
203 let Some(GetTargetResult::Target(resolved_target)) = &resolved_target else {
204 return resolved_target;
205 };
206 if resolved_target != target {
207 return None;
208 }
209 }
210 }
211
212 target
213}
214
215fn resolve_target(
216 input_target: UnResolvedExportInfoTarget,
217 already_visited: &mut HashSet<ExportInfo>,
218 resolve_filter: &ResolveFilterFnTy<'_>,
219 mg: &ModuleGraph,
220 exports_info_artifact: &ExportsInfoArtifact,
221) -> Option<GetTargetResult> {
222 let mut target = ResolvedExportInfoTarget {
223 module: *input_target
224 .dependency
225 .and_then(|dep_id| mg.connection_by_dependency_id(&dep_id))
226 .expect("should have connection")
227 .module_identifier(),
228 export: input_target.export,
229 dependency: input_target.dependency.expect("should have dependency"),
230 };
231 if target.export.is_none() {
232 return Some(GetTargetResult::Target(target));
233 }
234 if !resolve_filter(&target) {
235 return Some(GetTargetResult::Target(target));
236 }
237 loop {
238 let Some(name) = target.export.as_ref().and_then(|exports| exports.first()) else {
239 return Some(GetTargetResult::Target(target));
240 };
241
242 let exports_info = exports_info_artifact.get_exports_info_data(&target.module);
243 let maybe_export_info = exports_info.get_export_info_without_mut_module_graph(name);
244 let maybe_export_info_id = maybe_export_info.id();
245 if already_visited.contains(&maybe_export_info_id) {
246 return Some(GetTargetResult::Circular);
247 }
248 let new_target = get_target(
249 &maybe_export_info,
250 mg,
251 exports_info_artifact,
252 resolve_filter,
253 already_visited,
254 );
255
256 match new_target {
257 Some(GetTargetResult::Circular) => {
258 return Some(GetTargetResult::Circular);
259 }
260 None => return Some(GetTargetResult::Target(target)),
261 Some(GetTargetResult::Target(t)) => {
262 let target_exports = target.export.as_ref().expect("should have exports");
264 if target_exports.len() == 1 {
265 target = t;
266 if target.export.is_none() {
267 return Some(GetTargetResult::Target(target));
268 }
269 } else {
270 target.module = t.module;
271 target.dependency = t.dependency;
272 target.export = if let Some(mut exports) = t.export {
273 exports.extend_from_slice(&target_exports[1..]);
274 Some(exports)
275 } else {
276 Some(target_exports[1..].to_vec())
277 }
278 }
279 }
280 }
281 if !resolve_filter(&target) {
282 return Some(GetTargetResult::Target(target));
283 }
284 already_visited.insert(maybe_export_info_id);
285 }
286}
287
288pub fn can_move_target(
289 export_info: &ExportInfoData,
290 mg: &ModuleGraph,
291 exports_info_artifact: &ExportsInfoArtifact,
292 resolve_filter: &ResolveFilterFnTy<'_>,
293) -> Option<ResolvedExportInfoTarget> {
294 let Some(GetTargetResult::Target(target)) = get_target(
295 export_info,
296 mg,
297 exports_info_artifact,
298 resolve_filter,
299 &mut Default::default(),
300 ) else {
301 return None;
302 };
303 let max_target = export_info.get_max_target();
304 let original_target = max_target
305 .values()
306 .next()
307 .expect("should have export info target"); if original_target.dependency.as_ref() == Some(&target.dependency)
309 && original_target.export == target.export
310 {
311 return None;
312 }
313 Some(target)
314}