fallow_graph/resolve/
types.rs1use std::path::{Path, PathBuf};
4
5use oxc_resolver::Resolver;
6use rustc_hash::FxHashMap;
7
8use fallow_types::discover::FileId;
9
10#[derive(Debug, Clone)]
12pub enum ResolveResult {
13 InternalModule(FileId),
15 ExternalFile(PathBuf),
17 NpmPackage(String),
19 Unresolvable(String),
21}
22
23#[derive(Debug, Clone)]
25pub struct ResolvedImport {
26 pub info: fallow_types::extract::ImportInfo,
28 pub target: ResolveResult,
30}
31
32#[derive(Debug, Clone)]
34pub struct ResolvedReExport {
35 pub info: fallow_types::extract::ReExportInfo,
37 pub target: ResolveResult,
39}
40
41#[derive(Debug)]
43pub struct ResolvedModule {
44 pub file_id: FileId,
46 pub path: PathBuf,
48 pub exports: Vec<fallow_types::extract::ExportInfo>,
50 pub re_exports: Vec<ResolvedReExport>,
52 pub resolved_imports: Vec<ResolvedImport>,
54 pub resolved_dynamic_imports: Vec<ResolvedImport>,
56 pub resolved_dynamic_patterns: Vec<(fallow_types::extract::DynamicImportPattern, Vec<FileId>)>,
58 pub member_accesses: Vec<fallow_types::extract::MemberAccess>,
60 pub whole_object_uses: Vec<String>,
62 pub has_cjs_exports: bool,
64 pub unused_import_bindings: Vec<String>,
66}
67
68pub(super) struct ResolveContext<'a> {
73 pub resolver: &'a Resolver,
75 pub path_to_id: &'a FxHashMap<&'a Path, FileId>,
77 pub raw_path_to_id: &'a FxHashMap<&'a Path, FileId>,
79 pub workspace_roots: &'a FxHashMap<&'a str, &'a Path>,
81 pub path_aliases: &'a [(String, String)],
83 pub root: &'a Path,
85 pub canonical_fallback: Option<&'a CanonicalFallback<'a>>,
89}
90
91pub(super) struct CanonicalFallback<'a> {
93 files: &'a [fallow_types::discover::DiscoveredFile],
94 map: std::sync::OnceLock<FxHashMap<std::path::PathBuf, FileId>>,
95}
96
97impl<'a> CanonicalFallback<'a> {
98 pub fn new(files: &'a [fallow_types::discover::DiscoveredFile]) -> Self {
99 Self {
100 files,
101 map: std::sync::OnceLock::new(),
102 }
103 }
104
105 pub fn get(&self, canonical: &Path) -> Option<FileId> {
107 let map = self.map.get_or_init(|| {
108 tracing::warn!(
109 "intra-project symlinks detected — building canonical path index ({} files)",
110 self.files.len()
111 );
112 self.files
113 .iter()
114 .filter_map(|f| {
115 f.path
116 .canonicalize()
117 .ok()
118 .map(|canonical| (canonical, f.id))
119 })
120 .collect()
121 });
122 map.get(canonical).copied()
123 }
124}
125
126#[cfg(all(test, not(miri)))]
127mod tests {
128 use super::*;
129 use fallow_types::discover::DiscoveredFile;
130 use std::path::PathBuf;
131
132 #[test]
133 fn canonical_fallback_returns_none_for_empty_files() {
134 let files: Vec<DiscoveredFile> = vec![];
135 let fallback = CanonicalFallback::new(&files);
136 assert!(fallback.get(Path::new("/nonexistent")).is_none());
137 }
138
139 #[test]
140 fn canonical_fallback_finds_existing_file() {
141 let temp = std::env::temp_dir().join("fallow-test-canonical-fallback");
142 let _ = std::fs::create_dir_all(&temp);
143 let test_file = temp.join("test.ts");
144 std::fs::write(&test_file, "").unwrap();
145
146 let files = vec![DiscoveredFile {
147 id: FileId(42),
148 path: test_file.clone(),
149 size_bytes: 0,
150 }];
151 let fallback = CanonicalFallback::new(&files);
152
153 let canonical = test_file.canonicalize().unwrap();
154 assert_eq!(fallback.get(&canonical), Some(FileId(42)));
155
156 assert_eq!(fallback.get(&canonical), Some(FileId(42)));
158
159 let _ = std::fs::remove_dir_all(&temp);
160 }
161
162 #[test]
163 fn canonical_fallback_returns_none_for_missing_path() {
164 let temp = std::env::temp_dir().join("fallow-test-canonical-miss");
165 let _ = std::fs::create_dir_all(&temp);
166 let test_file = temp.join("exists.ts");
167 std::fs::write(&test_file, "").unwrap();
168
169 let files = vec![DiscoveredFile {
170 id: FileId(1),
171 path: test_file,
172 size_bytes: 0,
173 }];
174 let fallback = CanonicalFallback::new(&files);
175 assert!(fallback.get(Path::new("/nonexistent/file.ts")).is_none());
176
177 let _ = std::fs::remove_dir_all(&temp);
178 }
179}
180
181pub const OUTPUT_DIRS: &[&str] = &["dist", "build", "out", "esm", "cjs"];
186
187pub const SOURCE_EXTS: &[&str] = &["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
189
190pub const RN_PLATFORM_PREFIXES: &[&str] = &[".web", ".ios", ".android", ".native"];