swc_ecma_minifier/compress/optimize/
strings.rs1use swc_atoms::{atom, Atom, Wtf8Atom};
2use swc_common::{util::take::Take, Spanned, SyntaxContext};
3use swc_ecma_ast::*;
4use swc_ecma_utils::{ExprExt, Value::Known};
5
6use super::Optimizer;
7
8impl Optimizer<'_> {
9 pub(super) fn optimize_expr_in_str_ctx_unsafely(&mut self, e: &mut Expr) {
10 if !self.options.unsafe_passes {
11 return;
12 }
13
14 if let Expr::Call(CallExpr {
15 callee: Callee::Expr(callee),
16 args,
17 ..
18 }) = e
19 {
20 if args
21 .iter()
22 .any(|arg| arg.expr.may_have_side_effects(self.ctx.expr_ctx))
23 {
24 return;
25 }
26
27 if callee.is_ident_ref_to("RegExp") && self.options.unsafe_regexp {
28 if args.len() != 1 {
29 return;
30 }
31
32 self.optimize_expr_in_str_ctx(&mut args[0].expr);
33
34 if let Expr::Lit(Lit::Str(..)) = &*args[0].expr {
35 self.changed = true;
36 report_change!("strings: Unsafely reduced `RegExp` call in a string context");
37
38 *e = *args[0].expr.take();
39 }
40 }
41 }
42 }
43
44 pub(super) fn optimize_expr_in_str_ctx(&mut self, n: &mut Expr) {
46 match n {
47 Expr::Lit(Lit::Str(..)) => return,
48 Expr::Paren(e) => {
49 self.optimize_expr_in_str_ctx(&mut e.expr);
50 if let Expr::Lit(Lit::Str(..)) = &*e.expr {
51 *n = *e.expr.take();
52 self.changed = true;
53 report_change!("string: Removed a paren in a string context");
54 }
55
56 return;
57 }
58 _ => {}
59 }
60
61 let value = n.as_pure_string(self.ctx.expr_ctx);
62 if let Known(value) = value {
63 let span = n.span();
64
65 self.changed = true;
66 report_change!(
67 "strings: Converted an expression into a string literal (in string context)"
68 );
69 *n = Lit::Str(Str {
70 span,
71 raw: None,
72 value: value.into(),
73 })
74 .into();
75 return;
76 }
77
78 match n {
79 Expr::Lit(Lit::Num(v)) => {
80 self.changed = true;
81 report_change!(
82 "strings: Converted a numeric literal ({}) into a string literal (in string \
83 context)",
84 v.value
85 );
86
87 let value = format!("{:?}", v.value);
88
89 *n = Lit::Str(Str {
90 span: v.span,
91 raw: None,
92 value: value.into(),
93 })
94 .into();
95 }
96
97 Expr::Lit(Lit::Regex(v)) => {
98 if !self.options.evaluate {
99 return;
100 }
101 self.changed = true;
102 report_change!(
103 "strings: Converted a regex (/{}/{}) into a string literal (in string context)",
104 v.exp,
105 v.flags
106 );
107
108 let value = format!("/{}/{}", v.exp, v.flags);
109
110 *n = Lit::Str(Str {
111 span: v.span,
112 raw: None,
113 value: value.into(),
114 })
115 .into();
116 }
117
118 Expr::Bin(BinExpr {
119 span,
120 op: op!("/"),
121 left,
122 right,
123 }) => {
124 if let (Expr::Lit(Lit::Num(l)), Expr::Lit(Lit::Num(r))) = (&**left, &**right) {
125 if l.value == 0.0 && r.value == 0.0 {
126 *n = Ident::new(
127 atom!("NaN"),
128 *span,
129 SyntaxContext::empty().apply_mark(self.marks.unresolved_mark),
130 )
131 .into();
132 self.changed = true;
133 report_change!("strings: Evaluated 0 / 0 => NaN in string context");
134 }
135 }
136 }
137
138 _ => {}
139 }
140 }
141
142 pub(super) fn reduce_escaped_newline_for_str_lit(&mut self, expr: &mut Expr) {
145 if self.options.ecma < EsVersion::Es2015
146 || !self.options.experimental.reduce_escaped_newline
147 {
148 return;
149 }
150
151 if let Expr::Lit(Lit::Str(s)) = expr {
152 if s.value.contains_char('\n') {
153 *expr = Expr::Tpl(Tpl {
154 span: s.span,
155 exprs: Default::default(),
156 quasis: vec![TplElement {
157 span: s.span,
158 cooked: Some(s.value.clone()),
159 raw: convert_str_value_to_tpl_raw(&s.value),
160 tail: true,
161 }],
162 });
163 self.changed = true;
164 report_change!("strings: Reduced escaped newline for a string literal");
165 }
166 }
167 }
168}
169
170pub(super) fn convert_str_value_to_tpl_raw(value: &Wtf8Atom) -> Atom {
171 let mut result = String::with_capacity(value.len());
172 let mut code_points = value.code_points().peekable();
173
174 while let Some(code_point) = code_points.next() {
175 if let Some(ch) = code_point.to_char() {
176 match ch {
178 '\\' => result.push_str("\\\\"),
179 '`' => result.push_str("\\`"),
180 '\r' => result.push_str("\\r"),
181 '$' if code_points.peek().and_then(|cp| cp.to_char()) == Some('{') => {
182 result.push_str("\\${");
183 code_points.next(); }
185 _ => result.push(ch),
186 }
187 } else {
188 result.push_str(&format!("\\\\u{:04X}", code_point.to_u32()));
190 }
191 }
192
193 Atom::new(result)
194}