fallow_graph/graph/
package_entry.rs1use std::path::Path;
7
8use fallow_types::discover::FileId;
9
10use super::ModuleGraph;
11
12impl ModuleGraph {
13 #[must_use]
21 pub fn package_entry_file_id(
22 &self,
23 package_root: &Path,
24 entry_path: &Path,
25 lookup: impl Fn(&Path) -> Option<FileId>,
26 ) -> Option<FileId> {
27 lookup(entry_path).or_else(|| {
28 let canonical = dunce::canonicalize(entry_path).ok()?;
29 lookup(&canonical).or_else(|| {
30 match_canonical_entry_under_package(
31 self.modules
32 .iter()
33 .map(|module| (module.path.as_path(), module.file_id)),
34 package_root,
35 &canonical,
36 )
37 })
38 })
39 }
40}
41
42fn match_canonical_entry_under_package<'a>(
45 candidates: impl Iterator<Item = (&'a Path, FileId)>,
46 package_root: &Path,
47 canonical_entry: &Path,
48) -> Option<FileId> {
49 candidates
50 .filter(|(path, _)| path.starts_with(package_root))
51 .find_map(|(path, file_id)| {
52 (dunce::canonicalize(path).ok().as_deref() == Some(canonical_entry)).then_some(file_id)
53 })
54}
55
56#[cfg(all(test, unix))]
57mod tests {
58 use super::*;
59
60 #[cfg_attr(miri, ignore)]
64 #[test]
65 fn scoped_canonical_matches_module_reached_through_symlink() {
66 let dir = tempfile::tempdir().unwrap();
67 let real_dir = dir.path().join("real");
68 std::fs::create_dir(&real_dir).unwrap();
69 let real_file = real_dir.join("mod.ts");
70 std::fs::write(&real_file, "export const x = 1;\n").unwrap();
71 let link_dir = dir.path().join("link");
72 std::os::unix::fs::symlink(&real_dir, &link_dir).unwrap();
73
74 let module_raw_path = link_dir.join("mod.ts");
75 let canonical_entry = dunce::canonicalize(&real_file).unwrap();
76 let package_root = dir.path();
77
78 let candidates = [(module_raw_path.as_path(), FileId(7))];
79 assert_eq!(
80 match_canonical_entry_under_package(
81 candidates.iter().copied(),
82 package_root,
83 &canonical_entry,
84 ),
85 Some(FileId(7)),
86 );
87
88 let outside_root = dir.path().join("other-package");
89 assert_eq!(
90 match_canonical_entry_under_package(
91 candidates.iter().copied(),
92 &outside_root,
93 &canonical_entry,
94 ),
95 None,
96 );
97
98 let unrelated = dunce::canonicalize(dir.path()).unwrap().join("nope.ts");
99 assert_eq!(
100 match_canonical_entry_under_package(
101 candidates.iter().copied(),
102 package_root,
103 &unrelated,
104 ),
105 None,
106 );
107 }
108}