oden/third-party/vendor/swc_ecma_ast/src/ident.rs
2024-03-08 11:03:01 -08:00

413 lines
10 KiB
Rust

use std::fmt::Display;
use scoped_tls::scoped_thread_local;
use swc_atoms::{js_word, JsWord};
use swc_common::{
ast_node, util::take::Take, BytePos, EqIgnoreSpan, Span, Spanned, SyntaxContext, DUMMY_SP,
};
use unicode_id::UnicodeID;
use crate::typescript::TsTypeAnn;
/// Identifier used as a pattern.
#[derive(Spanned, Clone, Debug, PartialEq, Eq, Hash, EqIgnoreSpan)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(
any(feature = "rkyv-impl"),
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[cfg_attr(
any(feature = "rkyv-impl"),
archive(bound(
serialize = "__S: rkyv::ser::Serializer + rkyv::ser::ScratchSpace + \
rkyv::ser::SharedSerializeRegistry",
deserialize = "__D: rkyv::de::SharedDeserializeRegistry"
))
)]
#[cfg_attr(feature = "rkyv-impl", archive(check_bytes))]
#[cfg_attr(feature = "rkyv-impl", archive_attr(repr(C)))]
#[cfg_attr(feature = "serde-impl", derive(serde::Serialize, serde::Deserialize))]
pub struct BindingIdent {
#[span]
#[cfg_attr(feature = "serde-impl", serde(flatten))]
#[cfg_attr(feature = "__rkyv", omit_bounds)]
pub id: Ident,
#[cfg_attr(feature = "serde-impl", serde(default, rename = "typeAnnotation"))]
#[cfg_attr(feature = "__rkyv", omit_bounds)]
pub type_ann: Option<Box<TsTypeAnn>>,
}
impl std::ops::Deref for BindingIdent {
type Target = Ident;
fn deref(&self) -> &Self::Target {
&self.id
}
}
impl BindingIdent {
/// See [`Ident::to_id`] for documentation.
pub fn to_id(&self) -> Id {
self.id.to_id()
}
}
impl From<Ident> for BindingIdent {
fn from(id: Ident) -> Self {
Self { id, type_ann: None }
}
}
bridge_from!(BindingIdent, Ident, Id);
/// A complete identifier with span.
///
/// Identifier of swc consists of two parts. The first one is symbol, which is
/// stored using an interned string, [JsWord] . The second
/// one is [SyntaxContext][swc_common::SyntaxContext], which can be
/// used to distinguish identifier with same symbol.
///
/// Let me explain this with an example.
///
/// ```ts
/// let a = 5
/// {
/// let a = 3;
/// }
/// ```
/// In the code above, there are two variables with the symbol a.
///
///
/// Other compilers typically uses type like `Scope`, and store them nested, but
/// in rust, type like `Scope` requires [Arc<Mutex<Scope>>] so swc uses
/// different approach. Instead of passing scopes, swc annotates two variables
/// with different tag, which is named
/// [SyntaxContext]. The notation for the syntax
/// context is #n where n is a number. e.g. `foo#1`
///
/// For the example above, after applying resolver pass, it becomes.
///
/// ```ts
/// let a#1 = 5
/// {
/// let a#2 = 3;
/// }
/// ```
///
/// Thanks to the `tag` we attached, we can now distinguish them.
///
/// ([JsWord], [SyntaxContext])
///
/// See [Id], which is a type alias for this.
///
/// This can be used to store all variables in a module to single hash map.
///
/// # Comparison
///
/// While comparing two identifiers, you can use `.to_id()`.
///
/// # HashMap
///
/// There's a type named [Id] which only contains minimal information to
/// distinguish identifiers.
#[ast_node("Identifier")]
#[derive(Eq, Hash)]
pub struct Ident {
pub span: Span,
#[cfg_attr(feature = "serde-impl", serde(rename = "value"))]
#[cfg_attr(any(feature = "rkyv-impl"), with(swc_atoms::EncodeJsWord))]
pub sym: JsWord,
/// TypeScript only. Used in case of an optional parameter.
#[cfg_attr(feature = "serde-impl", serde(default))]
pub optional: bool,
}
scoped_thread_local!(static EQ_IGNORE_SPAN_IGNORE_CTXT: ());
impl EqIgnoreSpan for Ident {
fn eq_ignore_span(&self, other: &Self) -> bool {
if self.sym != other.sym {
return false;
}
if self.span.ctxt == other.span.ctxt {
return true;
}
EQ_IGNORE_SPAN_IGNORE_CTXT.is_set()
}
}
impl From<Id> for Ident {
fn from(id: Id) -> Self {
Ident::new(id.0, DUMMY_SP.with_ctxt(id.1))
}
}
impl From<Ident> for Id {
fn from(i: Ident) -> Self {
(i.sym, i.span.ctxt)
}
}
impl Ident {
/// In `op`, [EqIgnoreSpan] of [Ident] will ignore the syntax context.
pub fn within_ignored_ctxt<F, Ret>(op: F) -> Ret
where
F: FnOnce() -> Ret,
{
EQ_IGNORE_SPAN_IGNORE_CTXT.set(&(), op)
}
/// Preserve syntax context while drop `span.lo` and `span.hi`.
pub fn without_loc(mut self) -> Ident {
self.span.lo = BytePos::DUMMY;
self.span.hi = BytePos::DUMMY;
self
}
/// Creates `Id` using `JsWord` and `SyntaxContext` of `self`.
pub fn to_id(&self) -> Id {
(self.sym.clone(), self.span.ctxt)
}
/// Returns true if `c` is a valid character for an identifier start.
#[inline]
pub fn is_valid_start(c: char) -> bool {
c == '$' || c == '_' || c.is_ascii_alphabetic() || {
if c.is_ascii() {
false
} else {
UnicodeID::is_id_start(c)
}
}
}
/// Returns true if `c` is a valid character for an identifier part after
/// start.
#[inline]
pub fn is_valid_continue(c: char) -> bool {
c == '$' || c == '_' || c == '\u{200c}' || c == '\u{200d}' || c.is_ascii_alphanumeric() || {
if c.is_ascii() {
false
} else {
UnicodeID::is_id_continue(c)
}
}
}
/// Alternative for `toIdentifier` of babel.
///
/// Returns [Ok] if it's a valid identifier and [Err] if it's not valid.
/// The returned [Err] contains the valid symbol.
pub fn verify_symbol(s: &str) -> Result<(), String> {
fn is_reserved_symbol(s: &str) -> bool {
s.is_reserved() || s.is_reserved_in_strict_mode(true) || s.is_reserved_in_strict_bind()
}
if is_reserved_symbol(s) {
let mut buf = String::with_capacity(s.len() + 1);
buf.push('_');
buf.push_str(s);
return Err(buf);
}
{
let mut chars = s.chars();
if let Some(first) = chars.next() {
if Self::is_valid_start(first) && chars.all(Self::is_valid_continue) {
return Ok(());
}
}
}
let mut buf = String::with_capacity(s.len() + 2);
let mut has_start = false;
for c in s.chars() {
if !has_start && Self::is_valid_start(c) {
has_start = true;
buf.push(c);
continue;
}
if Self::is_valid_continue(c) {
buf.push(c);
}
}
if buf.is_empty() {
buf.push('_');
}
if is_reserved_symbol(&buf) {
let mut new_buf = String::with_capacity(buf.len() + 1);
new_buf.push('_');
new_buf.push_str(&buf);
buf = new_buf;
}
Err(buf)
}
#[inline]
pub fn is_dummy(&self) -> bool {
self.sym == js_word!("") && self.span.is_dummy()
}
}
/// See [Ident] for documentation.
pub type Id = (JsWord, SyntaxContext);
impl Take for Ident {
fn dummy() -> Self {
Ident::new(js_word!(""), DUMMY_SP)
}
}
impl Display for Ident {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{:?}", self.sym, self.span.ctxt)
}
}
#[cfg(feature = "arbitrary")]
#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
impl<'a> arbitrary::Arbitrary<'a> for Ident {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let span = u.arbitrary()?;
let sym = u.arbitrary::<String>()?;
if sym.is_empty() {
return Err(arbitrary::Error::NotEnoughData);
}
let sym = sym.into();
let optional = u.arbitrary()?;
Ok(Self {
span,
sym,
optional,
})
}
}
#[ast_node("PrivateName")]
#[derive(Eq, Hash, EqIgnoreSpan)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct PrivateName {
pub span: Span,
pub id: Ident,
}
impl AsRef<str> for Ident {
fn as_ref(&self) -> &str {
&self.sym
}
}
impl Ident {
pub const fn new(sym: JsWord, span: Span) -> Self {
Ident {
span,
sym,
optional: false,
}
}
}
pub trait IdentExt: AsRef<str> {
fn is_reserved(&self) -> bool {
[
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"enum",
"export",
"extends",
"false",
"finally",
"for",
"function",
"if",
"import",
"in",
"instanceof",
"new",
"null",
"package",
"return",
"super",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"var",
"void",
"while",
"with",
]
.contains(&self.as_ref())
}
fn is_reserved_in_strict_mode(&self, is_module: bool) -> bool {
if is_module && self.as_ref() == "await" {
return true;
}
[
"implements",
"interface",
"let",
"package",
"private",
"protected",
"public",
"static",
"yield",
]
.contains(&self.as_ref())
}
fn is_reserved_in_strict_bind(&self) -> bool {
["eval", "arguments"].contains(&self.as_ref())
}
fn is_reserved_in_es3(&self) -> bool {
[
"abstract",
"boolean",
"byte",
"char",
"double",
"final",
"float",
"goto",
"int",
"long",
"native",
"short",
"synchronized",
"throws",
"transient",
"volatile",
]
.contains(&self.as_ref())
}
}
impl IdentExt for JsWord {}
impl IdentExt for Ident {}
impl IdentExt for &'_ str {}
impl IdentExt for String {}