use std::fmt::Display;
use scoped_tls::scoped_thread_local;
use swc_atoms::{js_word, Atom};
use swc_common::{
ast_node, util::take::Take, BytePos, EqIgnoreSpan, Span, Spanned, SyntaxContext, DUMMY_SP,
};
use unicode_id::UnicodeID;
use crate::typescript::TsTypeAnn;
#[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 {
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);
#[ast_node("Identifier")]
#[derive(Eq, Hash)]
pub struct Ident {
pub span: Span,
#[cfg_attr(feature = "serde-impl", serde(rename = "value"))]
pub sym: Atom,
#[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 {
pub fn within_ignored_ctxt<F, Ret>(op: F) -> Ret
where
F: FnOnce() -> Ret,
{
EQ_IGNORE_SPAN_IGNORE_CTXT.set(&(), op)
}
pub fn without_loc(mut self) -> Ident {
self.span.lo = BytePos::DUMMY;
self.span.hi = BytePos::DUMMY;
self
}
pub fn to_id(&self) -> Id {
(self.sym.clone(), self.span.ctxt)
}
#[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)
}
}
}
#[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)
}
}
}
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()
}
}
pub type Id = (Atom, 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: Atom, 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 Atom {}
impl IdentExt for Ident {}
impl IdentExt for &'_ str {}
impl IdentExt for String {}