cargo_mobile2/config/app/
identifier.rs

1use crate::util::list_display;
2use std::error::Error;
3use std::fmt;
4
5static RESERVED_PACKAGE_NAMES: [&str; 2] = ["kotlin", "java"];
6// https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html
7static RESERVED_JAVA_KEYWORDS: [&str; 53] = [
8    "abstract",
9    "assert",
10    "boolean",
11    "break",
12    "byte",
13    "case",
14    "catch",
15    "char",
16    "class",
17    "const",
18    "continue",
19    "default",
20    "do",
21    "double",
22    "else",
23    "enum",
24    "extends",
25    "false",
26    "final",
27    "finally",
28    "float",
29    "for",
30    "goto",
31    "if",
32    "implements",
33    "import",
34    "instanceof",
35    "int",
36    "interface",
37    "long",
38    "native",
39    "new",
40    "null",
41    "package",
42    "private",
43    "protected",
44    "public",
45    "return",
46    "short",
47    "static",
48    "strictfp",
49    "super",
50    "switch",
51    "synchronized",
52    "this",
53    "throw",
54    "throws",
55    "transient",
56    "true",
57    "try",
58    "void",
59    "volatile",
60    "while",
61];
62
63#[derive(Debug)]
64pub enum IdentifierError {
65    Empty,
66    NotAsciiAlphanumeric { bad_chars: Vec<char> },
67    StartsWithDigit { label: String },
68    ReservedPackageName { package_name: String },
69    ReservedKeyword { keyword: String },
70    StartsOrEndsWithADot,
71    EmptyLabel,
72}
73
74impl Error for IdentifierError {}
75
76impl fmt::Display for IdentifierError {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        match self {
79            Self::Empty => write!(f, "Identifier can't be empty."),
80            Self::NotAsciiAlphanumeric { bad_chars } => write!(
81                f,
82                "{} characters were used in identifier, but only ASCII letters and numbers are allowed.",
83                list_display(
84                    &bad_chars
85                        .iter()
86                        .map(|c| format!("'{}'", c))
87                        .collect::<Vec<_>>()
88                ),
89            ),
90            Self::ReservedPackageName { package_name } => write!(
91                f,
92                "\"{}\" is a reserved package name in this project and can't be used as a top-level identifier.",
93                package_name
94            ),
95            Self::ReservedKeyword { keyword } => write!(
96                f,
97                "\"{}\" is a reserved keyword in java/kotlin and can't be used. For more info, please visit https://kotlinlang.org/docs/reference/keyword-reference.html and https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html",
98                keyword
99            ),
100            Self::StartsWithDigit { label } => write!(
101                f,
102                "\"{}\" label starts with a digit, which is not allowed in java/kotlin packages.",
103                label
104            ),
105            Self::StartsOrEndsWithADot => write!(f, "Identifier can't start or end with a dot."),
106            Self::EmptyLabel => write!(f, "Labels can't be empty."),
107        }
108    }
109}
110
111pub fn check_identifier_syntax(identifier_name: &str) -> Result<(), IdentifierError> {
112    if identifier_name.is_empty() {
113        return Err(IdentifierError::Empty);
114    }
115    if identifier_name.starts_with('.') || identifier_name.ends_with('.') {
116        return Err(IdentifierError::StartsOrEndsWithADot);
117    }
118    let labels = identifier_name.split('.');
119    for label in labels {
120        if label.is_empty() {
121            return Err(IdentifierError::EmptyLabel);
122        }
123        if RESERVED_JAVA_KEYWORDS.contains(&label) {
124            return Err(IdentifierError::ReservedKeyword {
125                keyword: label.to_owned(),
126            });
127        }
128        if label.chars().next().unwrap().is_ascii_digit() {
129            return Err(IdentifierError::StartsWithDigit {
130                label: label.to_owned(),
131            });
132        }
133        let mut bad_chars = Vec::new();
134        for c in label.chars() {
135            if !(c.is_ascii_alphanumeric() || c == '_' || c == '-' || bad_chars.contains(&c)) {
136                bad_chars.push(c);
137            }
138        }
139        if !bad_chars.is_empty() {
140            return Err(IdentifierError::NotAsciiAlphanumeric { bad_chars });
141        }
142    }
143    for pkg_name in RESERVED_PACKAGE_NAMES.iter() {
144        if identifier_name.ends_with(pkg_name) {
145            return Err(IdentifierError::ReservedPackageName {
146                package_name: pkg_name.to_string(),
147            });
148        }
149    }
150    Ok(())
151}
152
153#[cfg(test)]
154mod test {
155    use super::*;
156    use rstest::rstest;
157
158    #[rstest(
159        input,
160        case("com.example"),
161        case("t2900.e1.s709.t1000"),
162        case("kotlin.com"),
163        case("java.test"),
164        case("synchronized2.com"),
165        case("com.tauri-apps.dev"),
166        case("com-tauri.apps_demo.core")
167    )]
168    fn test_check_identifier_syntax_correct(input: &str) {
169        check_identifier_syntax(input).unwrap();
170    }
171
172    #[rstest(input, error,
173        case("ラスト.テスト", IdentifierError::NotAsciiAlphanumeric { bad_chars: vec!['ラ', 'ス', 'ト'] }),
174        case("test.digits.87", IdentifierError::StartsWithDigit { label: String::from("87") }),
175        case("", IdentifierError::Empty {}),
176        case(".bad.dot.syntax", IdentifierError::StartsOrEndsWithADot {}),
177        case("com.kotlin", IdentifierError::ReservedPackageName { package_name: String::from("kotlin") }),
178        case("some.identifier.catch.com", IdentifierError::ReservedKeyword { keyword: String::from("catch") }),
179        case("com..empty.label", IdentifierError::EmptyLabel)
180    )]
181    fn test_check_identifier_syntax_error(input: &str, error: IdentifierError) {
182        assert_eq!(
183            check_identifier_syntax(input).unwrap_err().to_string(),
184            error.to_string()
185        )
186    }
187}