Coverage Report

Created: 2026-10-28 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/oxc_resolver-11.19.1/src/path.rs
Line
Count
Source
1
//! Path Utilities
2
//!
3
//! Code adapted from the following libraries
4
//! * [path-absolutize](https://docs.rs/path-absolutize)
5
//! * [normalize_path](https://docs.rs/normalize-path)
6
use std::path::{Component, Path, PathBuf};
7
8
pub const SLASH_START: &[char; 2] = &['/', '\\'];
9
10
/// Extension trait to add path normalization to std's [`Path`].
11
pub trait PathUtil {
12
    /// Normalize this path without performing I/O.
13
    ///
14
    /// All redundant separator and up-level references are collapsed.
15
    ///
16
    /// However, this does not resolve links.
17
    fn normalize(&self) -> PathBuf;
18
19
    /// Like `normalize`, but don't require the path to be absolute.
20
    fn normalize_relative(&self) -> PathBuf;
21
22
    /// Normalize with subpath assuming this path is normalized without performing I/O.
23
    ///
24
    /// All redundant separator and up-level references are collapsed.
25
    ///
26
    /// However, this does not resolve links.
27
    fn normalize_with<P: AsRef<Path>>(&self, subpath: P) -> PathBuf;
28
29
    /// Defined in ESM PACKAGE_TARGET_RESOLVE
30
    /// If target split on "/" or "\" contains any "", ".", "..", or "node_modules" segments after the first "." segment, case insensitive and including percent encoded variants
31
    fn is_invalid_exports_target(&self) -> bool;
32
}
33
34
impl PathUtil for Path {
35
    // https://github.com/parcel-bundler/parcel/blob/e0b99c2a42e9109a9ecbd6f537844a1b33e7faf5/packages/utils/node-resolver-rs/src/path.rs#L7
36
0
    fn normalize(&self) -> PathBuf {
37
0
        let mut components = self.components().peekable();
38
0
        let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek() {
39
0
            let buf = PathBuf::from(c.as_os_str());
40
0
            components.next();
41
0
            buf
42
        } else {
43
0
            PathBuf::new()
44
        };
45
46
0
        for component in components {
47
0
            match component {
48
0
                Component::Prefix(..) => unreachable!("Path {:?}", self),
49
0
                Component::RootDir => {
50
0
                    ret.push(component.as_os_str());
51
0
                }
52
0
                Component::CurDir => {}
53
0
                Component::ParentDir => {
54
0
                    ret.pop();
55
0
                }
56
0
                Component::Normal(c) => {
57
0
                    ret.push(c);
58
0
                }
59
            }
60
        }
61
62
0
        ret
63
0
    }
64
65
0
    fn normalize_relative(&self) -> PathBuf {
66
0
        let mut normalized = PathBuf::new();
67
0
        for comp in self.components() {
68
0
            match comp {
69
                Component::ParentDir => {
70
0
                    if !normalized.pop() {
71
0
                        normalized.push(Component::ParentDir);
72
0
                    }
73
                }
74
0
                Component::CurDir => {}
75
0
                comp => normalized.push(comp),
76
            }
77
        }
78
0
        normalized
79
0
    }
80
81
    // https://github.com/parcel-bundler/parcel/blob/e0b99c2a42e9109a9ecbd6f537844a1b33e7faf5/packages/utils/node-resolver-rs/src/path.rs#L37
82
0
    fn normalize_with<B: AsRef<Self>>(&self, subpath: B) -> PathBuf {
83
0
        let subpath = subpath.as_ref();
84
85
0
        let mut components = subpath.components();
86
87
0
        let Some(head) = components.next() else { return subpath.to_path_buf() };
88
89
0
        if matches!(head, Component::Prefix(..) | Component::RootDir) {
90
0
            return subpath.to_path_buf();
91
0
        }
92
93
0
        let mut ret = self.to_path_buf();
94
0
        for component in std::iter::once(head).chain(components) {
95
0
            match component {
96
0
                Component::CurDir => {}
97
0
                Component::ParentDir => {
98
0
                    ret.pop();
99
0
                }
100
0
                Component::Normal(c) => {
101
0
                    ret.push(c);
102
0
                }
103
                Component::Prefix(..) | Component::RootDir => {
104
0
                    unreachable!("Path {:?} Subpath {:?}", self, subpath)
105
                }
106
            }
107
        }
108
109
0
        ret
110
0
    }
Unexecuted instantiation: <std::path::Path as oxc_resolver::path::PathUtil>::normalize_with::<&std::path::Path>
Unexecuted instantiation: <std::path::Path as oxc_resolver::path::PathUtil>::normalize_with::<std::path::PathBuf>
Unexecuted instantiation: <std::path::Path as oxc_resolver::path::PathUtil>::normalize_with::<&std::path::PathBuf>
Unexecuted instantiation: <std::path::Path as oxc_resolver::path::PathUtil>::normalize_with::<&&mut std::path::PathBuf>
Unexecuted instantiation: <std::path::Path as oxc_resolver::path::PathUtil>::normalize_with::<&str>
111
112
0
    fn is_invalid_exports_target(&self) -> bool {
113
0
        self.components().enumerate().any(|(index, c)| match c {
114
0
            Component::ParentDir => true,
115
0
            Component::CurDir => index > 0,
116
0
            Component::Normal(c) => c.eq_ignore_ascii_case("node_modules"),
117
0
            _ => false,
118
0
        })
119
0
    }
120
}
121
122
// https://github.com/webpack/enhanced-resolve/blob/main/test/path.test.js
123
#[test]
124
fn is_invalid_exports_target() {
125
    let test_cases = [
126
        "../a.js",
127
        "../",
128
        "./a/b/../../../c.js",
129
        "./a/b/../../../",
130
        "./../../c.js",
131
        "./../../",
132
        "./a/../b/../../c.js",
133
        "./a/../b/../../",
134
        "./././../",
135
    ];
136
137
    for case in test_cases {
138
        assert!(Path::new(case).is_invalid_exports_target(), "{case}");
139
    }
140
141
    assert!(!Path::new("C:").is_invalid_exports_target());
142
    assert!(!Path::new("/").is_invalid_exports_target());
143
}
144
145
#[test]
146
fn normalize() {
147
    assert_eq!(Path::new("/foo/.././foo/").normalize(), Path::new("/foo"));
148
    assert_eq!(Path::new("C://").normalize(), Path::new("C://"));
149
    assert_eq!(Path::new("C:").normalize(), Path::new("C:"));
150
    assert_eq!(Path::new(r"\\server\share").normalize(), Path::new(r"\\server\share"));
151
}
152
153
#[test]
154
fn normalize_relative() {
155
    assert_eq!(Path::new("foo/../../foo/").normalize_relative(), Path::new("../foo"));
156
    assert_eq!(Path::new("foo/.././foo/").normalize_relative(), Path::new("foo"));
157
    assert_eq!(Path::new("foo../../..").normalize_relative(), Path::new(".."));
158
    assert_eq!(Path::new("jest-runner-../../").normalize_relative(), Path::new(""));
159
}