frs/cmds/rsl/rules/common/
path_resolution.rs1use syn::PathArguments;
2
3pub fn path_parts(path: &syn::Path) -> Option<Vec<String>> {
4 if path.leading_colon.is_some() || path.segments.is_empty() {
5 return None;
6 }
7
8 let last_segment = path.segments.len().saturating_sub(1);
9 let mut parts = Vec::new();
10 for (idx, segment) in path.segments.iter().enumerate() {
11 if idx != last_segment && !matches!(&segment.arguments, PathArguments::None) {
12 return None;
13 }
14 parts.push(segment.ident.to_string());
15 }
16 Some(parts)
17}
18
19pub fn associated_receiver_parts(path: &syn::Path) -> Option<Vec<String>> {
20 let parts = self::path_parts(path)?;
21 is_associated_fn_path(&parts)
22 .then(|| parts.get(..parts.len().saturating_sub(1)))
23 .flatten()
24 .map(ToOwned::to_owned)
25}
26
27pub fn is_associated_fn_path(parts: &[String]) -> bool {
28 parts
29 .get(..parts.len().saturating_sub(1))
30 .is_some_and(|prefix| prefix.iter().any(|part| is_type_name(part)))
31}
32
33pub fn is_non_fn_call_path(parts: &[String]) -> bool {
34 parts.last().is_some_and(|name| is_type_name(name))
35}
36
37pub fn is_import_style_path(parts: &[String]) -> bool {
38 parts
39 .first()
40 .is_some_and(|part| matches!(part.as_str(), "crate" | "self" | "super") || is_module_name(part))
41}
42
43fn is_module_name(name: &str) -> bool {
44 name.chars().next().is_some_and(char::is_lowercase)
45}
46
47fn is_type_name(name: &str) -> bool {
48 name.chars().next().is_some_and(char::is_uppercase)
49}
50
51pub fn normalize_path(current_module: &[String], parts: &[String]) -> Vec<String> {
52 let mut absolute_parts = current_module.to_vec();
53 let mut first_item = 0;
54
55 match parts.first().map(String::as_str) {
56 Some("crate") => {
57 absolute_parts.clear();
58 first_item = 1;
59 }
60 Some("self") => first_item = 1,
61 Some("super") => {
62 while parts.get(first_item).is_some_and(|part| part == "super") {
63 if absolute_parts.pop().is_none() {
64 return Vec::new();
65 }
66 first_item = first_item.saturating_add(1);
67 }
68 }
69 Some(_) | None => {}
70 }
71
72 if let Some(suffix) = parts.get(first_item..) {
73 absolute_parts.extend(suffix.iter().cloned());
74 }
75 absolute_parts
76}
77
78pub fn path_label(path: &syn::Path) -> String {
79 self::path_parts(path).map_or_else(|| "qualified path".to_owned(), |parts| parts.join("::"))
80}