1mod cache;
9pub(crate) mod fallbacks;
10mod path_info;
11mod react_native;
12mod specifier;
13mod types;
14
15pub use path_info::{extract_package_name, is_path_alias};
16pub use types::{ResolveResult, ResolvedImport, ResolvedModule, ResolvedReExport};
17
18use std::path::{Path, PathBuf};
19
20use rayon::prelude::*;
21use rustc_hash::FxHashMap;
22
23use fallow_types::discover::{DiscoveredFile, FileId};
24use fallow_types::extract::{ImportInfo, ModuleInfo};
25
26use cache::BareSpecifierCache;
27use fallbacks::make_glob_from_pattern;
28use specifier::{create_resolver, resolve_specifier};
29use types::ResolveContext;
30
31pub fn resolve_all_imports(
33 modules: &[ModuleInfo],
34 files: &[DiscoveredFile],
35 workspaces: &[fallow_config::WorkspaceInfo],
36 active_plugins: &[String],
37 path_aliases: &[(String, String)],
38 root: &Path,
39) -> Vec<ResolvedModule> {
40 let canonical_ws_roots: Vec<PathBuf> = workspaces
45 .par_iter()
46 .map(|ws| ws.root.canonicalize().unwrap_or_else(|_| ws.root.clone()))
47 .collect();
48 let workspace_roots: FxHashMap<&str, &Path> = workspaces
49 .iter()
50 .zip(canonical_ws_roots.iter())
51 .map(|(ws, canonical)| (ws.name.as_str(), canonical.as_path()))
52 .collect();
53
54 let canonical_paths: Vec<PathBuf> = files
57 .par_iter()
58 .map(|f| f.path.canonicalize().unwrap_or_else(|_| f.path.clone()))
59 .collect();
60
61 let path_to_id: FxHashMap<&Path, FileId> = canonical_paths
63 .iter()
64 .enumerate()
65 .map(|(idx, canonical)| (canonical.as_path(), files[idx].id))
66 .collect();
67
68 let raw_path_to_id: FxHashMap<&Path, FileId> =
70 files.iter().map(|f| (f.path.as_path(), f.id)).collect();
71
72 let file_paths: Vec<&Path> = files.iter().map(|f| f.path.as_path()).collect();
74
75 let resolver = create_resolver(active_plugins);
77
78 let bare_cache = BareSpecifierCache::new();
80
81 let ctx = ResolveContext {
83 resolver: &resolver,
84 path_to_id: &path_to_id,
85 raw_path_to_id: &raw_path_to_id,
86 bare_cache: &bare_cache,
87 workspace_roots: &workspace_roots,
88 path_aliases,
89 root,
90 };
91
92 modules
94 .par_iter()
95 .filter_map(|module| {
96 let Some(file_path) = file_paths.get(module.file_id.0 as usize) else {
97 tracing::warn!(
98 file_id = module.file_id.0,
99 "Skipping module with unknown file_id during resolution"
100 );
101 return None;
102 };
103
104 let resolved_imports: Vec<ResolvedImport> = module
105 .imports
106 .iter()
107 .map(|imp| ResolvedImport {
108 info: imp.clone(),
109 target: resolve_specifier(&ctx, file_path, &imp.source),
110 })
111 .collect();
112
113 let resolved_dynamic_imports: Vec<ResolvedImport> = module
114 .dynamic_imports
115 .iter()
116 .flat_map(|imp| {
117 let target = resolve_specifier(&ctx, file_path, &imp.source);
118 if !imp.destructured_names.is_empty() {
119 imp.destructured_names
121 .iter()
122 .map(|name| ResolvedImport {
123 info: ImportInfo {
124 source: imp.source.clone(),
125 imported_name: fallow_types::extract::ImportedName::Named(
126 name.clone(),
127 ),
128 local_name: name.clone(),
129 is_type_only: false,
130 span: imp.span,
131 },
132 target: target.clone(),
133 })
134 .collect()
135 } else if imp.local_name.is_some() {
136 vec![ResolvedImport {
138 info: ImportInfo {
139 source: imp.source.clone(),
140 imported_name: fallow_types::extract::ImportedName::Namespace,
141 local_name: imp.local_name.clone().unwrap_or_default(),
142 is_type_only: false,
143 span: imp.span,
144 },
145 target,
146 }]
147 } else {
148 vec![ResolvedImport {
150 info: ImportInfo {
151 source: imp.source.clone(),
152 imported_name: fallow_types::extract::ImportedName::SideEffect,
153 local_name: String::new(),
154 is_type_only: false,
155 span: imp.span,
156 },
157 target,
158 }]
159 }
160 })
161 .collect();
162
163 let re_exports: Vec<ResolvedReExport> = module
164 .re_exports
165 .iter()
166 .map(|re| ResolvedReExport {
167 info: re.clone(),
168 target: resolve_specifier(&ctx, file_path, &re.source),
169 })
170 .collect();
171
172 let require_imports: Vec<ResolvedImport> = module
175 .require_calls
176 .iter()
177 .flat_map(|req| {
178 let target = resolve_specifier(&ctx, file_path, &req.source);
179 if req.destructured_names.is_empty() {
180 vec![ResolvedImport {
181 info: ImportInfo {
182 source: req.source.clone(),
183 imported_name: fallow_types::extract::ImportedName::Namespace,
184 local_name: req.local_name.clone().unwrap_or_default(),
185 is_type_only: false,
186 span: req.span,
187 },
188 target,
189 }]
190 } else {
191 req.destructured_names
192 .iter()
193 .map(|name| ResolvedImport {
194 info: ImportInfo {
195 source: req.source.clone(),
196 imported_name: fallow_types::extract::ImportedName::Named(
197 name.clone(),
198 ),
199 local_name: name.clone(),
200 is_type_only: false,
201 span: req.span,
202 },
203 target: target.clone(),
204 })
205 .collect()
206 }
207 })
208 .collect();
209
210 let mut all_imports = resolved_imports;
211 all_imports.extend(require_imports);
212
213 let from_dir = canonical_paths
216 .get(module.file_id.0 as usize)
217 .and_then(|p| p.parent())
218 .unwrap_or(file_path);
219 let resolved_dynamic_patterns: Vec<(
220 fallow_types::extract::DynamicImportPattern,
221 Vec<FileId>,
222 )> = module
223 .dynamic_import_patterns
224 .iter()
225 .filter_map(|pattern| {
226 let glob_str = make_glob_from_pattern(pattern);
227 let matcher = globset::Glob::new(&glob_str)
228 .ok()
229 .map(|g| g.compile_matcher())?;
230 let matched: Vec<FileId> = canonical_paths
231 .iter()
232 .enumerate()
233 .filter(|(_idx, canonical)| {
234 canonical.strip_prefix(from_dir).is_ok_and(|relative| {
235 let rel_str = format!("./{}", relative.to_string_lossy());
236 matcher.is_match(&rel_str)
237 })
238 })
239 .map(|(idx, _)| files[idx].id)
240 .collect();
241 if matched.is_empty() {
242 None
243 } else {
244 Some((pattern.clone(), matched))
245 }
246 })
247 .collect();
248
249 Some(ResolvedModule {
250 file_id: module.file_id,
251 path: file_path.to_path_buf(),
252 exports: module.exports.clone(),
253 re_exports,
254 resolved_imports: all_imports,
255 resolved_dynamic_imports,
256 resolved_dynamic_patterns,
257 member_accesses: module.member_accesses.clone(),
258 whole_object_uses: module.whole_object_uses.clone(),
259 has_cjs_exports: module.has_cjs_exports,
260 unused_import_bindings: module.unused_import_bindings.clone(),
261 })
262 })
263 .collect()
264}