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 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
131 #[test]
132 fn canonical_fallback_returns_none_for_empty_files() {
133 let files: Vec<DiscoveredFile> = vec![];
134 let fallback = CanonicalFallback::new(&files);
135 assert!(fallback.get(Path::new("/nonexistent")).is_none());
136 }
137
138 #[test]
139 fn canonical_fallback_finds_existing_file() {
140 let temp = std::env::temp_dir().join("fallow-test-canonical-fallback");
141 let _ = std::fs::create_dir_all(&temp);
142 let test_file = temp.join("test.ts");
143 std::fs::write(&test_file, "").unwrap();
144
145 let files = vec![DiscoveredFile {
146 id: FileId(42),
147 path: test_file.clone(),
148 size_bytes: 0,
149 }];
150 let fallback = CanonicalFallback::new(&files);
151
152 let canonical = test_file.canonicalize().unwrap();
153 assert_eq!(fallback.get(&canonical), Some(FileId(42)));
154
155 assert_eq!(fallback.get(&canonical), Some(FileId(42)));
157
158 let _ = std::fs::remove_dir_all(&temp);
159 }
160
161 #[test]
162 fn canonical_fallback_returns_none_for_missing_path() {
163 let temp = std::env::temp_dir().join("fallow-test-canonical-miss");
164 let _ = std::fs::create_dir_all(&temp);
165 let test_file = temp.join("exists.ts");
166 std::fs::write(&test_file, "").unwrap();
167
168 let files = vec![DiscoveredFile {
169 id: FileId(1),
170 path: test_file,
171 size_bytes: 0,
172 }];
173 let fallback = CanonicalFallback::new(&files);
174 assert!(fallback.get(Path::new("/nonexistent/file.ts")).is_none());
175
176 let _ = std::fs::remove_dir_all(&temp);
177 }
178}
179
180pub const OUTPUT_DIRS: &[&str] = &["dist", "build", "out", "esm", "cjs"];
185
186pub const SOURCE_EXTS: &[&str] = &["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
188
189pub const RN_PLATFORM_PREFIXES: &[&str] = &[".web", ".ios", ".android", ".native"];