1use std::{fmt::Write, io, str};
2
3use ascii::AsciiChar;
4use compact_str::CompactString;
5use swc_common::{Spanned, DUMMY_SP};
6use swc_ecma_ast::*;
7use swc_ecma_codegen_macros::node_impl;
8
9#[cfg(swc_ast_unknown)]
10use crate::unknown_error;
11use crate::{text_writer::WriteJs, CowStr, Emitter, SourceMapperExt};
12
13#[node_impl]
14impl MacroNode for Lit {
15 fn emit(&mut self, emitter: &mut Macro) -> Result {
16 emitter.emit_leading_comments_of_span(self.span(), false)?;
17
18 srcmap!(emitter, self, true);
19
20 match self {
21 Lit::Bool(Bool { value, .. }) => {
22 if *value {
23 keyword!(emitter, "true")
24 } else {
25 keyword!(emitter, "false")
26 }
27 }
28 Lit::Null(Null { .. }) => keyword!(emitter, "null"),
29 Lit::Str(ref s) => emit!(s),
30 Lit::BigInt(ref s) => emit!(s),
31 Lit::Num(ref n) => emit!(n),
32 Lit::Regex(ref n) => {
33 punct!(emitter, "/");
34 emitter.wr.write_str(&n.exp)?;
35 punct!(emitter, "/");
36 emitter.wr.write_str(&n.flags)?;
37 }
38 Lit::JSXText(ref n) => emit!(n),
39 #[cfg(swc_ast_unknown)]
40 _ => return Err(unknown_error()),
41 }
42
43 Ok(())
44 }
45}
46
47#[node_impl]
48impl MacroNode for Str {
49 fn emit(&mut self, emitter: &mut Macro) -> Result {
50 emitter.wr.commit_pending_semi()?;
51
52 emitter.emit_leading_comments_of_span(self.span(), false)?;
53
54 srcmap!(emitter, self, true);
55
56 if &*self.value == "use strict"
57 && self.raw.is_some()
58 && self.raw.as_ref().unwrap().contains('\\')
59 && (!emitter.cfg.inline_script || !self.raw.as_ref().unwrap().contains("script"))
60 {
61 emitter
62 .wr
63 .write_str_lit(DUMMY_SP, self.raw.as_ref().unwrap())?;
64
65 srcmap!(emitter, self, false);
66
67 return Ok(());
68 }
69
70 let target = emitter.cfg.target;
71
72 if !emitter.cfg.minify {
73 if let Some(raw) = &self.raw {
74 let es5_safe = match emitter.cfg.target {
75 EsVersion::Es3 | EsVersion::Es5 => {
76 !raw.contains("\\u{")
79 }
80 _ => true,
81 };
82
83 if es5_safe
84 && (!emitter.cfg.ascii_only || raw.is_ascii())
85 && (!emitter.cfg.inline_script
86 || !self.raw.as_ref().unwrap().contains("script"))
87 {
88 emitter.wr.write_str_lit(DUMMY_SP, raw)?;
89 return Ok(());
90 }
91 }
92 }
93
94 let (quote_char, mut value) = get_quoted_utf16(&self.value, emitter.cfg.ascii_only, target);
95
96 if emitter.cfg.inline_script {
97 value = CowStr::Owned(
98 replace_close_inline_script(&value)
99 .replace("\x3c!--", "\\x3c!--")
100 .replace("--\x3e", "--\\x3e")
101 .into(),
102 );
103 }
104
105 let quote_str = [quote_char.as_byte()];
106 let quote_str = unsafe {
107 str::from_utf8_unchecked("e_str)
109 };
110
111 emitter.wr.write_str(quote_str)?;
112 emitter.wr.write_str_lit(DUMMY_SP, &value)?;
113 emitter.wr.write_str(quote_str)?;
114
115 Ok(())
118 }
119}
120
121#[node_impl]
122impl MacroNode for Number {
123 fn emit(&mut self, emitter: &mut Macro) -> Result {
124 emitter.emit_num_lit_internal(self, false)?;
125
126 Ok(())
127 }
128}
129
130#[node_impl]
131impl MacroNode for BigInt {
132 fn emit(&mut self, emitter: &mut Macro) -> Result {
133 emitter.emit_leading_comments_of_span(self.span, false)?;
134
135 if emitter.cfg.minify {
136 let value = if *self.value >= 10000000000000000_i64.into() {
137 format!("0x{}", self.value.to_str_radix(16))
138 } else if *self.value <= (-10000000000000000_i64).into() {
139 format!("-0x{}", (-*self.value.clone()).to_str_radix(16))
140 } else {
141 self.value.to_string()
142 };
143 emitter.wr.write_lit(self.span, &value)?;
144 emitter.wr.write_lit(self.span, "n")?;
145 } else {
146 match &self.raw {
147 Some(raw) => {
148 if raw.len() > 2 && emitter.cfg.target < EsVersion::Es2021 && raw.contains('_')
149 {
150 emitter.wr.write_str_lit(self.span, &raw.replace('_', ""))?;
151 } else {
152 emitter.wr.write_str_lit(self.span, raw)?;
153 }
154 }
155 _ => {
156 emitter.wr.write_lit(self.span, &self.value.to_string())?;
157 emitter.wr.write_lit(self.span, "n")?;
158 }
159 }
160 }
161
162 Ok(())
163 }
164}
165
166#[node_impl]
167impl MacroNode for Bool {
168 fn emit(&mut self, emitter: &mut Macro) -> Result {
169 emitter.emit_leading_comments_of_span(self.span(), false)?;
170
171 if self.value {
172 keyword!(emitter, self.span, "true")
173 } else {
174 keyword!(emitter, self.span, "false")
175 }
176
177 Ok(())
178 }
179}
180
181pub fn replace_close_inline_script(raw: &str) -> CowStr {
182 let chars = raw.as_bytes();
183 let pattern_len = 8; let mut matched_indexes = chars
186 .iter()
187 .enumerate()
188 .filter(|(index, byte)| {
189 byte == &&b'<'
190 && index + pattern_len < chars.len()
191 && chars[index + 1..index + pattern_len].eq_ignore_ascii_case(b"/script")
192 && matches!(
193 chars[index + pattern_len],
194 b'>' | b' ' | b'\t' | b'\n' | b'\x0C' | b'\r'
195 )
196 })
197 .map(|(index, _)| index)
198 .peekable();
199
200 if matched_indexes.peek().is_none() {
201 return CowStr::Borrowed(raw);
202 }
203
204 let mut result = CompactString::new(raw);
205
206 for (offset, i) in matched_indexes.enumerate() {
207 result.insert(i + 1 + offset, '\\');
208 }
209
210 CowStr::Owned(result)
211}
212
213impl<W, S: swc_common::SourceMapper> Emitter<'_, W, S>
214where
215 W: WriteJs,
216 S: SourceMapperExt,
217{
218 pub fn emit_num_lit_internal(
221 &mut self,
222 num: &Number,
223 mut detect_dot: bool,
224 ) -> std::result::Result<bool, io::Error> {
225 self.wr.commit_pending_semi()?;
226
227 self.emit_leading_comments_of_span(num.span(), false)?;
228
229 if num.value.is_infinite() && num.raw.is_none() {
231 self.wr.write_str_lit(num.span, &num.value.print())?;
232
233 return Ok(false);
234 }
235
236 let mut striped_raw = None;
237 let mut value = String::default();
238
239 srcmap!(self, num, true);
240
241 if self.cfg.minify {
242 if num.value.is_infinite() && num.raw.is_some() {
243 self.wr.write_str_lit(DUMMY_SP, num.raw.as_ref().unwrap())?;
244 } else {
245 value = minify_number(num.value, &mut detect_dot);
246 self.wr.write_str_lit(DUMMY_SP, &value)?;
247 }
248 } else {
249 match &num.raw {
250 Some(raw) => {
251 if raw.len() > 2 && self.cfg.target < EsVersion::Es2015 && {
252 let slice = &raw.as_bytes()[..2];
253 slice == b"0b" || slice == b"0o" || slice == b"0B" || slice == b"0O"
254 } {
255 if num.value.is_infinite() && num.raw.is_some() {
256 self.wr.write_str_lit(DUMMY_SP, num.raw.as_ref().unwrap())?;
257 } else {
258 value = num.value.print();
259 self.wr.write_str_lit(DUMMY_SP, &value)?;
260 }
261 } else if raw.len() > 2
262 && self.cfg.target < EsVersion::Es2021
263 && raw.contains('_')
264 {
265 let value = raw.replace('_', "");
266 self.wr.write_str_lit(DUMMY_SP, &value)?;
267
268 striped_raw = Some(value);
269 } else {
270 self.wr.write_str_lit(DUMMY_SP, raw)?;
271
272 if !detect_dot {
273 return Ok(false);
274 }
275
276 striped_raw = Some(raw.replace('_', ""));
277 }
278 }
279 _ => {
280 value = num.value.print();
281 self.wr.write_str_lit(DUMMY_SP, &value)?;
282 }
283 }
284 }
285
286 if !detect_dot {
288 return Ok(false);
289 }
290
291 Ok(striped_raw
292 .map(|raw| {
293 if raw.bytes().all(|c| c.is_ascii_digit()) {
294 let slice = raw.as_bytes();
297 if slice.len() >= 2 && slice[0] == b'0' {
298 return false;
299 }
300
301 return true;
302 }
303
304 false
305 })
306 .unwrap_or_else(|| {
307 let bytes = value.as_bytes();
308
309 if !bytes.contains(&b'.') && !bytes.contains(&b'e') {
310 return true;
311 }
312
313 false
314 }))
315 }
316}
317
318pub fn get_quoted_utf16(v: &str, ascii_only: bool, target: EsVersion) -> (AsciiChar, CowStr) {
320 if v.is_ascii() {
323 let mut needs_escaping = false;
324 let mut single_quote_count = 0;
325 let mut double_quote_count = 0;
326
327 for &b in v.as_bytes() {
328 match b {
329 b'\'' => single_quote_count += 1,
330 b'"' => double_quote_count += 1,
331 0..=0x1f | b'\\' => {
333 needs_escaping = true;
334 break;
335 }
336 _ => {}
337 }
338 }
339
340 if !needs_escaping {
341 let quote_char = if double_quote_count > single_quote_count {
342 AsciiChar::Apostrophe
343 } else {
344 AsciiChar::Quotation
345 };
346
347 if (quote_char == AsciiChar::Apostrophe && single_quote_count == 0)
349 || (quote_char == AsciiChar::Quotation && double_quote_count == 0)
350 {
351 return (quote_char, CowStr::Borrowed(v));
352 }
353 }
354 }
355
356 let (mut single_quote_count, mut double_quote_count) = (0, 0);
359 for c in v.chars() {
360 match c {
361 '\'' => single_quote_count += 1,
362 '"' => double_quote_count += 1,
363 _ => {}
364 }
365 }
366
367 let quote_char = if double_quote_count > single_quote_count {
369 AsciiChar::Apostrophe
370 } else {
371 AsciiChar::Quotation
372 };
373 let escape_char = if quote_char == AsciiChar::Apostrophe {
374 AsciiChar::Apostrophe
375 } else {
376 AsciiChar::Quotation
377 };
378 let escape_count = if quote_char == AsciiChar::Apostrophe {
379 single_quote_count
380 } else {
381 double_quote_count
382 };
383
384 let capacity = v.len() + escape_count;
386 let mut buf = CompactString::with_capacity(capacity);
387
388 let mut iter = v.chars().peekable();
389 while let Some(c) = iter.next() {
390 match c {
391 '\x00' => {
392 if target < EsVersion::Es5 || matches!(iter.peek(), Some('0'..='9')) {
393 buf.push_str("\\x00");
394 } else {
395 buf.push_str("\\0");
396 }
397 }
398 '\u{0008}' => buf.push_str("\\b"),
399 '\u{000c}' => buf.push_str("\\f"),
400 '\n' => buf.push_str("\\n"),
401 '\r' => buf.push_str("\\r"),
402 '\u{000b}' => buf.push_str("\\v"),
403 '\t' => buf.push('\t'),
404 '\\' => {
405 let next = iter.peek();
406 match next {
407 Some('u') => {
408 let mut inner_iter = iter.clone();
409 inner_iter.next();
410
411 let mut is_curly = false;
412 let mut next = inner_iter.peek();
413
414 if next == Some(&'{') {
415 is_curly = true;
416 inner_iter.next();
417 next = inner_iter.peek();
418 } else if next != Some(&'D') && next != Some(&'d') {
419 buf.push('\\');
420 }
421
422 if let Some(c @ 'D' | c @ 'd') = next {
423 let mut inner_buf = String::with_capacity(8);
424 inner_buf.push('\\');
425 inner_buf.push('u');
426
427 if is_curly {
428 inner_buf.push('{');
429 }
430
431 inner_buf.push(*c);
432 inner_iter.next();
433
434 let mut is_valid = true;
435 for _ in 0..3 {
436 match inner_iter.next() {
437 Some(c @ '0'..='9') | Some(c @ 'a'..='f')
438 | Some(c @ 'A'..='F') => {
439 inner_buf.push(c);
440 }
441 _ => {
442 is_valid = false;
443 break;
444 }
445 }
446 }
447
448 if is_curly {
449 inner_buf.push('}');
450 }
451
452 let range = if is_curly {
453 3..(inner_buf.len() - 1)
454 } else {
455 2..6
456 };
457
458 if is_valid {
459 let val_str = &inner_buf[range];
460 if let Ok(v) = u32::from_str_radix(val_str, 16) {
461 if v > 0xffff {
462 buf.push_str(&inner_buf);
463 let end = if is_curly { 7 } else { 5 };
464 for _ in 0..end {
465 iter.next();
466 }
467 } else if (0xd800..=0xdfff).contains(&v) {
468 buf.push('\\');
469 } else {
470 buf.push_str("\\\\");
471 }
472 } else {
473 buf.push_str("\\\\");
474 }
475 } else {
476 buf.push_str("\\\\");
477 }
478 } else if is_curly {
479 buf.push_str("\\\\");
480 } else {
481 buf.push('\\');
482 }
483 }
484 _ => buf.push_str("\\\\"),
485 }
486 }
487 c if c == escape_char => {
488 buf.push('\\');
489 buf.push(c);
490 }
491 '\x01'..='\x0f' => {
492 buf.push_str("\\x0");
493 write!(&mut buf, "{:x}", c as u8).unwrap();
494 }
495 '\x10'..='\x1f' => {
496 buf.push_str("\\x");
497 write!(&mut buf, "{:x}", c as u8).unwrap();
498 }
499 '\x20'..='\x7e' => buf.push(c),
500 '\u{7f}'..='\u{ff}' => {
501 if ascii_only || target <= EsVersion::Es5 {
502 buf.push_str("\\x");
503 write!(&mut buf, "{:x}", c as u8).unwrap();
504 } else {
505 buf.push(c);
506 }
507 }
508 '\u{2028}' => buf.push_str("\\u2028"),
509 '\u{2029}' => buf.push_str("\\u2029"),
510 '\u{FEFF}' => buf.push_str("\\uFEFF"),
511 c => {
512 if c.is_ascii() {
513 buf.push(c);
514 } else if c > '\u{FFFF}' {
515 if target <= EsVersion::Es5 {
516 let h = ((c as u32 - 0x10000) / 0x400) + 0xd800;
517 let l = (c as u32 - 0x10000) % 0x400 + 0xdc00;
518 write!(&mut buf, "\\u{h:04X}\\u{l:04X}").unwrap();
519 } else if ascii_only {
520 write!(&mut buf, "\\u{{{:04X}}}", c as u32).unwrap();
521 } else {
522 buf.push(c);
523 }
524 } else if ascii_only {
525 write!(&mut buf, "\\u{:04X}", c as u16).unwrap();
526 } else {
527 buf.push(c);
528 }
529 }
530 }
531 }
532
533 (quote_char, CowStr::Owned(buf))
534}
535
536pub fn minify_number(num: f64, detect_dot: &mut bool) -> String {
537 'hex: {
541 if num.fract() == 0.0 && num.abs() <= u64::MAX as f64 {
542 let int = num.abs() as u64;
543
544 if int < 10000000 {
545 break 'hex;
546 }
547
548 if int % 1000 == 0 {
550 break 'hex;
551 }
552
553 *detect_dot = false;
554 return format!(
555 "{}{:#x}",
556 if num.is_sign_negative() { "-" } else { "" },
557 int
558 );
559 }
560 }
561
562 let mut num = num.to_string();
563
564 if num.contains(".") {
565 *detect_dot = false;
566 }
567
568 if let Some(num) = num.strip_prefix("0.") {
569 let cnt = clz(num);
570 if cnt > 2 {
571 return format!("{}e-{}", &num[cnt..], num.len());
572 }
573 return format!(".{num}");
574 }
575
576 if let Some(num) = num.strip_prefix("-0.") {
577 let cnt = clz(num);
578 if cnt > 2 {
579 return format!("-{}e-{}", &num[cnt..], num.len());
580 }
581 return format!("-.{num}");
582 }
583
584 if num.ends_with("000") {
585 *detect_dot = false;
586
587 let cnt = num
588 .as_bytes()
589 .iter()
590 .rev()
591 .skip(3)
592 .take_while(|&&c| c == b'0')
593 .count()
594 + 3;
595
596 num.truncate(num.len() - cnt);
597 num.push('e');
598 num.push_str(&cnt.to_string());
599 }
600
601 num
602}
603
604fn clz(s: &str) -> usize {
605 s.as_bytes().iter().take_while(|&&c| c == b'0').count()
606}
607
608pub trait Print {
609 fn print(&self) -> String;
610}
611
612impl Print for f64 {
613 fn print(&self) -> String {
614 if *self == 0.0 {
616 return self.to_string();
617 }
618
619 let mut buffer = ryu_js::Buffer::new();
620 buffer.format(*self).to_string()
621 }
622}