Skip to main content

fallow_types/
path_util.rs

1//! Cross-platform path classification helpers.
2//!
3//! Rust's [`std::path::Path::is_absolute`] uses platform-specific semantics:
4//! on Unix a path starting with `/` is absolute, while on Windows a path needs
5//! a drive prefix (`C:\foo`) or a UNC root (`\\?\C:\foo`). A POSIX-style
6//! absolute path like `/project/foo.ts` returns `false` from `is_absolute()`
7//! on Windows, which breaks code that conditionally joins relative paths
8//! against a root.
9//!
10//! Use these helpers whenever input paths may originate from user-supplied
11//! data shared across CI runners, config files, source maps, or diff output.
12
13use std::path::{Component, Path};
14
15/// Returns `true` if `path` is anchored under either platform's path
16/// conventions.
17///
18/// Recognises host-absolute paths, POSIX-style rooted paths (`/foo`), and
19/// Windows drive-prefixed paths (`C:\foo`, `c:/foo`) regardless of the host OS.
20pub fn is_absolute_path_any_platform(path: &Path) -> bool {
21    if path.is_absolute() {
22        return true;
23    }
24    if matches!(path.components().next(), Some(Component::RootDir)) {
25        return true;
26    }
27    looks_like_windows_drive_absolute(path.as_os_str().as_encoded_bytes())
28}
29
30/// Returns `true` if `value` looks like a Windows-style absolute path
31/// with a drive letter, colon, and path separator.
32///
33/// This string-shaped variant is useful before constructing a [`Path`].
34pub fn looks_like_windows_absolute_path(value: &str) -> bool {
35    looks_like_windows_drive_absolute(value.as_bytes())
36}
37
38fn looks_like_windows_drive_absolute(bytes: &[u8]) -> bool {
39    bytes.len() >= 3
40        && bytes[0].is_ascii_alphabetic()
41        && bytes[1] == b':'
42        && matches!(bytes[2], b'/' | b'\\')
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use std::path::PathBuf;
49
50    #[test]
51    fn posix_style_root_is_absolute_on_any_platform() {
52        assert!(is_absolute_path_any_platform(Path::new(
53            "/project/src/a.ts"
54        )));
55        assert!(is_absolute_path_any_platform(Path::new("/foo")));
56        assert!(is_absolute_path_any_platform(Path::new("/")));
57    }
58
59    #[test]
60    fn windows_drive_letter_is_absolute_on_any_platform() {
61        assert!(is_absolute_path_any_platform(Path::new(
62            "C:\\project\\src\\a.ts"
63        )));
64        assert!(is_absolute_path_any_platform(Path::new(
65            "C:/project/src/a.ts"
66        )));
67        assert!(is_absolute_path_any_platform(Path::new("d:/foo")));
68    }
69
70    #[test]
71    fn relative_paths_return_false() {
72        assert!(!is_absolute_path_any_platform(Path::new("src/a.ts")));
73        assert!(!is_absolute_path_any_platform(Path::new("./src/a.ts")));
74        assert!(!is_absolute_path_any_platform(Path::new("../parent/a.ts")));
75        assert!(!is_absolute_path_any_platform(Path::new("a.ts")));
76        assert!(!is_absolute_path_any_platform(Path::new("")));
77    }
78
79    #[cfg_attr(miri, ignore)]
80    #[test]
81    fn host_absolute_works_through_is_absolute() {
82        let cwd = std::env::current_dir().expect("current_dir");
83        assert!(is_absolute_path_any_platform(&cwd));
84    }
85
86    #[test]
87    fn looks_like_windows_absolute_path_recognises_drive_shapes() {
88        assert!(looks_like_windows_absolute_path("C:\\foo"));
89        assert!(looks_like_windows_absolute_path("c:/foo"));
90        assert!(looks_like_windows_absolute_path("Z:/very/deep/path.ts"));
91        assert!(!looks_like_windows_absolute_path("/foo"));
92        assert!(!looks_like_windows_absolute_path("src/foo"));
93        assert!(!looks_like_windows_absolute_path("C:"));
94        assert!(!looks_like_windows_absolute_path("CC:/foo"));
95        assert!(!looks_like_windows_absolute_path(""));
96    }
97
98    #[test]
99    fn drive_prefix_path_string_is_absolute_via_os_str_bytes() {
100        let p = PathBuf::from("E:/source/map.js");
101        assert!(is_absolute_path_any_platform(&p));
102    }
103}