#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, str::FromStr};
use std::error::Error;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum JsKeyword {
Await,
Break,
Case,
Catch,
Class,
Const,
Continue,
Debugger,
Default,
Delete,
Do,
Else,
Export,
Extends,
Finally,
For,
Function,
If,
Import,
In,
Instanceof,
Let,
New,
Return,
Super,
Switch,
This,
Throw,
Try,
Typeof,
Var,
Void,
While,
With,
Yield,
}
impl JsKeyword {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Await => "await",
Self::Break => "break",
Self::Case => "case",
Self::Catch => "catch",
Self::Class => "class",
Self::Const => "const",
Self::Continue => "continue",
Self::Debugger => "debugger",
Self::Default => "default",
Self::Delete => "delete",
Self::Do => "do",
Self::Else => "else",
Self::Export => "export",
Self::Extends => "extends",
Self::Finally => "finally",
Self::For => "for",
Self::Function => "function",
Self::If => "if",
Self::Import => "import",
Self::In => "in",
Self::Instanceof => "instanceof",
Self::Let => "let",
Self::New => "new",
Self::Return => "return",
Self::Super => "super",
Self::Switch => "switch",
Self::This => "this",
Self::Throw => "throw",
Self::Try => "try",
Self::Typeof => "typeof",
Self::Var => "var",
Self::Void => "void",
Self::While => "while",
Self::With => "with",
Self::Yield => "yield",
}
}
}
impl fmt::Display for JsKeyword {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for JsKeyword {
type Err = JsKeywordParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match normalized_word(input)?.as_str() {
"await" => Ok(Self::Await),
"break" => Ok(Self::Break),
"case" => Ok(Self::Case),
"catch" => Ok(Self::Catch),
"class" => Ok(Self::Class),
"const" => Ok(Self::Const),
"continue" => Ok(Self::Continue),
"debugger" => Ok(Self::Debugger),
"default" => Ok(Self::Default),
"delete" => Ok(Self::Delete),
"do" => Ok(Self::Do),
"else" => Ok(Self::Else),
"export" => Ok(Self::Export),
"extends" => Ok(Self::Extends),
"finally" => Ok(Self::Finally),
"for" => Ok(Self::For),
"function" => Ok(Self::Function),
"if" => Ok(Self::If),
"import" => Ok(Self::Import),
"in" => Ok(Self::In),
"instanceof" => Ok(Self::Instanceof),
"let" => Ok(Self::Let),
"new" => Ok(Self::New),
"return" => Ok(Self::Return),
"super" => Ok(Self::Super),
"switch" => Ok(Self::Switch),
"this" => Ok(Self::This),
"throw" => Ok(Self::Throw),
"try" => Ok(Self::Try),
"typeof" => Ok(Self::Typeof),
"var" => Ok(Self::Var),
"void" => Ok(Self::Void),
"while" => Ok(Self::While),
"with" => Ok(Self::With),
"yield" => Ok(Self::Yield),
_ => Err(JsKeywordParseError::Unknown),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum JsReservedWord {
Keyword(JsKeyword),
Enum,
Implements,
Interface,
Package,
Private,
Protected,
Public,
Static,
}
impl JsReservedWord {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Keyword(keyword) => keyword.as_str(),
Self::Enum => "enum",
Self::Implements => "implements",
Self::Interface => "interface",
Self::Package => "package",
Self::Private => "private",
Self::Protected => "protected",
Self::Public => "public",
Self::Static => "static",
}
}
}
impl fmt::Display for JsReservedWord {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for JsReservedWord {
type Err = JsKeywordParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
if let Ok(keyword) = input.parse::<JsKeyword>() {
return Ok(Self::Keyword(keyword));
}
match normalized_word(input)?.as_str() {
"enum" => Ok(Self::Enum),
"implements" => Ok(Self::Implements),
"interface" => Ok(Self::Interface),
"package" => Ok(Self::Package),
"private" => Ok(Self::Private),
"protected" => Ok(Self::Protected),
"public" => Ok(Self::Public),
"static" => Ok(Self::Static),
_ => Err(JsKeywordParseError::Unknown),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum JsKeywordParseError {
Empty,
Unknown,
}
impl fmt::Display for JsKeywordParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("JavaScript word cannot be empty"),
Self::Unknown => formatter.write_str("unknown JavaScript keyword or reserved word"),
}
}
}
impl Error for JsKeywordParseError {}
#[must_use]
pub fn is_js_keyword(input: &str) -> bool {
input.parse::<JsKeyword>().is_ok()
}
#[must_use]
pub fn is_js_reserved_word(input: &str) -> bool {
input.parse::<JsReservedWord>().is_ok()
}
fn normalized_word(input: &str) -> Result<String, JsKeywordParseError> {
let trimmed = input.trim();
if trimmed.is_empty() {
Err(JsKeywordParseError::Empty)
} else {
Ok(trimmed.to_ascii_lowercase())
}
}
#[cfg(test)]
mod tests {
use super::{
JsKeyword, JsKeywordParseError, JsReservedWord, is_js_keyword, is_js_reserved_word,
};
#[test]
fn parses_keywords() -> Result<(), JsKeywordParseError> {
let keyword: JsKeyword = "Class".parse()?;
assert_eq!(keyword, JsKeyword::Class);
assert_eq!(keyword.as_str(), "class");
assert!(is_js_keyword("await"));
Ok(())
}
#[test]
fn checks_reserved_words() -> Result<(), JsKeywordParseError> {
let reserved: JsReservedWord = "interface".parse()?;
assert_eq!(reserved.as_str(), "interface");
assert!(is_js_reserved_word("private"));
assert!(!is_js_reserved_word("component"));
Ok(())
}
}