1mod dynamic_imports;
19pub(crate) mod fallbacks;
20mod path_info;
21mod re_exports;
22mod react_native;
23mod require_imports;
24mod specifier;
25mod static_imports;
26#[cfg(test)]
27mod tests;
28mod types;
29mod upgrades;
30
31pub use fallbacks::extract_package_name_from_node_modules_path;
32pub use path_info::{extract_package_name, is_bare_specifier, is_path_alias};
33pub use types::{
34 ResolveResult, ResolvedImport, ResolvedModule, ResolvedReExport, ResolvedSourceEdge,
35};
36
37use std::path::{Path, PathBuf};
38use std::sync::Mutex;
39
40use rayon::prelude::*;
41use rustc_hash::{FxHashMap, FxHashSet};
42
43use fallow_types::discover::{DiscoveredFile, FileId};
44use fallow_types::extract::ModuleInfo;
45
46use dynamic_imports::{resolve_dynamic_imports, resolve_dynamic_patterns};
47use re_exports::resolve_re_exports;
48use react_native::{build_condition_names, build_extensions};
49use require_imports::resolve_require_imports;
50use specifier::create_resolver;
51use static_imports::resolve_static_imports;
52use types::{PackageManifestInfo, ResolveContext};
53use upgrades::apply_specifier_upgrades;
54
55#[must_use]
57#[expect(
58 clippy::too_many_arguments,
59 reason = "resolver inputs come from disjoint sources (config, plugins, workspace, filesystem); \
60 bundling them into a struct would be a cross-cutting refactor outside this task"
61)]
62pub fn resolve_all_imports(
63 modules: &[ModuleInfo],
64 files: &[DiscoveredFile],
65 workspaces: &[fallow_config::WorkspaceInfo],
66 active_plugins: &[String],
67 path_aliases: &[(String, String)],
68 scss_include_paths: &[PathBuf],
69 root: &Path,
70 extra_conditions: &[String],
71) -> Vec<ResolvedModule> {
72 let canonical_ws_roots: Vec<PathBuf> = workspaces
77 .par_iter()
78 .map(|ws| dunce::canonicalize(&ws.root).unwrap_or_else(|_| ws.root.clone()))
79 .collect();
80 let workspace_roots: FxHashMap<&str, &Path> = workspaces
81 .iter()
82 .zip(canonical_ws_roots.iter())
83 .map(|(ws, canonical)| (ws.name.as_str(), canonical.as_path()))
84 .collect();
85 let root_canonical = dunce::canonicalize(root).unwrap_or_else(|_| root.to_path_buf());
86 let mut package_manifests = Vec::new();
87 if let Ok(package_json) = fallow_config::PackageJson::load(&root.join("package.json")) {
88 package_manifests.push(PackageManifestInfo {
89 root: root.to_path_buf(),
90 canonical_root: root_canonical,
91 name: package_json.name.clone(),
92 package_json,
93 });
94 }
95 for (ws, canonical_root) in workspaces.iter().zip(canonical_ws_roots.iter()) {
96 if let Ok(package_json) = fallow_config::PackageJson::load(&ws.root.join("package.json")) {
97 package_manifests.push(PackageManifestInfo {
98 root: ws.root.clone(),
99 canonical_root: canonical_root.clone(),
100 name: package_json.name.clone().or_else(|| Some(ws.name.clone())),
101 package_json,
102 });
103 }
104 }
105
106 let root_is_canonical = dunce::canonicalize(root).is_ok_and(|c| c == root);
111
112 let canonical_paths: Vec<PathBuf> = if root_is_canonical {
115 Vec::new()
116 } else {
117 files
118 .par_iter()
119 .map(|f| dunce::canonicalize(&f.path).unwrap_or_else(|_| f.path.clone()))
120 .collect()
121 };
122
123 let path_to_id: FxHashMap<&Path, FileId> = if root_is_canonical {
126 files.iter().map(|f| (f.path.as_path(), f.id)).collect()
127 } else {
128 canonical_paths
129 .iter()
130 .enumerate()
131 .map(|(idx, canonical)| (canonical.as_path(), files[idx].id))
132 .collect()
133 };
134
135 let raw_path_to_id: FxHashMap<&Path, FileId> =
137 files.iter().map(|f| (f.path.as_path(), f.id)).collect();
138
139 let file_paths: Vec<&Path> = files.iter().map(|f| f.path.as_path()).collect();
141
142 let extensions = build_extensions(active_plugins);
144 let condition_names = build_condition_names(active_plugins, extra_conditions);
145 let resolver = create_resolver(active_plugins, extra_conditions);
146 let mut style_conditions = extra_conditions.to_vec();
147 style_conditions.push("style".to_string());
148 let style_resolver = create_resolver(active_plugins, &style_conditions);
149
150 let canonical_fallback = if root_is_canonical {
153 Some(types::CanonicalFallback::new(files))
154 } else {
155 None
156 };
157
158 let tsconfig_warned: Mutex<FxHashSet<String>> = Mutex::new(FxHashSet::default());
160
161 let ctx = ResolveContext {
163 resolver: &resolver,
164 style_resolver: &style_resolver,
165 extensions: &extensions,
166 path_to_id: &path_to_id,
167 raw_path_to_id: &raw_path_to_id,
168 workspace_roots: &workspace_roots,
169 package_manifests: &package_manifests,
170 condition_names: &condition_names,
171 path_aliases,
172 scss_include_paths,
173 root,
174 canonical_fallback: canonical_fallback.as_ref(),
175 tsconfig_warned: &tsconfig_warned,
176 };
177
178 let mut resolved: Vec<ResolvedModule> = modules
183 .par_iter()
184 .filter_map(|module| {
185 let Some(file_path) = file_paths.get(module.file_id.0 as usize) else {
186 tracing::warn!(
187 file_id = module.file_id.0,
188 "Skipping module with unknown file_id during resolution"
189 );
190 return None;
191 };
192
193 let mut all_imports = resolve_static_imports(&ctx, file_path, &module.imports);
194 all_imports.extend(resolve_require_imports(
195 &ctx,
196 file_path,
197 &module.require_calls,
198 ));
199
200 let from_dir = if canonical_paths.is_empty() {
201 file_path.parent().unwrap_or(file_path)
203 } else {
204 canonical_paths
205 .get(module.file_id.0 as usize)
206 .and_then(|p| p.parent())
207 .unwrap_or(file_path)
208 };
209
210 Some(ResolvedModule {
211 file_id: module.file_id,
212 path: file_path.to_path_buf(),
213 exports: module.exports.clone(),
214 re_exports: resolve_re_exports(&ctx, file_path, &module.re_exports),
215 resolved_imports: all_imports,
216 resolved_dynamic_imports: resolve_dynamic_imports(
217 &ctx,
218 file_path,
219 &module.dynamic_imports,
220 ),
221 resolved_dynamic_patterns: resolve_dynamic_patterns(
222 from_dir,
223 &module.dynamic_import_patterns,
224 &canonical_paths,
225 files,
226 ),
227 member_accesses: module.member_accesses.clone(),
228 whole_object_uses: module.whole_object_uses.clone(),
229 has_cjs_exports: module.has_cjs_exports,
230 has_angular_component_template_url: module.has_angular_component_template_url,
231 unused_import_bindings: module.unused_import_bindings.iter().cloned().collect(),
232 type_referenced_import_bindings: module.type_referenced_import_bindings.clone(),
233 value_referenced_import_bindings: module.value_referenced_import_bindings.clone(),
234 namespace_object_aliases: module.namespace_object_aliases.clone(),
235 })
236 })
237 .collect();
238
239 apply_specifier_upgrades(&mut resolved);
240
241 resolved
242}