fix(es/minifier): Fix a bug in tpl string <-> string logic (#8510)

**Related issue:**

 - Closes #8496
This commit is contained in:
Donny/강동윤 2024-01-19 09:44:19 +09:00 committed by GitHub
parent 104f604e50
commit 4946a11137
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 186 additions and 12 deletions

View File

@ -0,0 +1,70 @@
{
"jsc": {
"parser": {
"syntax": "ecmascript",
"jsx": false
},
"loose": true,
"minify": {
"mangle": false,
"compress": {
"arguments": false,
"arrows": true,
"booleans": true,
"booleans_as_integers": false,
"collapse_vars": true,
"comparisons": true,
"computed_props": true,
"conditionals": true,
"dead_code": true,
"directives": true,
"drop_console": false,
"drop_debugger": true,
"evaluate": true,
"expression": false,
"hoist_funs": false,
"hoist_props": true,
"hoist_vars": false,
"if_return": true,
"join_vars": true,
"keep_classnames": false,
"keep_fargs": true,
"keep_fnames": false,
"keep_infinity": false,
"loops": true,
"negate_iife": true,
"properties": true,
"reduce_funcs": false,
"reduce_vars": false,
"side_effects": true,
"switches": true,
"typeofs": true,
"unsafe": false,
"unsafe_arrows": false,
"unsafe_comps": false,
"unsafe_Function": false,
"unsafe_math": false,
"unsafe_symbols": false,
"unsafe_methods": false,
"unsafe_proto": false,
"unsafe_regexp": false,
"unsafe_undefined": false,
"unused": true,
"const_to_let": true,
"pristine_globals": true
}
}
},
"module": {
"type": "es6"
},
"minify": true,
"isModule": true,
"env": {
"targets": {
"chrome": 109
},
"coreJs": "3.21",
"mode": "usage"
}
}

View File

@ -0,0 +1,3 @@
export const strWithDollar = `$login`;
export const use = `${strWithDollar}`; // expected: "$login" without \\

View File

@ -0,0 +1,3 @@
export const strWithDollar = `$login`;
export const use = `${strWithDollar}${1}`; // expected: "$login" without \\

View File

@ -0,0 +1,3 @@
export const strWithDollar = `$login`;
export const use = `${1}${strWithDollar}`; // expected: "$login" without \\

View File

@ -0,0 +1,3 @@
export const strWithDollar = `$login`;
export const use = `asd${strWithDollar}`; // expected: "$login" without \\

View File

@ -0,0 +1,3 @@
export const strWithDollar = `$login`;
export const use = `$${strWithDollar}`; // expected: "$login" without \\

View File

@ -0,0 +1,3 @@
export const strWithDollar = `$login`;
export const use = `{${strWithDollar}`; // expected: "$login" without \\

View File

@ -0,0 +1,3 @@
export const strWithDollar = `$login`;
export const use = `\{${strWithDollar}`; // expected: "$login" without \\

View File

@ -0,0 +1 @@
export const use = `\xffathjax{$login`;

View File

@ -0,0 +1 @@
export const strWithDollar="$login";export const use="$login";

View File

@ -0,0 +1 @@
export const strWithDollar="$login";export const use="$login1";

View File

@ -0,0 +1 @@
export const strWithDollar="$login";export const use="1$login";

View File

@ -0,0 +1 @@
export const strWithDollar="$login";export const use="asd$login";

View File

@ -0,0 +1 @@
export const strWithDollar="$login";export const use="$$login";

View File

@ -0,0 +1 @@
export const strWithDollar="$login";export const use="{$login";

View File

@ -0,0 +1 @@
export const strWithDollar="$login";export const use="{$login";

View File

@ -0,0 +1 @@
export const use=`\xffathjax{$login`;

View File

@ -265,6 +265,43 @@ impl Pure<'_> {
let mut cur_raw = String::new(); let mut cur_raw = String::new();
let mut cur_cooked = Some(String::new()); let mut cur_cooked = Some(String::new());
for i in 0..(tpl.exprs.len() + tpl.quasis.len()) {
if i % 2 == 0 {
let i = i / 2;
let q = tpl.quasis[i].clone();
if q.cooked.is_some() {
if let Some(cur_cooked) = &mut cur_cooked {
cur_cooked.push_str("");
}
} else {
// If cooked is None, it means that the template literal contains invalid escape
// sequences.
cur_cooked = None;
}
} else {
let i = i / 2;
let e = &tpl.exprs[i];
match &**e {
Expr::Lit(Lit::Str(s)) => {
if cur_cooked.is_none() && s.raw.is_none() {
return;
}
if let Some(cur_cooked) = &mut cur_cooked {
cur_cooked.push_str("");
}
}
_ => {
cur_cooked = Some(String::new());
}
}
}
}
cur_cooked = Some(Default::default());
for i in 0..(tpl.exprs.len() + tpl.quasis.len()) { for i in 0..(tpl.exprs.len() + tpl.quasis.len()) {
if i % 2 == 0 { if i % 2 == 0 {
let i = i / 2; let i = i / 2;
@ -286,10 +323,19 @@ impl Pure<'_> {
match *e { match *e {
Expr::Lit(Lit::Str(s)) => { Expr::Lit(Lit::Str(s)) => {
cur_raw.push_str(&convert_str_value_to_tpl_raw(&s.value));
if let Some(cur_cooked) = &mut cur_cooked { if let Some(cur_cooked) = &mut cur_cooked {
cur_cooked.push_str(&convert_str_value_to_tpl_cooked(&s.value)); cur_cooked.push_str(&convert_str_value_to_tpl_cooked(&s.value));
} }
if let Some(raw) = &s.raw {
if raw.len() >= 2 {
// Exclude quotes
cur_raw
.push_str(&convert_str_raw_to_tpl_raw(&raw[1..raw.len() - 1]));
}
} else {
cur_raw.push_str(&convert_str_value_to_tpl_raw(&s.value));
}
} }
_ => { _ => {
quasis.push(TplElement { quasis.push(TplElement {
@ -325,6 +371,12 @@ impl Pure<'_> {
pub(super) fn concat_tpl(&mut self, l: &mut Expr, r: &mut Expr) { pub(super) fn concat_tpl(&mut self, l: &mut Expr, r: &mut Expr) {
match (&mut *l, &mut *r) { match (&mut *l, &mut *r) {
(Expr::Tpl(l), Expr::Lit(Lit::Str(rs))) => { (Expr::Tpl(l), Expr::Lit(Lit::Str(rs))) => {
if let Some(raw) = &rs.raw {
if raw.len() <= 2 {
return;
}
}
// Append // Append
if let Some(l_last) = l.quasis.last_mut() { if let Some(l_last) = l.quasis.last_mut() {
self.changed = true; self.changed = true;
@ -340,15 +392,27 @@ impl Pure<'_> {
.into(); .into();
} }
let new: Atom = l_last.raw = format!(
format!("{}{}", l_last.raw, convert_str_value_to_tpl_raw(&rs.value)).into(); "{}{}",
l_last.raw = new; l_last.raw,
rs.raw
.clone()
.map(|s| convert_str_raw_to_tpl_raw(&s[1..s.len() - 1]))
.unwrap_or_else(|| convert_str_value_to_tpl_raw(&rs.value).into())
)
.into();
r.take(); r.take();
} }
} }
(Expr::Lit(Lit::Str(ls)), Expr::Tpl(r)) => { (Expr::Lit(Lit::Str(ls)), Expr::Tpl(r)) => {
if let Some(raw) = &ls.raw {
if raw.len() <= 2 {
return;
}
}
// Append // Append
if let Some(r_first) = r.quasis.first_mut() { if let Some(r_first) = r.quasis.first_mut() {
self.changed = true; self.changed = true;
@ -364,9 +428,15 @@ impl Pure<'_> {
.into() .into()
} }
let new: Atom = let new: Atom = format!(
format!("{}{}", convert_str_value_to_tpl_raw(&ls.value), r_first.raw) "{}{}",
.into(); ls.raw
.clone()
.map(|s| convert_str_raw_to_tpl_raw(&s[1..s.len() - 1]))
.unwrap_or_else(|| convert_str_value_to_tpl_raw(&ls.value).into()),
r_first.raw
)
.into();
r_first.raw = new; r_first.raw = new;
l.take(); l.take();
@ -392,7 +462,7 @@ impl Pure<'_> {
debug_assert!(l.quasis.len() == l.exprs.len() + 1, "{:?} is invalid", l); debug_assert!(l.quasis.len() == l.exprs.len() + 1, "{:?} is invalid", l);
self.changed = true; self.changed = true;
report_change!("strings: Merged to template literals"); report_change!("strings: Merged two template literals");
} }
_ => {} _ => {}
} }
@ -498,9 +568,9 @@ impl Pure<'_> {
pub(super) fn convert_str_value_to_tpl_cooked(value: &JsWord) -> Cow<str> { pub(super) fn convert_str_value_to_tpl_cooked(value: &JsWord) -> Cow<str> {
value value
.replace('\\', "\\\\") .replace("\\\\", "\\")
.replace('`', "\\`") .replace("\\`", "`")
.replace('$', "\\$") .replace("\\$", "$")
.into() .into()
} }
@ -513,3 +583,7 @@ pub(super) fn convert_str_value_to_tpl_raw(value: &JsWord) -> Cow<str> {
.replace('\r', "\\r") .replace('\r', "\\r")
.into() .into()
} }
pub(super) fn convert_str_raw_to_tpl_raw(value: &str) -> Atom {
value.replace('`', "\\`").replace('$', "\\$").into()
}

View File

@ -479,7 +479,7 @@ fn fixture(input: PathBuf) {
let expected = { let expected = {
let expected = read_to_string(dir.join("output.js")).unwrap(); let expected = read_to_string(dir.join("output.js")).unwrap();
let fm = cm.new_source_file(FileName::Anon, expected); let fm = cm.new_source_file(FileName::Custom("expected.js".into()), expected);
let lexer = Lexer::new( let lexer = Lexer::new(
Default::default(), Default::default(),
Default::default(), Default::default(),