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