Skip to main content

swc_ecma_ast/
lit.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Display, Formatter},
4    hash::{Hash, Hasher},
5};
6
7use is_macro::Is;
8use num_bigint::BigInt as BigIntValue;
9use swc_atoms::{
10    wtf8::{CodePoint, Wtf8Buf},
11    Atom, Wtf8Atom,
12};
13use swc_common::{ast_node, errors::HANDLER, util::take::Take, EqIgnoreSpan, Span, DUMMY_SP};
14
15use crate::{jsx::JSXText, TplElement};
16
17#[ast_node]
18#[derive(Eq, Hash, EqIgnoreSpan, Is)]
19#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
20#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
21pub enum Lit {
22    #[tag("StringLiteral")]
23    Str(Str),
24
25    #[tag("BooleanLiteral")]
26    Bool(Bool),
27
28    #[tag("NullLiteral")]
29    Null(Null),
30
31    #[tag("NumericLiteral")]
32    Num(Number),
33
34    #[tag("BigIntLiteral")]
35    BigInt(BigInt),
36
37    #[tag("RegExpLiteral")]
38    Regex(Regex),
39
40    #[tag("JSXText")]
41    JSXText(JSXText),
42}
43
44macro_rules! bridge_lit_from {
45    ($bridge: ty, $src:ty) => {
46        bridge_expr_from!(crate::Lit, $src);
47        bridge_from!(Lit, $bridge, $src);
48    };
49}
50
51bridge_expr_from!(Lit, Str);
52bridge_expr_from!(Lit, Bool);
53bridge_expr_from!(Lit, Number);
54bridge_expr_from!(Lit, BigInt);
55bridge_expr_from!(Lit, Regex);
56bridge_expr_from!(Lit, Null);
57bridge_expr_from!(Lit, JSXText);
58
59bridge_lit_from!(Str, &'_ str);
60bridge_lit_from!(Str, Atom);
61bridge_lit_from!(Str, Wtf8Atom);
62bridge_lit_from!(Str, Cow<'_, str>);
63bridge_lit_from!(Str, String);
64bridge_lit_from!(Bool, bool);
65bridge_lit_from!(Number, f64);
66bridge_lit_from!(Number, usize);
67bridge_lit_from!(BigInt, BigIntValue);
68
69impl Lit {
70    pub fn set_span(&mut self, span: Span) {
71        match self {
72            Lit::Str(s) => s.span = span,
73            Lit::Bool(b) => b.span = span,
74            Lit::Null(n) => n.span = span,
75            Lit::Num(n) => n.span = span,
76            Lit::BigInt(n) => n.span = span,
77            Lit::Regex(n) => n.span = span,
78            Lit::JSXText(n) => n.span = span,
79            #[cfg(all(swc_ast_unknown, feature = "encoding-impl"))]
80            _ => swc_common::unknown!(),
81        }
82    }
83}
84
85#[ast_node("BigIntLiteral")]
86#[derive(Eq, Hash)]
87pub struct BigInt {
88    pub span: Span,
89    #[cfg_attr(feature = "encoding-impl", encoding(with = "EncodeBigInt2"))]
90    pub value: Box<BigIntValue>,
91
92    /// Use `None` value only for transformations to avoid recalculate
93    /// characters in big integer
94    #[cfg_attr(
95        feature = "encoding-impl",
96        encoding(with = "cbor4ii::core::types::Maybe")
97    )]
98    pub raw: Option<Atom>,
99}
100
101#[cfg(feature = "shrink-to-fit")]
102impl shrink_to_fit::ShrinkToFit for BigInt {
103    #[inline(always)]
104    fn shrink_to_fit(&mut self) {}
105}
106
107impl EqIgnoreSpan for BigInt {
108    fn eq_ignore_span(&self, other: &Self) -> bool {
109        self.value == other.value
110    }
111}
112
113#[cfg(feature = "encoding-impl")]
114struct EncodeBigInt2<T>(T);
115
116#[cfg(feature = "encoding-impl")]
117impl cbor4ii::core::enc::Encode for EncodeBigInt2<&'_ Box<BigIntValue>> {
118    #[inline]
119    fn encode<W: cbor4ii::core::enc::Write>(
120        &self,
121        writer: &mut W,
122    ) -> Result<(), cbor4ii::core::enc::Error<W::Error>> {
123        cbor4ii::core::types::Bytes(self.0.to_signed_bytes_le().as_slice()).encode(writer)
124    }
125}
126
127#[cfg(feature = "encoding-impl")]
128impl<'de> cbor4ii::core::dec::Decode<'de> for EncodeBigInt2<Box<BigIntValue>> {
129    #[inline]
130    fn decode<R: cbor4ii::core::dec::Read<'de>>(
131        reader: &mut R,
132    ) -> Result<Self, cbor4ii::core::dec::Error<R::Error>> {
133        let buf = <cbor4ii::core::types::Bytes<&'de [u8]>>::decode(reader)?;
134        Ok(EncodeBigInt2(Box::new(BigIntValue::from_signed_bytes_le(
135            buf.0,
136        ))))
137    }
138}
139
140#[cfg(feature = "arbitrary")]
141#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
142impl<'a> arbitrary::Arbitrary<'a> for BigInt {
143    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
144        let span = u.arbitrary()?;
145        let value = Box::new(u.arbitrary::<usize>()?.into());
146        let raw = Some(u.arbitrary::<String>()?.into());
147
148        Ok(Self { span, value, raw })
149    }
150}
151
152impl From<BigIntValue> for BigInt {
153    #[inline]
154    fn from(value: BigIntValue) -> Self {
155        BigInt {
156            span: DUMMY_SP,
157            value: Box::new(value),
158            raw: None,
159        }
160    }
161}
162
163/// A string literal.
164#[ast_node("StringLiteral")]
165#[derive(Eq, Hash)]
166#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
167pub struct Str {
168    pub span: Span,
169
170    pub value: Wtf8Atom,
171
172    /// Use `None` value only for transformations to avoid recalculate escaped
173    /// characters in strings
174    #[cfg_attr(
175        feature = "encoding-impl",
176        encoding(with = "cbor4ii::core::types::Maybe")
177    )]
178    pub raw: Option<Atom>,
179}
180
181impl Take for Str {
182    fn dummy() -> Self {
183        Str {
184            span: DUMMY_SP,
185            value: Wtf8Atom::default(),
186            raw: None,
187        }
188    }
189}
190
191#[cfg(feature = "arbitrary")]
192#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
193impl<'a> arbitrary::Arbitrary<'a> for Str {
194    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
195        let span = u.arbitrary()?;
196        let value = u.arbitrary::<Wtf8Atom>()?.into();
197        let raw = Some(u.arbitrary::<String>()?.into());
198
199        Ok(Self { span, value, raw })
200    }
201}
202
203fn emit_span_error(span: Span, msg: &str) {
204    HANDLER.with(|handler| {
205        handler.struct_span_err(span, msg).emit();
206    });
207}
208
209impl Str {
210    #[inline]
211    pub fn is_empty(&self) -> bool {
212        self.value.is_empty()
213    }
214
215    /// Decodes an ordinary template element's raw text into its string value.
216    ///
217    /// Normalizes line endings and preserves unpaired UTF-16 surrogates.
218    /// Invalid escapes emit diagnostics; tagged templates must preserve
219    /// their optional cooked value instead of using this conversion.
220    pub fn from_tpl_raw(tpl: &TplElement) -> Wtf8Atom {
221        let tpl_raw = &tpl.raw;
222        let span = tpl.span;
223        let mut buf: Wtf8Buf = Wtf8Buf::with_capacity(tpl_raw.len());
224        let mut iter = tpl_raw.chars();
225        while let Some(c) = iter.next() {
226            match c {
227                '\\' => {
228                    if let Some(c) = iter.next() {
229                        match c {
230                            '`' | '$' | '\\' => {
231                                buf.push_char(c);
232                            }
233                            'b' => {
234                                buf.push_char('\u{0008}');
235                            }
236                            'f' => {
237                                buf.push_char('\u{000C}');
238                            }
239                            'n' => {
240                                buf.push_char('\n');
241                            }
242                            'r' => {
243                                buf.push_char('\r');
244                            }
245                            't' => {
246                                buf.push_char('\t');
247                            }
248                            'v' => {
249                                buf.push_char('\u{000B}');
250                            }
251                            '\r' => {
252                                let mut next_iter = iter.clone();
253                                if let Some('\n') = next_iter.next() {
254                                    iter = next_iter;
255                                }
256                            }
257                            '\n' | '\u{2028}' | '\u{2029}' => {}
258                            'u' | 'x' => {
259                                let braced = c == 'u' && iter.clone().next() == Some('{');
260                                if braced {
261                                    iter.next();
262                                }
263                                let digits = if c == 'u' { 4 } else { 2 };
264                                let mut count = 0;
265                                let mut result = 0u32;
266                                let mut terminated = !braced;
267                                for c in &mut iter {
268                                    if braced && c == '}' {
269                                        terminated = true;
270                                        break;
271                                    }
272                                    let Some(digit) = c.to_digit(16) else {
273                                        emit_span_error(
274                                            span,
275                                            "Uncaught SyntaxError: Invalid Unicode escape sequence",
276                                        );
277                                        break;
278                                    };
279                                    // Saturation keeps arbitrarily long invalid escapes from
280                                    // overflowing while allowing leading zeroes in valid ones.
281                                    result = result.saturating_mul(16).saturating_add(digit);
282                                    count += 1;
283                                    if !braced && count == digits {
284                                        break;
285                                    }
286                                }
287                                if !terminated || count == 0 || (!braced && count != digits) {
288                                    emit_span_error(
289                                        span,
290                                        "Uncaught SyntaxError: Invalid hexadecimal escape sequence",
291                                    );
292                                }
293                                if let Some(code_point) = CodePoint::from_u32(result) {
294                                    // Wtf8Buf joins adjacent surrogate pairs and preserves
295                                    // unpaired surrogates without lookahead or buffering.
296                                    buf.push(code_point);
297                                } else {
298                                    emit_span_error(
299                                        span,
300                                        "Uncaught SyntaxError: Undefined Unicode code-point",
301                                    );
302                                }
303                            }
304                            '0'..='7' => {
305                                let next = iter.clone().next();
306                                if c == '0' {
307                                    match next {
308                                        Some(next) => {
309                                            if !next.is_digit(8) {
310                                                buf.push_char('\u{0000}');
311                                                continue;
312                                            }
313                                        }
314                                        // \0 is not an octal literal nor decimal literal.
315                                        _ => {
316                                            buf.push_char('\u{0000}');
317                                            continue;
318                                        }
319                                    }
320                                }
321                                emit_span_error(
322                                    span,
323                                    "Uncaught SyntaxError: Octal escape sequences are not allowed \
324                                     in template strings.",
325                                );
326                            }
327                            _ => {
328                                // output raw value when this is not supported
329                                buf.push_char(c);
330                            }
331                        }
332                    }
333                }
334
335                '\r' => {
336                    if iter.clone().next() == Some('\n') {
337                        iter.next();
338                    }
339                    buf.push_char('\n');
340                }
341
342                c => {
343                    buf.push_char(c);
344                }
345            }
346        }
347
348        buf.into()
349    }
350}
351
352impl EqIgnoreSpan for Str {
353    fn eq_ignore_span(&self, other: &Self) -> bool {
354        self.value == other.value
355    }
356}
357
358impl From<Atom> for Str {
359    #[inline]
360    fn from(value: Atom) -> Self {
361        Str {
362            span: DUMMY_SP,
363            value: value.into(),
364            raw: None,
365        }
366    }
367}
368
369impl From<Wtf8Atom> for Str {
370    #[inline]
371    fn from(value: Wtf8Atom) -> Self {
372        Str {
373            span: DUMMY_SP,
374            value,
375            raw: None,
376        }
377    }
378}
379
380bridge_from!(Str, Atom, &'_ str);
381bridge_from!(Str, Atom, String);
382bridge_from!(Str, Atom, Cow<'_, str>);
383
384/// A boolean literal.
385///
386///
387/// # Creation
388///
389/// If you are creating a boolean literal with a dummy span, please use
390/// `true.into()` or `false.into()`, instead of creating this struct directly.
391///
392/// All of `Box<Expr>`, `Expr`, `Lit`, `Bool` implements `From<bool>`.
393#[ast_node("BooleanLiteral")]
394#[derive(Copy, Eq, Hash, EqIgnoreSpan)]
395#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
396#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
397pub struct Bool {
398    pub span: Span,
399    pub value: bool,
400}
401
402impl Take for Bool {
403    fn dummy() -> Self {
404        Bool {
405            span: DUMMY_SP,
406            value: false,
407        }
408    }
409}
410
411impl From<bool> for Bool {
412    #[inline]
413    fn from(value: bool) -> Self {
414        Bool {
415            span: DUMMY_SP,
416            value,
417        }
418    }
419}
420
421#[ast_node("NullLiteral")]
422#[derive(Copy, Eq, Hash, EqIgnoreSpan)]
423#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
424#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
425pub struct Null {
426    pub span: Span,
427}
428
429impl Take for Null {
430    fn dummy() -> Self {
431        Null { span: DUMMY_SP }
432    }
433}
434
435#[ast_node("RegExpLiteral")]
436#[derive(Eq, Hash, EqIgnoreSpan)]
437#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
438pub struct Regex {
439    pub span: Span,
440
441    #[cfg_attr(feature = "serde-impl", serde(rename = "pattern"))]
442    pub exp: Atom,
443
444    #[cfg_attr(feature = "serde-impl", serde(default))]
445    pub flags: Atom,
446}
447
448impl Take for Regex {
449    fn dummy() -> Self {
450        Self {
451            span: DUMMY_SP,
452            exp: Default::default(),
453            flags: Default::default(),
454        }
455    }
456}
457
458#[cfg(feature = "arbitrary")]
459#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
460impl<'a> arbitrary::Arbitrary<'a> for Regex {
461    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
462        use swc_atoms::atom;
463
464        let span = u.arbitrary()?;
465        let exp = u.arbitrary::<String>()?.into();
466        let flags = atom!(""); // TODO
467
468        Ok(Self { span, exp, flags })
469    }
470}
471
472/// A numeric literal.
473///
474///
475/// # Creation
476///
477/// If you are creating a numeric literal with a dummy span, please use
478/// `literal.into()`, instead of creating this struct directly.
479///
480/// All of `Box<Expr>`, `Expr`, `Lit`, `Number` implements `From<64>` and
481/// `From<usize>`.
482
483#[ast_node("NumericLiteral", no_partial_eq)]
484#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
485pub struct Number {
486    pub span: Span,
487    /// The numeric value, including `NaN` and positive or negative infinity.
488    ///
489    /// Equality treats all `NaN` representations as equal and distinguishes
490    /// positive and negative zero.
491    pub value: f64,
492
493    /// Use `None` value only for transformations to avoid recalculate
494    /// characters in number literal
495    #[cfg_attr(
496        feature = "encoding-impl",
497        encoding(with = "cbor4ii::core::types::Maybe")
498    )]
499    pub raw: Option<Atom>,
500}
501
502impl PartialEq for Number {
503    fn eq(&self, other: &Self) -> bool {
504        self.span == other.span && number_value_eq(self.value, other.value) && self.raw == other.raw
505    }
506}
507
508impl Eq for Number {}
509
510impl EqIgnoreSpan for Number {
511    fn eq_ignore_span(&self, other: &Self) -> bool {
512        number_value_eq(self.value, other.value)
513    }
514}
515
516impl Hash for Number {
517    fn hash<H: Hasher>(&self, state: &mut H) {
518        self.span.hash(state);
519        canonical_number_bits(self.value).hash(state);
520        self.raw.hash(state);
521    }
522}
523
524/// Compares numeric values using the equality semantics required by `Number`.
525///
526/// NaN payloads are not observable in ECMAScript, while the sign of zero is.
527#[inline]
528fn number_value_eq(left: f64, right: f64) -> bool {
529    // Compare bits so positive and negative zero remain distinct.
530    left.to_bits() == right.to_bits() || (left.is_nan() && right.is_nan())
531}
532
533/// Returns bits consistent with [`number_value_eq`].
534///
535/// This follows the NaN canonicalization used by `ordered-float`, but retains
536/// the sign bit of zero.
537#[inline]
538fn canonical_number_bits(value: f64) -> u64 {
539    const CANONICAL_NAN_BITS: u64 = 0x7ff8_0000_0000_0000;
540
541    if value.is_nan() {
542        CANONICAL_NAN_BITS
543    } else {
544        value.to_bits()
545    }
546}
547
548impl Display for Number {
549    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
550        if self.value.is_infinite() {
551            if self.value.is_sign_positive() {
552                Display::fmt("Infinity", f)
553            } else {
554                Display::fmt("-Infinity", f)
555            }
556        } else {
557            Display::fmt(&self.value, f)
558        }
559    }
560}
561
562#[cfg(feature = "arbitrary")]
563#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
564impl<'a> arbitrary::Arbitrary<'a> for Number {
565    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
566        let span = u.arbitrary()?;
567        let value = u.arbitrary::<f64>()?;
568        let raw = Some(u.arbitrary::<String>()?.into());
569
570        Ok(Self { span, value, raw })
571    }
572}
573
574impl From<f64> for Number {
575    #[inline]
576    fn from(value: f64) -> Self {
577        Number {
578            span: DUMMY_SP,
579            value,
580            raw: None,
581        }
582    }
583}
584
585impl From<usize> for Number {
586    #[inline]
587    fn from(value: usize) -> Self {
588        Number {
589            span: DUMMY_SP,
590            value: value as _,
591            raw: None,
592        }
593    }
594}