1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum JsKeyword {
10 Await,
11 Break,
12 Case,
13 Catch,
14 Class,
15 Const,
16 Continue,
17 Debugger,
18 Default,
19 Delete,
20 Do,
21 Else,
22 Export,
23 Extends,
24 Finally,
25 For,
26 Function,
27 If,
28 Import,
29 In,
30 Instanceof,
31 Let,
32 New,
33 Return,
34 Super,
35 Switch,
36 This,
37 Throw,
38 Try,
39 Typeof,
40 Var,
41 Void,
42 While,
43 With,
44 Yield,
45}
46
47impl JsKeyword {
48 #[must_use]
50 pub const fn as_str(self) -> &'static str {
51 match self {
52 Self::Await => "await",
53 Self::Break => "break",
54 Self::Case => "case",
55 Self::Catch => "catch",
56 Self::Class => "class",
57 Self::Const => "const",
58 Self::Continue => "continue",
59 Self::Debugger => "debugger",
60 Self::Default => "default",
61 Self::Delete => "delete",
62 Self::Do => "do",
63 Self::Else => "else",
64 Self::Export => "export",
65 Self::Extends => "extends",
66 Self::Finally => "finally",
67 Self::For => "for",
68 Self::Function => "function",
69 Self::If => "if",
70 Self::Import => "import",
71 Self::In => "in",
72 Self::Instanceof => "instanceof",
73 Self::Let => "let",
74 Self::New => "new",
75 Self::Return => "return",
76 Self::Super => "super",
77 Self::Switch => "switch",
78 Self::This => "this",
79 Self::Throw => "throw",
80 Self::Try => "try",
81 Self::Typeof => "typeof",
82 Self::Var => "var",
83 Self::Void => "void",
84 Self::While => "while",
85 Self::With => "with",
86 Self::Yield => "yield",
87 }
88 }
89}
90
91impl fmt::Display for JsKeyword {
92 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
93 formatter.write_str(self.as_str())
94 }
95}
96
97impl FromStr for JsKeyword {
98 type Err = JsKeywordParseError;
99
100 fn from_str(input: &str) -> Result<Self, Self::Err> {
101 match normalized_word(input)?.as_str() {
102 "await" => Ok(Self::Await),
103 "break" => Ok(Self::Break),
104 "case" => Ok(Self::Case),
105 "catch" => Ok(Self::Catch),
106 "class" => Ok(Self::Class),
107 "const" => Ok(Self::Const),
108 "continue" => Ok(Self::Continue),
109 "debugger" => Ok(Self::Debugger),
110 "default" => Ok(Self::Default),
111 "delete" => Ok(Self::Delete),
112 "do" => Ok(Self::Do),
113 "else" => Ok(Self::Else),
114 "export" => Ok(Self::Export),
115 "extends" => Ok(Self::Extends),
116 "finally" => Ok(Self::Finally),
117 "for" => Ok(Self::For),
118 "function" => Ok(Self::Function),
119 "if" => Ok(Self::If),
120 "import" => Ok(Self::Import),
121 "in" => Ok(Self::In),
122 "instanceof" => Ok(Self::Instanceof),
123 "let" => Ok(Self::Let),
124 "new" => Ok(Self::New),
125 "return" => Ok(Self::Return),
126 "super" => Ok(Self::Super),
127 "switch" => Ok(Self::Switch),
128 "this" => Ok(Self::This),
129 "throw" => Ok(Self::Throw),
130 "try" => Ok(Self::Try),
131 "typeof" => Ok(Self::Typeof),
132 "var" => Ok(Self::Var),
133 "void" => Ok(Self::Void),
134 "while" => Ok(Self::While),
135 "with" => Ok(Self::With),
136 "yield" => Ok(Self::Yield),
137 _ => Err(JsKeywordParseError::Unknown),
138 }
139 }
140}
141
142#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
144pub enum JsReservedWord {
145 Keyword(JsKeyword),
146 Enum,
147 Implements,
148 Interface,
149 Package,
150 Private,
151 Protected,
152 Public,
153 Static,
154}
155
156impl JsReservedWord {
157 #[must_use]
159 pub const fn as_str(self) -> &'static str {
160 match self {
161 Self::Keyword(keyword) => keyword.as_str(),
162 Self::Enum => "enum",
163 Self::Implements => "implements",
164 Self::Interface => "interface",
165 Self::Package => "package",
166 Self::Private => "private",
167 Self::Protected => "protected",
168 Self::Public => "public",
169 Self::Static => "static",
170 }
171 }
172}
173
174impl fmt::Display for JsReservedWord {
175 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
176 formatter.write_str(self.as_str())
177 }
178}
179
180impl FromStr for JsReservedWord {
181 type Err = JsKeywordParseError;
182
183 fn from_str(input: &str) -> Result<Self, Self::Err> {
184 if let Ok(keyword) = input.parse::<JsKeyword>() {
185 return Ok(Self::Keyword(keyword));
186 }
187
188 match normalized_word(input)?.as_str() {
189 "enum" => Ok(Self::Enum),
190 "implements" => Ok(Self::Implements),
191 "interface" => Ok(Self::Interface),
192 "package" => Ok(Self::Package),
193 "private" => Ok(Self::Private),
194 "protected" => Ok(Self::Protected),
195 "public" => Ok(Self::Public),
196 "static" => Ok(Self::Static),
197 _ => Err(JsKeywordParseError::Unknown),
198 }
199 }
200}
201
202#[derive(Clone, Copy, Debug, Eq, PartialEq)]
204pub enum JsKeywordParseError {
205 Empty,
206 Unknown,
207}
208
209impl fmt::Display for JsKeywordParseError {
210 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
211 match self {
212 Self::Empty => formatter.write_str("JavaScript word cannot be empty"),
213 Self::Unknown => formatter.write_str("unknown JavaScript keyword or reserved word"),
214 }
215 }
216}
217
218impl Error for JsKeywordParseError {}
219
220#[must_use]
222pub fn is_js_keyword(input: &str) -> bool {
223 input.parse::<JsKeyword>().is_ok()
224}
225
226#[must_use]
228pub fn is_js_reserved_word(input: &str) -> bool {
229 input.parse::<JsReservedWord>().is_ok()
230}
231
232fn normalized_word(input: &str) -> Result<String, JsKeywordParseError> {
233 let trimmed = input.trim();
234 if trimmed.is_empty() {
235 Err(JsKeywordParseError::Empty)
236 } else {
237 Ok(trimmed.to_ascii_lowercase())
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::{
244 JsKeyword, JsKeywordParseError, JsReservedWord, is_js_keyword, is_js_reserved_word,
245 };
246
247 #[test]
248 fn parses_keywords() -> Result<(), JsKeywordParseError> {
249 let keyword: JsKeyword = "Class".parse()?;
250 assert_eq!(keyword, JsKeyword::Class);
251 assert_eq!(keyword.as_str(), "class");
252 assert!(is_js_keyword("await"));
253 Ok(())
254 }
255
256 #[test]
257 fn checks_reserved_words() -> Result<(), JsKeywordParseError> {
258 let reserved: JsReservedWord = "interface".parse()?;
259 assert_eq!(reserved.as_str(), "interface");
260 assert!(is_js_reserved_word("private"));
261 assert!(!is_js_reserved_word("component"));
262 Ok(())
263 }
264}