Skip to main content

use_js_keyword/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// Common JavaScript keywords.
8#[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    /// Returns the lowercase keyword label.
49    #[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/// JavaScript reserved words, including keywords and common future reserved words.
143#[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    /// Returns the lowercase reserved-word label.
158    #[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/// Error returned when parsing JavaScript vocabulary fails.
203#[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/// Returns whether `input` is one of the common JavaScript keywords.
221#[must_use]
222pub fn is_js_keyword(input: &str) -> bool {
223    input.parse::<JsKeyword>().is_ok()
224}
225
226/// Returns whether `input` is a keyword or common reserved word.
227#[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}