chaste_types/
module_path.rs1use std::sync::LazyLock;
5
6use crate::error::{Error, Result};
7use crate::name::{package_name, PackageName, PackageNameBorrowed, PackageNamePositions};
8
9pub static ROOT_MODULE_PATH: LazyLock<ModulePath> =
10 LazyLock::new(|| ModulePath::new("".to_string()).unwrap());
11
12#[derive(Debug, PartialEq, Eq, Clone)]
13enum ModulePathSegmentInternal {
14 Arbitrary(usize),
15 NodeModules(usize),
16 PackageName(usize, PackageNamePositions),
17}
18
19impl ModulePathSegmentInternal {
20 fn end_idx(&self) -> usize {
21 match self {
22 Self::Arbitrary(i) => *i,
23 Self::NodeModules(i) => *i,
24 Self::PackageName(i, _) => *i,
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
30pub struct ModulePath {
31 inner: String,
32 segments: Vec<ModulePathSegmentInternal>,
33}
34
35impl ModulePath {
36 pub fn new(value: String) -> Result<Self> {
37 let mut segments = Vec::new();
38 let mut end_idx = 0usize;
39 let mut inside_node_modules = false;
40 let mut expecting_package_name_now = false;
41 let mut inside_scoped = false;
42 for (i, segment) in value.split("/").enumerate() {
43 end_idx += segment.len();
44 if i != 0 {
45 end_idx += 1;
46 }
47 debug_assert!(!expecting_package_name_now || inside_node_modules);
48 match segment {
49 "" => {
50 if value.is_empty() {
52 break;
53 }
54 return Err(Error::InvalidModulePath(value.to_string()));
55 }
56 "node_modules" => {
57 if expecting_package_name_now {
58 return Err(Error::InvalidModulePath(value.to_string()));
59 }
60 inside_node_modules = true;
61 expecting_package_name_now = true;
62 segments.push(ModulePathSegmentInternal::NodeModules(end_idx));
63 }
64 _ if !inside_node_modules => {
65 segments.push(ModulePathSegmentInternal::Arbitrary(end_idx));
66 }
67 seg if expecting_package_name_now && !inside_scoped && seg.starts_with("@") => {
68 inside_scoped = true;
69 }
70 _ if expecting_package_name_now => {
71 let last_seg = segments.last().unwrap();
72 let start_idx = last_seg.end_idx() + 1;
73 let pn_str = &value[start_idx..end_idx];
74 let (remaining, pn_positions) = package_name(pn_str)
75 .map_err(|_| Error::InvalidPackageName(pn_str.to_string()))?;
76 if !remaining.is_empty() {
77 return Err(Error::InvalidPackageName(pn_str.to_string()));
78 }
79 segments.push(ModulePathSegmentInternal::PackageName(
80 end_idx,
81 pn_positions,
82 ));
83
84 inside_scoped = false;
85 expecting_package_name_now = false;
86 }
87 _ => return Err(Error::InvalidModulePath(value.to_string())),
88 }
89 }
90 if inside_scoped
91 || segments
92 .last()
93 .is_some_and(|s| matches!(s, ModulePathSegmentInternal::NodeModules(_)))
94 {
95 return Err(Error::InvalidModulePath(value.to_string()));
96 }
97
98 debug_assert_eq!(
99 segments.last().map(|s| s.end_idx()).unwrap_or(0),
100 value.len()
101 );
102 debug_assert!({
103 let mut segs = segments.iter().peekable();
104 while let Some(ModulePathSegmentInternal::Arbitrary(..)) = segs.peek() {
105 debug_assert!(matches!(
106 segs.next(),
107 Some(ModulePathSegmentInternal::Arbitrary(..))
108 ));
109 }
110 while let Some(seg) = segs.next() {
111 debug_assert!(matches!(seg, ModulePathSegmentInternal::NodeModules(..)));
112 debug_assert!(matches!(
113 segs.next(),
114 Some(ModulePathSegmentInternal::PackageName(..))
115 ));
116 }
117 segs.next().is_none()
118 });
119
120 Ok(Self {
121 inner: value,
122 segments,
123 })
124 }
125
126 pub fn implied_package_name(&self) -> Option<PackageName> {
127 let iter = self.iter();
128 match iter.last() {
129 Some(ModulePathSegment::PackageName(pn)) => Some(pn.to_owned()),
130 Some(ModulePathSegment::Arbitrary(a)) => match self.segments.len() {
131 1 => PackageName::new(a.to_string()).ok(),
132 2 if self.inner.starts_with("@") => PackageName::new(self.inner.clone()).ok(),
133 0 => unreachable!(),
134 len => {
135 let scope_start = self.segments.get(len - 2).unwrap().end_idx() + 1;
136 PackageName::new(self.inner[scope_start..].to_string()).ok()
137 }
138 },
139 Some(ModulePathSegment::NodeModules(_)) => unreachable!(),
140 None => None,
141 }
142 }
143}
144
145impl AsRef<str> for ModulePath {
146 fn as_ref(&self) -> &str {
147 &self.inner
148 }
149}
150
151#[derive(Debug, PartialEq, Eq, Clone)]
152pub enum ModulePathSegment<'a> {
153 Arbitrary(&'a str),
154 NodeModules(&'a str),
155 PackageName(PackageNameBorrowed<'a>),
156}
157impl AsRef<str> for ModulePathSegment<'_> {
158 fn as_ref(&self) -> &str {
159 match self {
160 ModulePathSegment::Arbitrary(i) => i,
161 ModulePathSegment::NodeModules(i) => i,
162 ModulePathSegment::PackageName(package_name_borrowed) => package_name_borrowed.as_ref(),
163 }
164 }
165}
166impl PartialOrd for ModulePathSegment<'_> {
167 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
168 Some(self.cmp(other))
169 }
170}
171impl Ord for ModulePathSegment<'_> {
172 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
173 self.as_ref().cmp(other.as_ref())
174 }
175}
176
177pub struct ModulePathIter<'a> {
178 inner: &'a ModulePath,
179 idx: usize,
180}
181
182impl<'a> Iterator for ModulePathIter<'a> {
183 type Item = ModulePathSegment<'a>;
184
185 fn next(&mut self) -> Option<Self::Item> {
186 let seg_idx = self.idx;
187 self.idx += 1;
188 let seg_int = self.inner.segments.get(seg_idx)?;
189 let start_idx = if seg_idx == 0 {
190 0
191 } else {
192 self.inner
193 .segments
194 .get(seg_idx - 1)
195 .map(|s| s.end_idx() + 1)
196 .unwrap()
197 };
198 let slic = &self.inner.inner[start_idx..seg_int.end_idx()];
199 Some(match seg_int {
200 ModulePathSegmentInternal::Arbitrary(_) => ModulePathSegment::Arbitrary(slic),
201 ModulePathSegmentInternal::NodeModules(_) => ModulePathSegment::NodeModules(slic),
202 ModulePathSegmentInternal::PackageName(_, package_name_positions) => {
203 ModulePathSegment::PackageName(PackageNameBorrowed {
204 inner: slic,
205 positions: package_name_positions,
206 })
207 }
208 })
209 }
210}
211
212impl ModulePath {
213 pub fn iter<'a>(&'a self) -> ModulePathIter<'a> {
214 ModulePathIter {
215 inner: self,
216 idx: 0,
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use std::sync::LazyLock;
224
225 use crate::error::Result;
226 use crate::name::PackageName;
227
228 use super::{ModulePath, ModulePathSegment};
229
230 static SEMVER_PN: LazyLock<PackageName> =
231 LazyLock::new(|| PackageName::new("semver".to_string()).unwrap());
232 static TESTCASE_PN: LazyLock<PackageName> =
233 LazyLock::new(|| PackageName::new("@chastelock/testcase".to_string()).unwrap());
234
235 #[test]
236 fn basic_mod() -> Result<()> {
237 let path = ModulePath::new("node_modules/semver".to_string())?;
238 let mut segments = path.iter();
239 assert_eq!(
240 segments.next(),
241 Some(ModulePathSegment::NodeModules("node_modules"))
242 );
243 assert_eq!(
244 segments.next(),
245 Some(ModulePathSegment::PackageName(SEMVER_PN.as_borrowed()))
246 );
247 assert_eq!(segments.next(), None);
248
249 Ok(())
250 }
251
252 #[test]
253 fn basic_mod_scoped() -> Result<()> {
254 let path = ModulePath::new("node_modules/@chastelock/testcase".to_string())?;
255 let mut segments = path.iter();
256 assert_eq!(
257 segments.next(),
258 Some(ModulePathSegment::NodeModules("node_modules"))
259 );
260 assert_eq!(
261 segments.next(),
262 Some(ModulePathSegment::PackageName(TESTCASE_PN.as_borrowed()))
263 );
264 assert_eq!(segments.next(), None);
265
266 Ok(())
267 }
268
269 #[test]
270 fn empty() -> Result<()> {
271 let path = ModulePath::new("".to_string())?;
272 let mut segments = path.iter();
273 assert_eq!(segments.next(), None);
274
275 Ok(())
276 }
277
278 #[test]
279 fn mod_inside_workspace_member() -> Result<()> {
280 let path =
281 ModulePath::new("arbitrary/prefix/node_modules/@chastelock/testcase".to_string())?;
282 let mut segments = path.iter();
283 assert_eq!(
284 segments.next(),
285 Some(ModulePathSegment::Arbitrary("arbitrary"))
286 );
287 assert_eq!(
288 segments.next(),
289 Some(ModulePathSegment::Arbitrary("prefix"))
290 );
291 assert_eq!(
292 segments.next(),
293 Some(ModulePathSegment::NodeModules("node_modules"))
294 );
295 assert_eq!(
296 segments.next(),
297 Some(ModulePathSegment::PackageName(TESTCASE_PN.as_borrowed()))
298 );
299 assert_eq!(segments.next(), None);
300
301 Ok(())
302 }
303
304 #[test]
305 fn nested_modules() -> Result<()> {
306 let path =
307 ModulePath::new("node_modules/@chastelock/testcase/node_modules/semver".to_string())?;
308 let mut segments = path.iter();
309 assert_eq!(
310 segments.next(),
311 Some(ModulePathSegment::NodeModules("node_modules"))
312 );
313 assert_eq!(
314 segments.next(),
315 Some(ModulePathSegment::PackageName(TESTCASE_PN.as_borrowed()))
316 );
317 assert_eq!(
318 segments.next(),
319 Some(ModulePathSegment::NodeModules("node_modules"))
320 );
321 assert_eq!(
322 segments.next(),
323 Some(ModulePathSegment::PackageName(SEMVER_PN.as_borrowed()))
324 );
325 assert_eq!(segments.next(), None);
326
327 Ok(())
328 }
329
330 #[test]
331 fn bs_paths() -> Result<()> {
332 fn invalid(input: &str) {
333 assert!(ModulePath::new(input.to_string()).is_err());
334 }
335 invalid("/");
336 invalid("a/");
337 invalid("node_modules");
338 invalid("node_modules/@chastelock/testcase/something/deeper");
339 invalid("node_modules/@chastelock");
340 invalid("node_modules/node_modules/n");
341 Ok(())
342 }
343}