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>, } 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 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>] 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 for Ident { fn from(id: Id) -> Self { Ident::new(id.0, DUMMY_SP.with_ctxt(id.1)) } } impl From 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(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 { let span = u.arbitrary()?; let sym = u.arbitrary::()?; 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 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 { 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 {}