fallow_graph/resolve/
types.rs1use std::path::{Path, PathBuf};
4
5use oxc_resolver::Resolver;
6use rustc_hash::{FxHashMap, FxHashSet};
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: FxHashSet<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 const 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::debug!(
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 dunce::canonicalize(&f.path)
116 .ok()
117 .map(|canonical| (canonical, f.id))
118 })
119 .collect()
120 });
121 map.get(canonical).copied()
122 }
123}
124
125#[cfg(all(test, not(miri)))]
126mod tests {
127 use super::*;
128 use fallow_types::discover::DiscoveredFile;
129
130 #[test]
131 fn canonical_fallback_returns_none_for_empty_files() {
132 let files: Vec<DiscoveredFile> = vec![];
133 let fallback = CanonicalFallback::new(&files);
134 assert!(fallback.get(Path::new("/nonexistent")).is_none());
135 }
136
137 #[test]
138 fn canonical_fallback_finds_existing_file() {
139 let temp = std::env::temp_dir().join("fallow-test-canonical-fallback");
140 let _ = std::fs::create_dir_all(&temp);
141 let test_file = temp.join("test.ts");
142 std::fs::write(&test_file, "").unwrap();
143
144 let files = vec![DiscoveredFile {
145 id: FileId(42),
146 path: test_file.clone(),
147 size_bytes: 0,
148 }];
149 let fallback = CanonicalFallback::new(&files);
150
151 let canonical = dunce::canonicalize(&test_file).unwrap();
152 assert_eq!(fallback.get(&canonical), Some(FileId(42)));
153
154 assert_eq!(fallback.get(&canonical), Some(FileId(42)));
156
157 let _ = std::fs::remove_dir_all(&temp);
158 }
159
160 #[test]
161 fn canonical_fallback_returns_none_for_missing_path() {
162 let temp = std::env::temp_dir().join("fallow-test-canonical-miss");
163 let _ = std::fs::create_dir_all(&temp);
164 let test_file = temp.join("exists.ts");
165 std::fs::write(&test_file, "").unwrap();
166
167 let files = vec![DiscoveredFile {
168 id: FileId(1),
169 path: test_file,
170 size_bytes: 0,
171 }];
172 let fallback = CanonicalFallback::new(&files);
173 assert!(fallback.get(Path::new("/nonexistent/file.ts")).is_none());
174
175 let _ = std::fs::remove_dir_all(&temp);
176 }
177}
178
179pub const OUTPUT_DIRS: &[&str] = &["dist", "build", "out", "esm", "cjs"];
184
185pub const SOURCE_EXTS: &[&str] = &["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
187
188pub const RN_PLATFORM_PREFIXES: &[&str] = &[".web", ".ios", ".android", ".native"];