1use std::{fmt::Write, io, str};
2
3use ascii::AsciiChar;
4use compact_str::CompactString;
5use swc_atoms::wtf8::{CodePoint, Wtf8};
6use swc_common::{Spanned, DUMMY_SP};
7use swc_ecma_ast::*;
8use swc_ecma_codegen_macros::node_impl;
9use swc_ecma_utils::number::minify_number;
10
11#[cfg(swc_ast_unknown)]
12use crate::unknown_error;
13use crate::{text_writer::WriteJs, CowStr, Emitter, ExprPrecedence, SourceMapperExt};
14
15#[derive(Clone, Copy)]
16enum NonFiniteNumber {
17 NaN,
18 PositiveInfinity,
19 NegativeInfinity,
20}
21
22impl NonFiniteNumber {
23 #[inline]
24 fn from_value(value: f64) -> Self {
25 debug_assert!(!value.is_finite());
26
27 if value.is_nan() {
28 Self::NaN
29 } else if value.is_sign_positive() {
30 Self::PositiveInfinity
31 } else {
32 Self::NegativeInfinity
33 }
34 }
35
36 #[inline]
37 fn division_text(self, minify: bool) -> &'static str {
38 match (self, minify) {
39 (Self::NaN, false) => "0 / 0",
40 (Self::PositiveInfinity, false) => "1 / 0",
41 (Self::NegativeInfinity, false) => "-1 / 0",
42 (Self::NaN, true) => "0/0",
43 (Self::PositiveInfinity, true) => "1/0",
44 (Self::NegativeInfinity, true) => "-1/0",
45 }
46 }
47}
48
49#[node_impl]
50impl MacroNode for Lit {
51 fn emit(&mut self, emitter: &mut Macro) -> Result {
52 emitter.emit_leading_comments_of_span(self.span(), false)?;
53
54 srcmap!(emitter, self, true);
55
56 match self {
57 Lit::Bool(Bool { value, .. }) => {
58 if *value {
59 keyword!(emitter, "true")
60 } else {
61 keyword!(emitter, "false")
62 }
63 }
64 Lit::Null(Null { .. }) => keyword!(emitter, "null"),
65 Lit::Str(ref s) => emit!(s),
66 Lit::BigInt(ref s) => emit!(s),
67 Lit::Num(ref n) => {
68 emitter.emit_num_lit_expr(n, ExprPrecedence::LOWEST, false)?;
69 }
70 Lit::Regex(ref n) => {
71 punct!(emitter, "/");
72 let encoded_exp = encode_regex_for_ascii(&n.exp, emitter.cfg.ascii_only);
74 emitter.wr.write_str(&encoded_exp)?;
75 punct!(emitter, "/");
76 emitter.wr.write_str(&n.flags)?;
77 }
78 Lit::JSXText(ref n) => emit!(n),
79 #[cfg(swc_ast_unknown)]
80 _ => return Err(unknown_error()),
81 }
82
83 Ok(())
84 }
85}
86
87#[node_impl]
88impl MacroNode for Str {
89 fn emit(&mut self, emitter: &mut Macro) -> Result {
90 emitter.wr.commit_pending_semi()?;
91
92 emitter.emit_leading_comments_of_span(self.span(), false)?;
93
94 srcmap!(emitter, self, true);
95
96 if &*self.value == "use strict"
97 && self.raw.is_some()
98 && self.raw.as_ref().unwrap().contains('\\')
99 && (!emitter.cfg.inline_script || !self.raw.as_ref().unwrap().contains("script"))
100 {
101 emitter
102 .wr
103 .write_str_lit(DUMMY_SP, self.raw.as_ref().unwrap())?;
104
105 srcmap!(emitter, self, false);
106
107 return Ok(());
108 }
109
110 let target = emitter.cfg.target;
111
112 if !emitter.cfg.minify {
113 if let Some(raw) = &self.raw {
114 let es5_safe = match emitter.cfg.target {
115 EsVersion::Es3 | EsVersion::Es5 => {
116 !raw.contains("\\u{")
119 }
120 _ => true,
121 };
122
123 if es5_safe
124 && (!emitter.cfg.ascii_only || raw.is_ascii())
125 && (!emitter.cfg.inline_script
126 || !self.raw.as_ref().unwrap().contains("script"))
127 {
128 emitter.wr.write_str_lit(DUMMY_SP, raw)?;
129 return Ok(());
130 }
131 }
132 }
133
134 let (quote_char, value) = get_quoted_utf16(&self.value, emitter.cfg.ascii_only, target);
135 let value = if emitter.cfg.inline_script {
136 escape_inline_script(&value)
137 } else {
138 CowStr::Borrowed(&value)
139 };
140
141 let quote_str = [quote_char.as_byte()];
142 let quote_str = unsafe {
143 str::from_utf8_unchecked("e_str)
145 };
146
147 emitter.wr.write_str(quote_str)?;
148 emitter.wr.write_str_lit(DUMMY_SP, &value)?;
149 emitter.wr.write_str(quote_str)?;
150
151 Ok(())
154 }
155}
156
157#[node_impl]
158impl MacroNode for Number {
159 fn emit(&mut self, emitter: &mut Macro) -> Result {
160 emitter.emit_num_lit_internal(self, false)?;
161
162 Ok(())
163 }
164}
165
166#[node_impl]
167impl MacroNode for BigInt {
168 fn emit(&mut self, emitter: &mut Macro) -> Result {
169 emitter.emit_leading_comments_of_span(self.span, false)?;
170
171 if emitter.cfg.minify {
172 let value = if *self.value >= 10000000000000000_i64.into() {
173 format!("0x{}", self.value.to_str_radix(16))
174 } else if *self.value <= (-10000000000000000_i64).into() {
175 format!("-0x{}", (-*self.value.clone()).to_str_radix(16))
176 } else {
177 self.value.to_string()
178 };
179 emitter.wr.write_lit(self.span, &value)?;
180 emitter.wr.write_lit(self.span, "n")?;
181 } else {
182 match &self.raw {
183 Some(raw) => {
184 if raw.len() > 2 && emitter.cfg.target < EsVersion::Es2021 && raw.contains('_')
185 {
186 emitter.wr.write_str_lit(self.span, &raw.replace('_', ""))?;
187 } else {
188 emitter.wr.write_str_lit(self.span, raw)?;
189 }
190 }
191 _ => {
192 emitter.wr.write_lit(self.span, &self.value.to_string())?;
193 emitter.wr.write_lit(self.span, "n")?;
194 }
195 }
196 }
197
198 Ok(())
199 }
200}
201
202#[node_impl]
203impl MacroNode for Bool {
204 fn emit(&mut self, emitter: &mut Macro) -> Result {
205 emitter.emit_leading_comments_of_span(self.span(), false)?;
206
207 if self.value {
208 keyword!(emitter, self.span, "true")
209 } else {
210 keyword!(emitter, self.span, "false")
211 }
212
213 Ok(())
214 }
215}
216
217pub(crate) fn escape_inline_script(raw: &str) -> CowStr<'_> {
220 let value = replace_close_inline_script(raw);
221 if value.contains("<!--") || value.contains("-->") {
222 CowStr::Owned(
223 value
224 .replace("<!--", "\\x3c!--")
225 .replace("-->", "--\\x3e")
226 .into(),
227 )
228 } else {
229 value
230 }
231}
232
233fn replace_close_inline_script(raw: &str) -> CowStr<'_> {
234 let chars = raw.as_bytes();
235 let pattern_len = 8; let mut matched_indexes = chars
238 .iter()
239 .enumerate()
240 .filter(|(index, byte)| {
241 byte == &&b'<'
242 && index + pattern_len < chars.len()
243 && chars[index + 1..index + pattern_len].eq_ignore_ascii_case(b"/script")
244 && matches!(
245 chars[index + pattern_len],
246 b'>' | b'/' | b' ' | b'\t' | b'\n' | b'\x0C' | b'\r'
247 )
248 })
249 .map(|(index, _)| index)
250 .peekable();
251
252 if matched_indexes.peek().is_none() {
253 return CowStr::Borrowed(raw);
254 }
255
256 let mut result = CompactString::new(raw);
257
258 for (offset, i) in matched_indexes.enumerate() {
259 result.insert(i + 1 + offset, '\\');
260 }
261
262 CowStr::Owned(result)
263}
264
265impl<W, S: swc_common::SourceMapper> Emitter<'_, W, S>
266where
267 W: WriteJs,
268 S: SourceMapperExt,
269{
270 pub(crate) fn emit_num_lit_expr(
280 &mut self,
281 num: &Number,
282 precedence: ExprPrecedence,
283 detect_dot: bool,
284 ) -> std::result::Result<bool, io::Error> {
285 if num.value.is_finite() {
286 return self.emit_num_lit_internal(num, detect_dot);
287 }
288
289 if !self.cfg.minify && num.raw.is_some() {
290 return self.emit_num_lit_internal(num, detect_dot);
291 }
292
293 self.wr.commit_pending_semi()?;
294 self.emit_leading_comments_of_span(num.span(), false)?;
295
296 srcmap!(self, num, true);
297
298 let kind = NonFiniteNumber::from_value(num.value);
299 let requires_parentheses = precedence >= ExprPrecedence::MULTIPLICATIVE;
300 let (text, wrap) =
301 if requires_parentheses && matches!(kind, NonFiniteNumber::PositiveInfinity) {
302 ("2e308", false)
303 } else {
304 (kind.division_text(self.cfg.minify), requires_parentheses)
305 };
306
307 if wrap {
308 punct!(self, "(");
309 }
310 self.wr.write_str_lit(num.span, text)?;
311 if wrap {
312 punct!(self, ")");
313 }
314
315 Ok(false)
316 }
317
318 pub(crate) fn non_finite_expr_starts_with_alpha_num(
319 &self,
320 num: &Number,
321 precedence: ExprPrecedence,
322 ) -> bool {
323 debug_assert!(!num.value.is_finite());
324
325 if !self.cfg.minify {
326 if let Some(raw) = &num.raw {
327 return raw
328 .as_bytes()
329 .first()
330 .is_some_and(u8::is_ascii_alphanumeric);
331 }
332 }
333
334 if precedence >= ExprPrecedence::MULTIPLICATIVE {
335 return num.value == f64::INFINITY;
336 }
337
338 !num.value.is_sign_negative() || num.value.is_nan()
339 }
340
341 pub fn emit_num_lit_internal(
344 &mut self,
345 num: &Number,
346 mut detect_dot: bool,
347 ) -> std::result::Result<bool, io::Error> {
348 self.wr.commit_pending_semi()?;
349
350 self.emit_leading_comments_of_span(num.span(), false)?;
351
352 if num.value.is_infinite() && num.raw.is_none() {
354 self.wr.write_str_lit(num.span, &num.value.print())?;
355
356 return Ok(false);
357 }
358
359 let mut striped_raw = None;
360 let mut value = String::default();
361
362 srcmap!(self, num, true);
363
364 if self.cfg.minify {
365 if let Some(raw) = &num.raw {
366 if num.value.is_infinite() {
367 self.wr.write_str_lit(DUMMY_SP, raw)?;
368 } else {
369 value = minify_number(num.value, &mut detect_dot);
370 self.wr.write_str_lit(DUMMY_SP, &value)?;
371 }
372 } else {
373 value = minify_number(num.value, &mut detect_dot);
374 self.wr.write_str_lit(DUMMY_SP, &value)?;
375 }
376 } else {
377 match &num.raw {
378 Some(raw) => {
379 if raw.len() > 2 && self.cfg.target < EsVersion::Es2015 && {
380 let slice = &raw.as_bytes()[..2];
381 slice == b"0b" || slice == b"0o" || slice == b"0B" || slice == b"0O"
382 } {
383 if num.value.is_infinite() {
384 self.wr.write_str_lit(DUMMY_SP, raw)?;
385 } else {
386 value = num.value.print();
387 self.wr.write_str_lit(DUMMY_SP, &value)?;
388 }
389 } else if raw.len() > 2
390 && self.cfg.target < EsVersion::Es2021
391 && raw.contains('_')
392 {
393 let value = raw.replace('_', "");
394 self.wr.write_str_lit(DUMMY_SP, &value)?;
395
396 striped_raw = Some(value);
397 } else {
398 self.wr.write_str_lit(DUMMY_SP, raw)?;
399
400 if !detect_dot {
401 return Ok(false);
402 }
403
404 striped_raw = Some(raw.replace('_', ""));
405 }
406 }
407 _ => {
408 value = num.value.print();
409 self.wr.write_str_lit(DUMMY_SP, &value)?;
410 }
411 }
412 }
413
414 if !detect_dot {
416 return Ok(false);
417 }
418
419 Ok(striped_raw
420 .map(|raw| {
421 if raw.bytes().all(|c| c.is_ascii_digit()) {
422 let slice = raw.as_bytes();
425 if slice.len() >= 2 && slice[0] == b'0' {
426 return false;
427 }
428
429 return true;
430 }
431
432 false
433 })
434 .unwrap_or_else(|| {
435 let bytes = value.as_bytes();
436
437 if !bytes.contains(&b'.') && !bytes.contains(&b'e') {
438 return true;
439 }
440
441 false
442 }))
443 }
444}
445
446pub fn encode_regex_for_ascii(pattern: &str, ascii_only: bool) -> CowStr<'_> {
458 if !ascii_only || pattern.is_ascii() {
459 return CowStr::Borrowed(pattern);
460 }
461
462 let mut buf = CompactString::with_capacity(pattern.len());
463
464 for c in pattern.chars() {
465 match c {
466 '\x00'..='\x7e' => buf.push(c),
468 '\u{7f}'..='\u{ff}' => {
470 buf.push_str("\\x");
471 write!(&mut buf, "{:02x}", c as u8).unwrap();
472 }
473 '\u{2028}' => buf.push_str("\\u2028"),
475 '\u{2029}' => buf.push_str("\\u2029"),
476 _ => {
478 if c > '\u{FFFF}' {
479 let code_point = c as u32;
481 let h = ((code_point - 0x10000) / 0x400) + 0xd800;
482 let l = ((code_point - 0x10000) % 0x400) + 0xdc00;
483 write!(&mut buf, "\\u{h:04x}\\u{l:04x}").unwrap();
484 } else {
485 write!(&mut buf, "\\u{:04x}", c as u16).unwrap();
486 }
487 }
488 }
489 }
490
491 CowStr::Owned(buf)
492}
493
494macro_rules! cp {
495 ($c:expr) => {
496 unsafe { CodePoint::from_u32_unchecked($c as u32) }
497 };
498}
499
500const DOUBLE_QUOTE: CodePoint = cp!('"');
501const SINGLE_QUOTE: CodePoint = cp!('\'');
502const NULL_CHAR: CodePoint = cp!('\x00');
503const BACKSPACE: CodePoint = cp!('\u{0008}');
504const FORM_FEED: CodePoint = cp!('\u{000c}');
505const LINE_FEED: CodePoint = cp!('\n');
506const CARRIAGE_RETURN: CodePoint = cp!('\r');
507const VERTICAL_TAB: CodePoint = cp!('\u{000b}');
508const TAB: CodePoint = cp!('\t');
509const BACKSLASH: CodePoint = cp!('\\');
510const CTRL_START_1: CodePoint = cp!('\x01');
511const CTRL_END_1: CodePoint = cp!('\x0f');
512const CTRL_START_2: CodePoint = cp!('\x10');
513const CTRL_END_2: CodePoint = cp!('\x1f');
514const PRINTABLE_START: CodePoint = cp!('\x20');
515const PRINTABLE_END: CodePoint = cp!('\x7e');
516const DEL_START: CodePoint = cp!('\u{7f}');
517const DEL_END: CodePoint = cp!('\u{ff}');
518const LINE_SEPARATOR: CodePoint = cp!('\u{2028}');
519const PARAGRAPH_SEPARATOR: CodePoint = cp!('\u{2029}');
520const ZERO_WIDTH_NO_BREAK_SPACE: CodePoint = cp!('\u{FEFF}');
521
522const SURROGATE_START: CodePoint = cp!(0xd800);
523const SURROGATE_END: CodePoint = cp!(0xdfff);
524
525pub fn get_quoted_utf16(v: &Wtf8, ascii_only: bool, target: EsVersion) -> (AsciiChar, CowStr<'_>) {
527 if v.is_ascii() {
530 let mut needs_escaping = false;
531 let mut single_quote_count = 0;
532 let mut double_quote_count = 0;
533
534 for &b in v.as_bytes() {
535 match b {
536 b'\'' => single_quote_count += 1,
537 b'"' => double_quote_count += 1,
538 0..=0x1f | b'\\' => {
540 needs_escaping = true;
541 break;
542 }
543 _ => {}
544 }
545 }
546
547 if !needs_escaping {
548 let quote_char = if double_quote_count > single_quote_count {
549 AsciiChar::Apostrophe
550 } else {
551 AsciiChar::Quotation
552 };
553
554 if (quote_char == AsciiChar::Apostrophe && single_quote_count == 0)
556 || (quote_char == AsciiChar::Quotation && double_quote_count == 0)
557 {
558 return (
559 quote_char,
560 CowStr::Borrowed(v.as_str().unwrap()),
563 );
564 }
565 }
566 }
567
568 let (mut single_quote_count, mut double_quote_count) = (0, 0);
571 for c in v.code_points() {
572 match c {
573 SINGLE_QUOTE => single_quote_count += 1,
574 DOUBLE_QUOTE => double_quote_count += 1,
575 _ => {}
576 }
577 }
578
579 let quote_char = if double_quote_count > single_quote_count {
581 AsciiChar::Apostrophe
582 } else {
583 AsciiChar::Quotation
584 };
585 let escape_char = if quote_char == AsciiChar::Apostrophe {
586 AsciiChar::Apostrophe
587 } else {
588 AsciiChar::Quotation
589 };
590 let escape_count = if quote_char == AsciiChar::Apostrophe {
591 single_quote_count
592 } else {
593 double_quote_count
594 };
595
596 let capacity = v.len() + escape_count;
598 let mut buf = CompactString::with_capacity(capacity);
599
600 let mut iter = v.code_points().peekable();
601 while let Some(c) = iter.next() {
602 match c {
603 NULL_CHAR => {
604 if target < EsVersion::Es5
605 || matches!(iter.peek(), Some(x) if *x >= cp!('0') && *x <= cp!('9'))
606 {
607 buf.push_str("\\x00");
608 } else {
609 buf.push_str("\\0");
610 }
611 }
612 BACKSPACE => buf.push_str("\\b"),
613 FORM_FEED => buf.push_str("\\f"),
614 LINE_FEED => buf.push_str("\\n"),
615 CARRIAGE_RETURN => buf.push_str("\\r"),
616 VERTICAL_TAB => buf.push_str("\\v"),
617 TAB => buf.push('\t'),
618 BACKSLASH => buf.push_str("\\\\"),
619 c if matches!(c.to_char(), Some(c) if c == escape_char) => {
620 buf.push('\\');
621 buf.push(c.to_char().unwrap());
623 }
624 c if c >= CTRL_START_1 && c <= CTRL_END_1 => {
625 buf.push_str("\\x0");
626 write!(&mut buf, "{:x}", c.to_u32() as u8).unwrap();
627 }
628 c if c >= CTRL_START_2 && c <= CTRL_END_2 => {
629 buf.push_str("\\x");
630 write!(&mut buf, "{:x}", c.to_u32() as u8).unwrap();
631 }
632 c if c >= PRINTABLE_START && c <= PRINTABLE_END => {
633 buf.push(c.to_char().unwrap())
635 }
636 c if c >= DEL_START && c <= DEL_END => {
637 if ascii_only || target <= EsVersion::Es5 {
638 buf.push_str("\\x");
639 write!(&mut buf, "{:x}", c.to_u32() as u8).unwrap();
640 } else {
641 buf.push(c.to_char().unwrap());
643 }
644 }
645 LINE_SEPARATOR => buf.push_str("\\u2028"),
646 PARAGRAPH_SEPARATOR => buf.push_str("\\u2029"),
647 ZERO_WIDTH_NO_BREAK_SPACE => buf.push_str("\\uFEFF"),
648 c => {
649 if c.is_ascii() {
650 buf.push(c.to_char().unwrap());
652 } else if c > cp!('\u{FFFF}') {
653 if target <= EsVersion::Es5 {
654 let h = ((c.to_u32() - 0x10000) / 0x400) + 0xd800;
655 let l = (c.to_u32() - 0x10000) % 0x400 + 0xdc00;
656 write!(&mut buf, "\\u{h:04X}\\u{l:04X}").unwrap();
657 } else if ascii_only {
658 write!(&mut buf, "\\u{{{:04X}}}", c.to_u32()).unwrap();
659 } else {
660 buf.push(c.to_char().unwrap());
663 }
664 } else if c >= SURROGATE_START && c <= SURROGATE_END {
665 write!(&mut buf, "\\u{:04X}", c.to_u32()).unwrap();
668 } else if ascii_only {
669 write!(&mut buf, "\\u{:04X}", c.to_u32() as u16).unwrap();
670 } else {
671 buf.push(c.to_char().unwrap());
674 }
675 }
676 }
677 }
678
679 (quote_char, CowStr::Owned(buf))
680}
681
682pub trait Print {
683 fn print(&self) -> String;
684}
685
686impl Print for f64 {
687 fn print(&self) -> String {
688 if *self == 0.0 {
690 return self.to_string();
691 }
692
693 let mut buffer = dragonbox_ecma::Buffer::new();
694 buffer.format(*self).to_string()
695 }
696}