swc_ecma_ast/
ident.rs

1use std::{
2    borrow::Cow,
3    fmt::Display,
4    ops::{Deref, DerefMut},
5};
6
7use phf::phf_set;
8use swc_atoms::{atom, Atom, UnsafeAtom};
9use swc_common::{
10    ast_node, util::take::Take, BytePos, EqIgnoreSpan, Mark, Span, Spanned, SyntaxContext, DUMMY_SP,
11};
12
13use crate::{typescript::TsTypeAnn, Expr};
14
15/// Identifier used as a pattern.
16#[derive(Clone, Debug, PartialEq, Eq, Hash, EqIgnoreSpan, Default)]
17#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
18#[cfg_attr(
19    any(feature = "rkyv-impl"),
20    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
21)]
22#[cfg_attr(
23    feature = "rkyv-impl",
24    rkyv(serialize_bounds(__S: rkyv::ser::Writer + rkyv::ser::Allocator,
25        __S::Error: rkyv::rancor::Source))
26)]
27#[cfg_attr(
28    feature = "rkyv-impl",
29    rkyv(deserialize_bounds(__D::Error: rkyv::rancor::Source))
30)]
31#[cfg_attr(
32    feature = "rkyv-impl",
33    rkyv(bytecheck(bounds(
34        __C: rkyv::validation::ArchiveContext,
35        __C::Error: rkyv::rancor::Source
36    )))
37)]
38#[cfg_attr(feature = "rkyv-impl", repr(C))]
39#[cfg_attr(feature = "serde-impl", derive(serde::Serialize, serde::Deserialize))]
40#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
41pub struct BindingIdent {
42    #[cfg_attr(feature = "serde-impl", serde(flatten))]
43    #[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))]
44    pub id: Ident,
45
46    #[cfg_attr(feature = "serde-impl", serde(default, rename = "typeAnnotation"))]
47    #[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))]
48    pub type_ann: Option<Box<TsTypeAnn>>,
49}
50
51impl Spanned for BindingIdent {
52    fn span(&self) -> Span {
53        match &self.type_ann {
54            Some(ann) => Span::new(self.id.span.lo(), ann.span().hi()),
55            None => self.id.span,
56        }
57    }
58}
59
60impl Deref for BindingIdent {
61    type Target = Ident;
62
63    fn deref(&self) -> &Self::Target {
64        &self.id
65    }
66}
67
68impl DerefMut for BindingIdent {
69    fn deref_mut(&mut self) -> &mut Self::Target {
70        &mut self.id
71    }
72}
73
74impl AsRef<str> for BindingIdent {
75    fn as_ref(&self) -> &str {
76        &self.sym
77    }
78}
79
80impl From<BindingIdent> for Box<Expr> {
81    fn from(bi: BindingIdent) -> Self {
82        Box::new(Expr::Ident(bi.into()))
83    }
84}
85impl From<&'_ BindingIdent> for Ident {
86    fn from(bi: &'_ BindingIdent) -> Self {
87        Ident {
88            span: bi.span,
89            ctxt: bi.ctxt,
90            sym: bi.sym.clone(),
91            optional: bi.optional,
92        }
93    }
94}
95
96impl BindingIdent {
97    /// See [`Ident::to_id`] for documentation.
98    pub fn to_id(&self) -> Id {
99        (self.sym.clone(), self.ctxt)
100    }
101}
102
103impl Take for BindingIdent {
104    fn dummy() -> Self {
105        Default::default()
106    }
107}
108
109impl From<Ident> for BindingIdent {
110    fn from(id: Ident) -> Self {
111        BindingIdent {
112            id,
113            ..Default::default()
114        }
115    }
116}
117
118bridge_from!(BindingIdent, Ident, Id);
119
120/// A complete identifier with span.
121///
122/// Identifier of swc consists of two parts. The first one is symbol, which is
123/// stored using an interned string, [Atom] . The second
124/// one is [SyntaxContext][swc_common::SyntaxContext], which can be
125/// used to distinguish identifier with same symbol.
126///
127/// Let me explain this with an example.
128///
129/// ```ts
130/// let a = 5
131/// {
132///     let a = 3;
133/// }
134/// ```
135/// In the code above, there are two variables with the symbol a.
136///
137///
138/// Other compilers typically uses type like `Scope`, and store them nested, but
139/// in rust, type like `Scope`  requires [Arc<Mutex<Scope>>] so swc uses
140/// different approach. Instead of passing scopes, swc annotates two variables
141/// with different tag, which is named
142/// [SyntaxContext]. The notation for the syntax
143/// context is #n where n is a number. e.g. `foo#1`
144///
145/// For the example above, after applying resolver pass, it becomes.
146///
147/// ```ts
148/// let a#1 = 5
149/// {
150///     let a#2 = 3;
151/// }
152/// ```
153///
154/// Thanks to the `tag` we attached, we can now distinguish them.
155///
156/// ([Atom], [SyntaxContext])
157///
158/// See [Id], which is a type alias for this.
159///
160/// This can be used to store all variables in a module to single hash map.
161///
162/// # Comparison
163///
164/// While comparing two identifiers, you can use `.to_id()`.
165///
166/// # HashMap
167///
168/// There's a type named [Id] which only contains minimal information to
169/// distinguish identifiers.
170#[ast_node("Identifier")]
171#[derive(Eq, Hash, Default)]
172#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
173pub struct Ident {
174    #[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))]
175    pub span: Span,
176
177    #[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))]
178    pub ctxt: SyntaxContext,
179
180    #[cfg_attr(feature = "serde-impl", serde(rename = "value"))]
181    pub sym: Atom,
182
183    /// TypeScript only. Used in case of an optional parameter.
184    #[cfg_attr(feature = "serde-impl", serde(default))]
185    pub optional: bool,
186}
187
188impl From<BindingIdent> for Ident {
189    fn from(bi: BindingIdent) -> Self {
190        bi.id
191    }
192}
193
194impl From<Atom> for Ident {
195    fn from(bi: Atom) -> Self {
196        Ident::new_no_ctxt(bi, DUMMY_SP)
197    }
198}
199bridge_from!(Ident, Atom, &'_ str);
200bridge_from!(Ident, Atom, Cow<'_, str>);
201bridge_from!(Ident, Atom, String);
202
203impl From<(Atom, Span)> for Ident {
204    fn from((sym, span): (Atom, Span)) -> Self {
205        Ident {
206            span,
207            sym,
208            ..Default::default()
209        }
210    }
211}
212
213impl EqIgnoreSpan for Ident {
214    fn eq_ignore_span(&self, other: &Self) -> bool {
215        if self.sym != other.sym {
216            return false;
217        }
218
219        self.ctxt.eq_ignore_span(&other.ctxt)
220    }
221}
222
223impl From<Id> for Ident {
224    fn from(id: Id) -> Self {
225        Ident::new(id.0, DUMMY_SP, id.1)
226    }
227}
228
229impl From<Ident> for Id {
230    fn from(i: Ident) -> Self {
231        (i.sym, i.ctxt)
232    }
233}
234
235#[repr(C, align(64))]
236struct Align64<T>(pub(crate) T);
237
238const T: bool = true;
239const F: bool = false;
240
241impl Ident {
242    /// In `op`, [EqIgnoreSpan] of [Ident] will ignore the syntax context.
243    pub fn within_ignored_ctxt<F, Ret>(op: F) -> Ret
244    where
245        F: FnOnce() -> Ret,
246    {
247        SyntaxContext::within_ignored_ctxt(op)
248    }
249
250    /// Preserve syntax context while drop `span.lo` and `span.hi`.
251    pub fn without_loc(mut self) -> Ident {
252        self.span.lo = BytePos::DUMMY;
253        self.span.hi = BytePos::DUMMY;
254        self
255    }
256
257    /// Creates `Id` using `Atom` and `SyntaxContext` of `self`.
258    pub fn to_id(&self) -> Id {
259        (self.sym.clone(), self.ctxt)
260    }
261
262    /// Returns true if `c` is a valid character for an identifier start.
263    #[inline]
264    pub fn is_valid_start(c: char) -> bool {
265        // This contains `$` (36) and `_` (95)
266        const ASCII_START: Align64<[bool; 128]> = Align64([
267            F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F,
268            F, F, F, F, F, F, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F,
269            F, F, F, F, F, F, F, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
270            T, T, T, T, F, F, F, F, T, F, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
271            T, T, T, T, T, T, T, F, F, F, F, F,
272        ]);
273
274        if c.is_ascii() {
275            return ASCII_START.0[c as usize];
276        }
277
278        unicode_id_start::is_id_start_unicode(c)
279    }
280
281    /// Returns true if `c` is a valid character for an identifier part after
282    /// start.
283    #[inline]
284    pub fn is_valid_continue(c: char) -> bool {
285        // This contains `$` (36)
286        const ASCII_CONTINUE: Align64<[bool; 128]> = Align64([
287            F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F,
288            F, F, F, F, F, F, F, T, F, F, F, F, F, F, F, F, F, F, F, T, T, T, T, T, T, T, T, T, T,
289            F, F, F, F, F, F, F, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
290            T, T, T, T, F, F, F, F, T, F, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
291            T, T, T, T, T, T, T, F, F, F, F, F,
292        ]);
293
294        if c.is_ascii() {
295            return ASCII_CONTINUE.0[c as usize];
296        }
297
298        unicode_id_start::is_id_continue_unicode(c)
299    }
300
301    /// Alternative for `toIdentifier` of babel.
302    ///
303    /// Returns [Ok] if it's a valid identifier and [Err] if it's not valid.
304    /// The returned [Err] contains the valid symbol.
305    pub fn verify_symbol(s: &str) -> Result<(), String> {
306        fn is_reserved_symbol(s: &str) -> bool {
307            s.is_reserved() || s.is_reserved_in_strict_mode(true) || s.is_reserved_in_strict_bind()
308        }
309
310        if is_reserved_symbol(s) {
311            let mut buf = String::with_capacity(s.len() + 1);
312            buf.push('_');
313            buf.push_str(s);
314            return Err(buf);
315        }
316
317        {
318            let mut chars = s.chars();
319
320            if let Some(first) = chars.next() {
321                if Self::is_valid_start(first) && chars.all(Self::is_valid_continue) {
322                    return Ok(());
323                }
324            }
325        }
326
327        let mut buf = String::with_capacity(s.len() + 2);
328        let mut has_start = false;
329
330        for c in s.chars() {
331            if !has_start && Self::is_valid_start(c) {
332                has_start = true;
333                buf.push(c);
334                continue;
335            }
336
337            if Self::is_valid_continue(c) {
338                buf.push(c);
339            }
340        }
341
342        if buf.is_empty() {
343            buf.push('_');
344        }
345
346        if is_reserved_symbol(&buf) {
347            let mut new_buf = String::with_capacity(buf.len() + 1);
348            new_buf.push('_');
349            new_buf.push_str(&buf);
350            buf = new_buf;
351        }
352
353        Err(buf)
354    }
355
356    /// Create a new identifier with the given prefix.
357    pub fn with_prefix(&self, prefix: &str) -> Ident {
358        Ident::new(
359            format!("{}{}", prefix, self.sym).into(),
360            self.span,
361            self.ctxt,
362        )
363    }
364
365    /// Create a private identifier that is unique in the file, but with the
366    /// same symbol.
367    pub fn into_private(self) -> Ident {
368        Self::new(
369            self.sym,
370            self.span,
371            SyntaxContext::empty().apply_mark(Mark::new()),
372        )
373    }
374
375    #[inline]
376    pub fn is_dummy(&self) -> bool {
377        self.sym == atom!("") && self.span.is_dummy()
378    }
379
380    /// Create a new identifier with the given position.
381    pub fn with_pos(mut self, lo: BytePos, hi: BytePos) -> Ident {
382        self.span = Span::new(lo, hi);
383        self
384    }
385}
386
387#[ast_node("Identifier")]
388#[derive(Eq, Hash, Default, EqIgnoreSpan)]
389#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
390#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
391pub struct IdentName {
392    #[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))]
393    pub span: Span,
394
395    #[cfg_attr(feature = "serde-impl", serde(rename = "value"))]
396    pub sym: Atom,
397}
398
399impl From<Atom> for IdentName {
400    fn from(sym: Atom) -> Self {
401        IdentName {
402            span: DUMMY_SP,
403            sym,
404        }
405    }
406}
407
408impl From<(Atom, Span)> for IdentName {
409    fn from((sym, span): (Atom, Span)) -> Self {
410        IdentName { span, sym }
411    }
412}
413
414bridge_from!(IdentName, Atom, &'_ str);
415bridge_from!(IdentName, Atom, Cow<'_, str>);
416bridge_from!(IdentName, Atom, String);
417bridge_from!(IdentName, Ident, &'_ BindingIdent);
418bridge_from!(IdentName, Ident, BindingIdent);
419
420impl AsRef<str> for IdentName {
421    fn as_ref(&self) -> &str {
422        &self.sym
423    }
424}
425
426impl IdentName {
427    pub const fn new(sym: Atom, span: Span) -> Self {
428        Self { span, sym }
429    }
430}
431
432impl Take for IdentName {
433    fn dummy() -> Self {
434        Default::default()
435    }
436}
437
438impl From<Ident> for IdentName {
439    fn from(i: Ident) -> Self {
440        IdentName {
441            span: i.span,
442            sym: i.sym,
443        }
444    }
445}
446
447impl From<IdentName> for Ident {
448    fn from(i: IdentName) -> Self {
449        Ident {
450            span: i.span,
451            sym: i.sym,
452            ..Default::default()
453        }
454    }
455}
456
457bridge_from!(BindingIdent, Ident, Atom);
458bridge_from!(BindingIdent, Atom, &'_ str);
459bridge_from!(BindingIdent, Atom, Cow<'_, str>);
460bridge_from!(BindingIdent, Atom, String);
461
462impl From<IdentName> for BindingIdent {
463    fn from(i: IdentName) -> Self {
464        BindingIdent {
465            id: i.into(),
466            ..Default::default()
467        }
468    }
469}
470
471/// UnsafeId is a wrapper around [Id] that does not allocate, but extremely
472/// unsafe.
473///
474/// Do not use this unless you know what you are doing.
475///
476/// **Currently, it's considered as a unstable API and may be changed in the
477/// future without a semver bump.**
478pub type UnsafeId = (UnsafeAtom, SyntaxContext);
479
480/// This is extremely unsafe so don't use it unless you know what you are doing.
481///
482/// # Safety
483///
484/// See [`UnsafeAtom::new`] for constraints.
485///
486/// **Currently, it's considered as a unstable API and may be changed in the
487/// future without a semver bump.**
488pub unsafe fn unsafe_id(id: &Id) -> UnsafeId {
489    (UnsafeAtom::new(&id.0), id.1)
490}
491
492/// This is extremely unsafe so don't use it unless you know what you are doing.
493///
494/// # Safety
495///
496/// See [`UnsafeAtom::new`] for constraints.
497///
498/// **Currently, it's considered as a unstable API and may be changed in the
499/// future without a semver bump.**
500pub unsafe fn unsafe_id_from_ident(id: &Ident) -> UnsafeId {
501    (UnsafeAtom::new(&id.sym), id.ctxt)
502}
503
504/// See [Ident] for documentation.
505pub type Id = (Atom, SyntaxContext);
506
507impl Take for Ident {
508    fn dummy() -> Self {
509        Ident::new_no_ctxt(atom!(""), DUMMY_SP)
510    }
511}
512
513impl Display for Ident {
514    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515        write!(f, "{}{:?}", self.sym, self.ctxt)
516    }
517}
518
519impl Display for IdentName {
520    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521        write!(f, "{}", self.sym)
522    }
523}
524
525impl Display for BindingIdent {
526    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
527        write!(f, "{}{:?}", self.sym, self.ctxt)
528    }
529}
530
531#[cfg(feature = "arbitrary")]
532#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
533impl<'a> arbitrary::Arbitrary<'a> for Ident {
534    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
535        let span = u.arbitrary()?;
536        let sym = u.arbitrary::<Atom>()?;
537
538        let optional = u.arbitrary()?;
539
540        Ok(Self {
541            span,
542            sym,
543            optional,
544            ctxt: Default::default(),
545        })
546    }
547}
548
549#[ast_node("PrivateName")]
550#[derive(Eq, Hash, EqIgnoreSpan, Default)]
551#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
552#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
553pub struct PrivateName {
554    pub span: Span,
555    #[cfg_attr(feature = "serde-impl", serde(rename = "value"))]
556    pub name: Atom,
557}
558
559impl AsRef<str> for Ident {
560    fn as_ref(&self) -> &str {
561        &self.sym
562    }
563}
564
565impl Ident {
566    pub const fn new(sym: Atom, span: Span, ctxt: SyntaxContext) -> Self {
567        Ident {
568            span,
569            ctxt,
570            sym,
571            optional: false,
572        }
573    }
574
575    /// Creates a new private identifier. A private identifier is an identifier
576    /// that is guaranteed to be unique.
577    ///
578    /// See https://swc.rs/docs/contributing/es-commons/variable-management for more details.
579    ///
580    /// Note: This method requires configuring
581    /// [GLOBALS](`swc_common::GLOBALS`) because this method use [`Mark::new`]
582    /// internally.
583    #[inline(never)]
584    pub fn new_private(sym: Atom, span: Span) -> Self {
585        Self::new(sym, span, SyntaxContext::empty().apply_mark(Mark::new()))
586    }
587
588    pub const fn new_no_ctxt(sym: Atom, span: Span) -> Self {
589        Self::new(sym, span, SyntaxContext::empty())
590    }
591}
592
593static RESERVED: phf::Set<&str> = phf_set!(
594    "break",
595    "case",
596    "catch",
597    "class",
598    "const",
599    "continue",
600    "debugger",
601    "default",
602    "delete",
603    "do",
604    "else",
605    "enum",
606    "export",
607    "extends",
608    "false",
609    "finally",
610    "for",
611    "function",
612    "if",
613    "import",
614    "in",
615    "instanceof",
616    "new",
617    "null",
618    "package",
619    "return",
620    "super",
621    "switch",
622    "this",
623    "throw",
624    "true",
625    "try",
626    "typeof",
627    "var",
628    "void",
629    "while",
630    "with",
631);
632
633static RESSERVED_IN_STRICT_MODE: phf::Set<&str> = phf_set!(
634    "implements",
635    "interface",
636    "let",
637    "package",
638    "private",
639    "protected",
640    "public",
641    "static",
642    "yield",
643);
644
645static RESSERVED_IN_STRICT_BIND: phf::Set<&str> = phf_set!("eval", "arguments",);
646
647static RESERVED_IN_ES3: phf::Set<&str> = phf_set!(
648    "abstract",
649    "boolean",
650    "byte",
651    "char",
652    "double",
653    "final",
654    "float",
655    "goto",
656    "int",
657    "long",
658    "native",
659    "short",
660    "synchronized",
661    "throws",
662    "transient",
663    "volatile",
664);
665
666pub trait EsReserved: AsRef<str> {
667    fn is_reserved(&self) -> bool {
668        RESERVED.contains(self.as_ref())
669    }
670
671    fn is_reserved_in_strict_mode(&self, is_module: bool) -> bool {
672        if is_module && self.as_ref() == "await" {
673            return true;
674        }
675        RESSERVED_IN_STRICT_MODE.contains(self.as_ref())
676    }
677
678    fn is_reserved_in_strict_bind(&self) -> bool {
679        RESSERVED_IN_STRICT_BIND.contains(self.as_ref())
680    }
681
682    fn is_reserved_in_es3(&self) -> bool {
683        RESERVED_IN_ES3.contains(self.as_ref())
684    }
685
686    fn is_reserved_in_any(&self) -> bool {
687        RESERVED.contains(self.as_ref())
688            || RESSERVED_IN_STRICT_MODE.contains(self.as_ref())
689            || RESSERVED_IN_STRICT_BIND.contains(self.as_ref())
690            || RESERVED_IN_ES3.contains(self.as_ref())
691    }
692}
693
694impl EsReserved for Atom {}
695impl EsReserved for IdentName {}
696impl EsReserved for Ident {}
697impl EsReserved for BindingIdent {}
698impl EsReserved for &'_ str {}
699impl EsReserved for String {}