/rust/registry/src/index.crates.io-1949cf8c6b5b557f/oxc_resolver-11.19.1/src/specifier.rs
Line | Count | Source |
1 | | use std::borrow::Cow; |
2 | | |
3 | | use crate::error::SpecifierError; |
4 | | |
5 | | #[derive(Debug)] |
6 | | pub struct Specifier<'a> { |
7 | | path: Cow<'a, str>, |
8 | | pub query: Option<&'a str>, |
9 | | pub fragment: Option<&'a str>, |
10 | | } |
11 | | |
12 | | impl<'a> Specifier<'a> { |
13 | 0 | pub fn path(&'a self) -> &'a str { |
14 | 0 | self.path.as_ref() |
15 | 0 | } |
16 | | |
17 | 0 | pub fn parse(specifier: &'a str) -> Result<Self, SpecifierError> { |
18 | 0 | if specifier.is_empty() { |
19 | | #[cold] |
20 | 0 | fn empty_specifier_error(specifier: &str) -> SpecifierError { |
21 | 0 | SpecifierError::Empty(specifier.to_string()) |
22 | 0 | } |
23 | 0 | return Err(empty_specifier_error(specifier)); |
24 | 0 | } |
25 | 0 | let offset = match specifier.as_bytes()[0] { |
26 | 0 | b'/' | b'.' | b'#' => 1, |
27 | 0 | _ => 0, |
28 | | }; |
29 | 0 | let (path, query, fragment) = Self::parse_query_fragment(specifier, offset); |
30 | 0 | if path.is_empty() { |
31 | | #[cold] |
32 | 0 | fn empty_path_error(specifier: &str) -> SpecifierError { |
33 | 0 | SpecifierError::Empty(specifier.to_string()) |
34 | 0 | } |
35 | 0 | return Err(empty_path_error(specifier)); |
36 | 0 | } |
37 | 0 | Ok(Self { path, query, fragment }) |
38 | 0 | } |
39 | | |
40 | 0 | fn parse_query_fragment( |
41 | 0 | specifier: &'a str, |
42 | 0 | skip: usize, |
43 | 0 | ) -> (Cow<'a, str>, Option<&'a str>, Option<&'a str>) { |
44 | 0 | let mut query_start: Option<usize> = None; |
45 | 0 | let mut fragment_start: Option<usize> = None; |
46 | | |
47 | 0 | let mut prev = specifier.as_bytes()[0]; |
48 | | // Optimize for the common case: most specifiers don't have escaped characters |
49 | 0 | let mut escaped_indexes: Option<Vec<usize>> = None; |
50 | 0 | for (i, &b) in specifier.as_bytes().iter().enumerate().skip(skip) { |
51 | 0 | if b == b'?' && query_start.is_none() { |
52 | 0 | query_start = Some(i); |
53 | 0 | } |
54 | 0 | if b == b'#' { |
55 | 0 | if prev == b'\0' { |
56 | 0 | escaped_indexes.get_or_insert_with(Vec::new).push(i - 1); |
57 | 0 | } else { |
58 | 0 | fragment_start = Some(i); |
59 | 0 | break; |
60 | | } |
61 | 0 | } |
62 | 0 | prev = b; |
63 | | } |
64 | | |
65 | 0 | let (path, query, fragment) = match (query_start, fragment_start) { |
66 | 0 | (Some(i), Some(j)) => { |
67 | 0 | debug_assert!(i < j); |
68 | 0 | (&specifier[..i], Some(&specifier[i..j]), Some(&specifier[j..])) |
69 | | } |
70 | 0 | (Some(i), None) => (&specifier[..i], Some(&specifier[i..]), None), |
71 | 0 | (None, Some(j)) => (&specifier[..j], None, Some(&specifier[j..])), |
72 | 0 | _ => (specifier, None, None), |
73 | | }; |
74 | | |
75 | 0 | let path = escaped_indexes.map_or(Cow::Borrowed(path), |escaped_indexes| { |
76 | 0 | let mut s = String::with_capacity(path.len()); |
77 | 0 | for (i, &b) in path.as_bytes().iter().enumerate() { |
78 | 0 | if !escaped_indexes.contains(&i) { |
79 | 0 | s.push(b as char); |
80 | 0 | } |
81 | | } |
82 | 0 | Cow::Owned(s) |
83 | 0 | }); |
84 | | |
85 | 0 | (path, query, fragment) |
86 | 0 | } |
87 | | } |
88 | | |
89 | | #[cfg(test)] |
90 | | mod tests { |
91 | | use super::{Specifier, SpecifierError}; |
92 | | |
93 | | #[test] |
94 | | fn debug() { |
95 | | let specifier = Specifier::parse("/").unwrap(); |
96 | | assert_eq!( |
97 | | format!("{specifier:?}"), |
98 | | r#"Specifier { path: "/", query: None, fragment: None }"# |
99 | | ); |
100 | | } |
101 | | |
102 | | #[test] |
103 | | fn empty() { |
104 | | let specifiers = ["", "?"]; |
105 | | for specifier in specifiers { |
106 | | let error = Specifier::parse(specifier).unwrap_err(); |
107 | | assert_eq!(error, SpecifierError::Empty(specifier.to_string())); |
108 | | } |
109 | | } |
110 | | |
111 | | #[test] |
112 | | fn absolute() -> Result<(), SpecifierError> { |
113 | | let specifier = "/test?#"; |
114 | | let parsed = Specifier::parse(specifier)?; |
115 | | assert_eq!(parsed.path, "/test"); |
116 | | assert_eq!(parsed.query, Some("?")); |
117 | | assert_eq!(parsed.fragment, Some("#")); |
118 | | Ok(()) |
119 | | } |
120 | | |
121 | | #[test] |
122 | | fn relative() -> Result<(), SpecifierError> { |
123 | | let specifiers = ["./test", "../test", "../../test"]; |
124 | | for specifier in specifiers { |
125 | | let mut r = specifier.to_string(); |
126 | | r.push_str("?#"); |
127 | | let parsed = Specifier::parse(&r)?; |
128 | | assert_eq!(parsed.path, specifier); |
129 | | assert_eq!(parsed.query, Some("?")); |
130 | | assert_eq!(parsed.fragment, Some("#")); |
131 | | } |
132 | | Ok(()) |
133 | | } |
134 | | |
135 | | #[test] |
136 | | fn hash() -> Result<(), SpecifierError> { |
137 | | let specifiers = ["#", "#path"]; |
138 | | for specifier in specifiers { |
139 | | let mut r = specifier.to_string(); |
140 | | r.push_str("?#"); |
141 | | let parsed = Specifier::parse(&r)?; |
142 | | assert_eq!(parsed.path, specifier); |
143 | | assert_eq!(parsed.query, Some("?")); |
144 | | assert_eq!(parsed.fragment, Some("#")); |
145 | | } |
146 | | Ok(()) |
147 | | } |
148 | | |
149 | | #[test] |
150 | | fn module() -> Result<(), SpecifierError> { |
151 | | let specifiers = ["module"]; |
152 | | for specifier in specifiers { |
153 | | let mut r = specifier.to_string(); |
154 | | r.push_str("?#"); |
155 | | let parsed = Specifier::parse(&r)?; |
156 | | assert_eq!(parsed.path, specifier); |
157 | | assert_eq!(parsed.query, Some("?")); |
158 | | assert_eq!(parsed.fragment, Some("#")); |
159 | | } |
160 | | Ok(()) |
161 | | } |
162 | | |
163 | | #[test] |
164 | | fn query_fragment() -> Result<(), SpecifierError> { |
165 | | let data = [ |
166 | | ("a?", Some("?"), None), |
167 | | ("a?query", Some("?query"), None), |
168 | | ("a?query1?query2", Some("?query1?query2"), None), |
169 | | ("a?query1?query2?query3", Some("?query1?query2?query3"), None), |
170 | | ("a#", None, Some("#")), |
171 | | ("a#b#c", None, Some("#b#c")), |
172 | | ("a#fragment", None, Some("#fragment")), |
173 | | ("a?#", Some("?"), Some("#")), |
174 | | ("a?#fragment", Some("?"), Some("#fragment")), |
175 | | ("a?query#", Some("?query"), Some("#")), |
176 | | ("a?query#fragment", Some("?query"), Some("#fragment")), |
177 | | ("a#fragment?", None, Some("#fragment?")), |
178 | | ("a#fragment?query", None, Some("#fragment?query")), |
179 | | ]; |
180 | | |
181 | | for (specifier_str, query, fragment) in data { |
182 | | let specifier = Specifier::parse(specifier_str)?; |
183 | | assert_eq!(specifier.path, "a", "{specifier_str}"); |
184 | | assert_eq!(specifier.query, query, "{specifier_str}"); |
185 | | assert_eq!(specifier.fragment, fragment, "{specifier_str}"); |
186 | | } |
187 | | |
188 | | Ok(()) |
189 | | } |
190 | | |
191 | | #[test] |
192 | | // https://github.com/webpack/enhanced-resolve/blob/main/test/identifier.test.js |
193 | | fn enhanced_resolve_edge_cases() -> Result<(), SpecifierError> { |
194 | | let data = [ |
195 | | ("path/#", "path/", "", "#"), |
196 | | ("path/as/?", "path/as/", "?", ""), |
197 | | ("path/#/?", "path/", "", "#/?"), |
198 | | ("path/#repo#hash", "path/", "", "#repo#hash"), |
199 | | ("path/#r#hash", "path/", "", "#r#hash"), |
200 | | ("path/#repo/#repo2#hash", "path/", "", "#repo/#repo2#hash"), |
201 | | ("path/#r/#r#hash", "path/", "", "#r/#r#hash"), |
202 | | ("path/#/not/a/hash?not-a-query", "path/", "", "#/not/a/hash?not-a-query"), |
203 | | ]; |
204 | | |
205 | | for (specifier_str, path, query, fragment) in data { |
206 | | let specifier = Specifier::parse(specifier_str)?; |
207 | | assert_eq!(specifier.path, path, "{specifier_str}"); |
208 | | assert_eq!(specifier.query.unwrap_or(""), query, "{specifier_str}"); |
209 | | assert_eq!(specifier.fragment.unwrap_or(""), fragment, "{specifier_str}"); |
210 | | } |
211 | | |
212 | | Ok(()) |
213 | | } |
214 | | |
215 | | // https://github.com/webpack/enhanced-resolve/blob/main/test/identifier.test.js |
216 | | #[test] |
217 | | fn enhanced_resolve_windows_like() -> Result<(), SpecifierError> { |
218 | | let data = [ |
219 | | ("path\\#", "path\\", "", "#"), |
220 | | ("path\\as\\?", "path\\as\\", "?", ""), |
221 | | ("path\\#\\?", "path\\", "", "#\\?"), |
222 | | ("path\\#repo#hash", "path\\", "", "#repo#hash"), |
223 | | ("path\\#r#hash", "path\\", "", "#r#hash"), |
224 | | ("path\\#repo\\#repo2#hash", "path\\", "", "#repo\\#repo2#hash"), |
225 | | ("path\\#r\\#r#hash", "path\\", "", "#r\\#r#hash"), |
226 | | ("path\\#/not/a/hash?not-a-query", "path\\", "", "#/not/a/hash?not-a-query"), |
227 | | ]; |
228 | | |
229 | | for (specifier_str, path, query, fragment) in data { |
230 | | let specifier = Specifier::parse(specifier_str)?; |
231 | | assert_eq!(specifier.path, path, "{specifier_str}"); |
232 | | assert_eq!(specifier.query.unwrap_or(""), query, "{specifier_str}"); |
233 | | assert_eq!(specifier.fragment.unwrap_or(""), fragment, "{specifier_str}"); |
234 | | } |
235 | | |
236 | | Ok(()) |
237 | | } |
238 | | } |