Files
cli/vendor/wasm-bindgen-shared/src/identifier.rs

152 lines
4.7 KiB
Rust
Raw Normal View History

use alloc::string::String;
/// Returns whether a character is a valid JS identifier start character.
///
/// This is only ever-so-slightly different from `XID_Start` in a few edge
/// cases, so we handle those edge cases manually and delegate everything else
/// to `unicode-ident`.
fn is_id_start(c: char) -> bool {
match c {
'\u{037A}' | '\u{0E33}' | '\u{0EB3}' | '\u{309B}' | '\u{309C}' | '\u{FC5E}'
| '\u{FC5F}' | '\u{FC60}' | '\u{FC61}' | '\u{FC62}' | '\u{FC63}' | '\u{FDFA}'
| '\u{FDFB}' | '\u{FE70}' | '\u{FE72}' | '\u{FE74}' | '\u{FE76}' | '\u{FE78}'
| '\u{FE7A}' | '\u{FE7C}' | '\u{FE7E}' | '\u{FF9E}' | '\u{FF9F}' => true,
'$' | '_' => true,
_ => unicode_ident::is_xid_start(c),
}
}
/// Returns whether a character is a valid JS identifier continue character.
///
/// This is only ever-so-slightly different from `XID_Continue` in a few edge
/// cases, so we handle those edge cases manually and delegate everything else
/// to `unicode-ident`.
fn is_id_continue(c: char) -> bool {
match c {
'\u{037A}' | '\u{309B}' | '\u{309C}' | '\u{FC5E}' | '\u{FC5F}' | '\u{FC60}'
| '\u{FC61}' | '\u{FC62}' | '\u{FC63}' | '\u{FDFA}' | '\u{FDFB}' | '\u{FE70}'
| '\u{FE72}' | '\u{FE74}' | '\u{FE76}' | '\u{FE78}' | '\u{FE7A}' | '\u{FE7C}'
| '\u{FE7E}' => true,
'$' | '\u{200C}' | '\u{200D}' => true,
_ => unicode_ident::is_xid_continue(c),
}
}
fn maybe_valid_chars(name: &str) -> impl Iterator<Item = Option<char>> + '_ {
let mut chars = name.chars();
// Always emit at least one `None` item - that way `is_valid_ident` can fail without
// a separate check for empty strings, and `to_valid_ident` will always produce at least
// one underscore.
core::iter::once(chars.next().filter(|&c| is_id_start(c))).chain(chars.map(|c| {
if is_id_continue(c) {
Some(c)
} else {
None
}
}))
}
/// Javascript keywords.
///
/// Note that some of these keywords are only reserved in strict mode. Since we
/// generate strict mode JS code, we treat all of these as reserved.
///
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words
const JS_KEYWORDS: [&str; 47] = [
"arguments",
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"enum",
"eval",
"export",
"extends",
"false",
"finally",
"for",
"function",
"if",
"implements",
"import",
"in",
"instanceof",
"interface",
"let",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"static",
"super",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"var",
"void",
"while",
"with",
"yield",
];
/// Javascript keywords that behave like values in that they can be called like
/// functions or have properties accessed on them.
///
/// Naturally, this list is a subset of `JS_KEYWORDS`.
const VALUE_LIKE_JS_KEYWORDS: [&str; 7] = [
"eval", // eval is a function-like keyword, so e.g. `eval(...)` is valid
"false", // false resolves to a boolean value, so e.g. `false.toString()` is valid
"import", // import.meta and import()
"new", // new.target
"super", // super can be used for a function call (`super(...)`) or property lookup (`super.prop`)
"this", // this obviously can be used as a value
"true", // true resolves to a boolean value, so e.g. `false.toString()` is valid
];
/// Returns whether the given string is a JS keyword.
pub fn is_js_keyword(keyword: &str) -> bool {
JS_KEYWORDS.contains(&keyword)
}
/// Returns whether the given string is a JS keyword that does NOT behave like
/// a value.
///
/// Value-like keywords can be called like functions or have properties
/// accessed, which makes it possible to use them in imports. In general,
/// imports should use this function to check for reserved keywords.
pub fn is_non_value_js_keyword(keyword: &str) -> bool {
JS_KEYWORDS.contains(&keyword) && !VALUE_LIKE_JS_KEYWORDS.contains(&keyword)
}
/// Returns whether a string is a valid JavaScript identifier.
/// Defined at https://tc39.es/ecma262/#prod-IdentifierName.
pub fn is_valid_ident(name: &str) -> bool {
maybe_valid_chars(name).all(|opt| opt.is_some())
}
/// Converts a string to a valid JavaScript identifier by replacing invalid
/// characters with underscores.
pub fn to_valid_ident(name: &str) -> String {
let result: String = maybe_valid_chars(name)
.map(|opt| opt.unwrap_or('_'))
.collect();
if is_js_keyword(&result) || is_non_value_js_keyword(&result) {
alloc::format!("_{result}")
} else {
result
}
}