1use std::{
5    fmt::{self, Display},
6    str::FromStr,
7};
8
9use crate::{error::ParseError, STD_CRATES};
10
11pub struct SimplePath(String, usize);
20
21impl SimplePath {
22    #[allow(clippy::missing_const_for_fn)]
24    #[must_use]
25    pub fn into_inner(self) -> String {
26        self.0
27    }
28
29    #[must_use]
33    pub fn crate_name(&self) -> &str {
34        &self.0[..self.1]
35    }
36
37    #[must_use]
39    pub fn is_std(&self) -> bool {
40        STD_CRATES.contains(&self.crate_name())
41    }
42
43    pub(crate) fn is_crate_only(&self) -> bool {
45        self.0.len() == self.1
46    }
47}
48
49impl FromStr for SimplePath {
50    type Err = ParseError;
51
52    fn from_str(s: &str) -> Result<Self, Self::Err> {
53        if s.is_empty() {
54            return Err(Self::Err::TooShort);
55        }
56
57        if !s.split("::").all(is_identifier) {
58            return Err(Self::Err::InvalidIdentifier);
59        }
60
61        let index = s.find("::").unwrap_or(s.len());
62
63        Ok(Self(s.to_owned(), index))
64    }
65}
66
67impl AsRef<str> for SimplePath {
68    fn as_ref(&self) -> &str {
69        &self.0
70    }
71}
72
73impl Display for SimplePath {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        self.0.fmt(f)
76    }
77}
78
79fn is_identifier_or_keyword(value: &str) -> bool {
97    fn variant_one(first_char: char, value: &str) -> bool {
98        unicode_ident::is_xid_start(first_char)
99            && value.chars().skip(1).all(unicode_ident::is_xid_continue)
100    }
101
102    fn variant_two(first_char: char, value: &str) -> bool {
103        first_char == '_'
104            && value.chars().skip(1).count() > 0
105            && value.chars().skip(1).all(unicode_ident::is_xid_continue)
106    }
107
108    let first_char = match value.chars().next() {
109        Some(ch) => ch,
110        None => return false,
111    };
112
113    variant_one(first_char, value) || variant_two(first_char, value)
114}
115
116fn is_raw_identifier(value: &str) -> bool {
124    const KEYWORDS: &[&str] = &["crate", "self", "super", "Self"];
125
126    value
127        .strip_prefix("r#")
128        .map(|value| is_identifier_or_keyword(value) && !KEYWORDS.contains(&value))
129        .unwrap_or_default()
130}
131
132fn is_non_keyword_identifier(value: &str) -> bool {
142    const STRICT_KEYWORDS: &[&str] = &[
143        "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
144        "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
145        "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe",
146        "use", "where", "while", "async", "await", "dyn",
147    ];
148    const RESERVED_KEYWORDS: &[&str] = &[
149        "abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof",
150        "unsized", "virtual", "yield",
151    ];
152
153    is_identifier_or_keyword(value)
154        && !STRICT_KEYWORDS.contains(&value)
155        && !RESERVED_KEYWORDS.contains(&value)
156}
157
158fn is_identifier(value: &str) -> bool {
170    is_non_keyword_identifier(value) || is_raw_identifier(value)
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn parse_valid() {
179        let inputs = &["anyhow", "anyhow::Result", "special::__", "__", "r#unsafe"];
180
181        for input in inputs {
182            assert!(input.parse::<SimplePath>().is_ok());
183        }
184    }
185
186    #[test]
187    fn parse_invalid() {
188        let inputs = &["", "a::::b", "::", "_", "unsafe", "Self", "r#Self"];
189
190        for input in inputs {
191            assert!(input.parse::<SimplePath>().is_err());
192        }
193    }
194}