Skip to main content

frs/cmds/rsl/rules/common/
path_resolution.rs

1use 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}