cargo_mobile2/config/app/
identifier.rs1use crate::util::list_display;
2use std::error::Error;
3use std::fmt;
4
5static RESERVED_PACKAGE_NAMES: [&str; 2] = ["kotlin", "java"];
6static 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}