fix(es/parser, es/compat): transform tagged template with invalid escape sequence (#2939)

swc_ecma_parser:
 - Preserve more data for invalid escapes in template literals.

swc_ecma_transforms_compat:
 - Fix handling of template literals. (Closes #2674)
This commit is contained in:
RiESAEX 2021-12-02 14:49:20 +08:00 committed by Donny
parent a93f1111f9
commit d8c8641e59
9 changed files with 222 additions and 159 deletions

View File

@ -1,3 +1,3 @@
var x_y = () => String.raw; console.log(({
console.log(x_y()`\4321\u\x`); y: ()=>String.raw
console.log(String.raw`\4321\u\x`); }).y()`\4321\u\x`), console.log(String.raw`\4321\u\x`);

View File

@ -972,7 +972,7 @@ impl<'a, I: Input> Lexer<'a, I> {
// TODO: Optimize // TODO: Optimize
let mut has_escape = false; let mut has_escape = false;
let mut cooked = Some(String::new()); let mut cooked = Ok(String::new());
let mut raw = String::new(); let mut raw = String::new();
while let Some(c) = self.cur() { while let Some(c) = self.cur() {
@ -1002,17 +1002,13 @@ impl<'a, I: Input> Lexer<'a, I> {
let mut wrapped = Raw(Some(raw)); let mut wrapped = Raw(Some(raw));
match self.read_escaped_char(&mut wrapped) { match self.read_escaped_char(&mut wrapped) {
Ok(Some(s)) => { Ok(Some(s)) => {
if let Some(ref mut cooked) = cooked { if let Ok(ref mut cooked) = cooked {
cooked.extend(s); cooked.extend(s);
} }
} }
Ok(None) => {} Ok(None) => {}
Err(error) => { Err(error) => {
if self.target < EsVersion::Es2018 { cooked = Err(error);
return Err(error);
} else {
cooked = None;
}
} }
} }
raw = wrapped.0.unwrap(); raw = wrapped.0.unwrap();
@ -1032,13 +1028,13 @@ impl<'a, I: Input> Lexer<'a, I> {
} }
}; };
self.bump(); self.bump();
if let Some(ref mut cooked) = cooked { if let Ok(ref mut cooked) = cooked {
cooked.push(c); cooked.push(c);
} }
raw.push(c); raw.push(c);
} else { } else {
self.bump(); self.bump();
if let Some(ref mut cooked) = cooked { if let Ok(ref mut cooked) = cooked {
cooked.push(c); cooked.push(c);
} }
raw.push(c); raw.push(c);

View File

@ -722,15 +722,6 @@ pub(crate) fn lex_tokens(syntax: Syntax, s: &'static str) -> Vec<Token> {
.unwrap() .unwrap()
} }
#[cfg(test)]
pub(crate) fn lex_tokens_with_target(
syntax: Syntax,
target: EsVersion,
s: &'static str,
) -> Vec<Token> {
with_lexer(syntax, target, s, |l| Ok(l.map(|ts| ts.token).collect())).unwrap()
}
/// Returns `(tokens, recovered_errors)`. `(tokens)` may contain an error token /// Returns `(tokens, recovered_errors)`. `(tokens)` may contain an error token
/// if the lexer fails to recover from it. /// if the lexer fails to recover from it.
#[cfg(test)] #[cfg(test)]

View File

@ -1,7 +1,7 @@
extern crate test; extern crate test;
use super::{ use super::{
state::{lex, lex_module_errors, lex_tokens, lex_tokens_with_target, with_lexer}, state::{lex, lex_module_errors, lex_tokens, with_lexer},
*, *,
}; };
use crate::{ use crate::{
@ -9,7 +9,7 @@ use crate::{
lexer::state::lex_errors, lexer::state::lex_errors,
}; };
use std::{ops::Range, str}; use std::{ops::Range, str};
use swc_ecma_ast::EsVersion; use swc_common::SyntaxContext;
use test::{black_box, Bencher}; use test::{black_box, Bencher};
fn sp(r: Range<usize>) -> Span { fn sp(r: Range<usize>) -> Span {
@ -231,7 +231,7 @@ multiline`"
vec![ vec![
tok!('`'), tok!('`'),
Token::Template { Token::Template {
cooked: Some("this\nis\nmultiline".into()), cooked: Ok("this\nis\nmultiline".into()),
raw: "this\nis\nmultiline".into(), raw: "this\nis\nmultiline".into(),
has_escape: false has_escape: false
}, },
@ -247,7 +247,7 @@ fn tpl_raw_unicode_escape() {
vec![ vec![
tok!('`'), tok!('`'),
Token::Template { Token::Template {
cooked: Some(format!("{}", '\u{0010}').into()), cooked: Ok(format!("{}", '\u{0010}').into()),
raw: "\\u{0010}".into(), raw: "\\u{0010}".into(),
has_escape: true has_escape: true
}, },
@ -259,11 +259,20 @@ fn tpl_raw_unicode_escape() {
#[test] #[test]
fn tpl_invalid_unicode_escape() { fn tpl_invalid_unicode_escape() {
assert_eq!( assert_eq!(
lex_tokens_with_target(Syntax::default(), EsVersion::Es2018, r"`\unicode`"), lex_tokens(Syntax::default(), r"`\unicode`"),
vec![ vec![
tok!('`'), tok!('`'),
Token::Template { Token::Template {
cooked: None, cooked: Err(Error {
error: Box::new((
Span {
lo: BytePos(1),
hi: BytePos(3),
ctxt: SyntaxContext::empty()
},
SyntaxError::ExpectedHexChars { count: 4 }
))
}),
raw: "\\unicode".into(), raw: "\\unicode".into(),
has_escape: true has_escape: true
}, },
@ -271,20 +280,47 @@ fn tpl_invalid_unicode_escape() {
] ]
); );
assert_eq!( assert_eq!(
lex_tokens_with_target(Syntax::default(), EsVersion::Es2017, r"`\unicode`"), lex_tokens(Syntax::default(), r"`\u{`"),
vec![ vec![
tok!('`'), tok!('`'),
Token::Error(Error {
error: Box::new((sp(1..3), SyntaxError::ExpectedHexChars { count: 4 })),
}),
Token::Template { Token::Template {
cooked: Some("nicode".into()), cooked: Err(Error {
raw: "nicode".into(), error: Box::new((
has_escape: false, Span {
lo: BytePos(4),
hi: BytePos(4),
ctxt: SyntaxContext::empty()
},
SyntaxError::InvalidCodePoint
))
}),
raw: "\\u{".into(),
has_escape: true
}, },
tok!('`') tok!('`'),
] ]
) );
assert_eq!(
lex_tokens(Syntax::default(), r"`\xhex`"),
vec![
tok!('`'),
Token::Template {
cooked: Err(Error {
error: Box::new((
Span {
lo: BytePos(1),
hi: BytePos(3),
ctxt: SyntaxContext::empty()
},
SyntaxError::ExpectedHexChars { count: 2 }
))
}),
raw: "\\xhex".into(),
has_escape: true
},
tok!('`'),
]
);
} }
#[test] #[test]
@ -687,7 +723,7 @@ fn tpl_empty() {
tok!('`'), tok!('`'),
Template { Template {
raw: "".into(), raw: "".into(),
cooked: Some("".into()), cooked: Ok("".into()),
has_escape: false has_escape: false
}, },
tok!('`') tok!('`')
@ -703,7 +739,7 @@ fn tpl() {
tok!('`'), tok!('`'),
Template { Template {
raw: "".into(), raw: "".into(),
cooked: Some("".into()), cooked: Ok("".into()),
has_escape: false has_escape: false
}, },
tok!("${"), tok!("${"),
@ -711,7 +747,7 @@ fn tpl() {
tok!('}'), tok!('}'),
Template { Template {
raw: "".into(), raw: "".into(),
cooked: Some("".into()), cooked: Ok("".into()),
has_escape: false has_escape: false
}, },
tok!('`'), tok!('`'),
@ -880,7 +916,7 @@ fn issue_191() {
tok!('`'), tok!('`'),
Token::Template { Token::Template {
raw: "".into(), raw: "".into(),
cooked: Some("".into()), cooked: Ok("".into()),
has_escape: false, has_escape: false,
}, },
tok!("${"), tok!("${"),
@ -888,7 +924,7 @@ fn issue_191() {
tok!('}'), tok!('}'),
Token::Template { Token::Template {
raw: "<bar>".into(), raw: "<bar>".into(),
cooked: Some("<bar>".into()), cooked: Ok("<bar>".into()),
has_escape: false, has_escape: false,
}, },
tok!('`') tok!('`')

View File

@ -332,7 +332,7 @@ impl<'a, I: Tokens> Parser<I> {
tok!('`') => { tok!('`') => {
// parse template literal // parse template literal
return Ok(Box::new(Expr::Tpl(self.parse_tpl()?))); return Ok(Box::new(Expr::Tpl(self.parse_tpl(false)?)));
} }
tok!('(') => { tok!('(') => {
@ -847,12 +847,15 @@ impl<'a, I: Tokens> Parser<I> {
} }
#[allow(clippy::vec_box)] #[allow(clippy::vec_box)]
fn parse_tpl_elements(&mut self) -> PResult<(Vec<Box<Expr>>, Vec<TplElement>)> { fn parse_tpl_elements(
&mut self,
is_tagged_tpl: bool,
) -> PResult<(Vec<Box<Expr>>, Vec<TplElement>)> {
trace_cur!(self, parse_tpl_elements); trace_cur!(self, parse_tpl_elements);
let mut exprs = vec![]; let mut exprs = vec![];
let cur_elem = self.parse_tpl_element()?; let cur_elem = self.parse_tpl_element(is_tagged_tpl)?;
let mut is_tail = cur_elem.tail; let mut is_tail = cur_elem.tail;
let mut quasis = vec![cur_elem]; let mut quasis = vec![cur_elem];
@ -860,7 +863,7 @@ impl<'a, I: Tokens> Parser<I> {
expect!(self, "${"); expect!(self, "${");
exprs.push(self.include_in_expr(true).parse_expr()?); exprs.push(self.include_in_expr(true).parse_expr()?);
expect!(self, '}'); expect!(self, '}');
let elem = self.parse_tpl_element()?; let elem = self.parse_tpl_element(is_tagged_tpl)?;
is_tail = elem.tail; is_tail = elem.tail;
quasis.push(elem); quasis.push(elem);
} }
@ -876,7 +879,7 @@ impl<'a, I: Tokens> Parser<I> {
let tagged_tpl_start = tag.span().lo(); let tagged_tpl_start = tag.span().lo();
trace_cur!(self, parse_tagged_tpl); trace_cur!(self, parse_tagged_tpl);
let tpl = self.parse_tpl()?; let tpl = self.parse_tpl(true)?;
let span = span!(self, tagged_tpl_start); let span = span!(self, tagged_tpl_start);
Ok(TaggedTpl { Ok(TaggedTpl {
@ -887,13 +890,13 @@ impl<'a, I: Tokens> Parser<I> {
}) })
} }
pub(super) fn parse_tpl(&mut self) -> PResult<Tpl> { pub(super) fn parse_tpl(&mut self, is_tagged_tpl: bool) -> PResult<Tpl> {
trace_cur!(self, parse_tpl); trace_cur!(self, parse_tpl);
let start = cur_pos!(self); let start = cur_pos!(self);
assert_and_bump!(self, '`'); assert_and_bump!(self, '`');
let (exprs, quasis) = self.parse_tpl_elements()?; let (exprs, quasis) = self.parse_tpl_elements(is_tagged_tpl)?;
expect!(self, '`'); expect!(self, '`');
@ -905,7 +908,7 @@ impl<'a, I: Tokens> Parser<I> {
}) })
} }
pub(super) fn parse_tpl_element(&mut self) -> PResult<TplElement> { pub(super) fn parse_tpl_element(&mut self, is_tagged_tpl: bool) -> PResult<TplElement> {
let start = cur_pos!(self); let start = cur_pos!(self);
let (raw, cooked) = match *cur!(self, true)? { let (raw, cooked) = match *cur!(self, true)? {
@ -914,24 +917,43 @@ impl<'a, I: Tokens> Parser<I> {
raw, raw,
cooked, cooked,
has_escape, has_escape,
} => ( } => match cooked {
Str { Ok(cooked) => (
span: span!(self, start), Str {
value: raw, span: span!(self, start),
has_escape, value: raw,
kind: StrKind::Normal { has_escape,
contains_quote: false, kind: StrKind::Normal {
contains_quote: false,
},
}, },
}, Some(Str {
cooked.map(|cooked| Str { span: span!(self, start),
span: span!(self, start), value: cooked,
value: cooked, has_escape,
has_escape, kind: StrKind::Normal {
kind: StrKind::Normal { contains_quote: false,
contains_quote: false, },
}, }),
}), ),
), Err(err) => {
if is_tagged_tpl {
(
Str {
span: span!(self, start),
value: raw,
has_escape,
kind: StrKind::Normal {
contains_quote: false,
},
},
None,
)
} else {
return Err(err);
}
}
},
_ => unreachable!(), _ => unreachable!(),
}, },
_ => unexpected!(self, "template token"), _ => unexpected!(self, "template token"),

View File

@ -1771,7 +1771,7 @@ impl<I: Tokens> Parser<I> {
let mut types = vec![]; let mut types = vec![];
let cur_elem = self.parse_tpl_element()?; let cur_elem = self.parse_tpl_element(false)?;
let mut is_tail = cur_elem.tail; let mut is_tail = cur_elem.tail;
let mut quasis = vec![cur_elem]; let mut quasis = vec![cur_elem];
@ -1779,7 +1779,7 @@ impl<I: Tokens> Parser<I> {
expect!(self, "${"); expect!(self, "${");
types.push(self.parse_ts_type()?); types.push(self.parse_ts_type()?);
expect!(self, '}'); expect!(self, '}');
let elem = self.parse_tpl_element()?; let elem = self.parse_tpl_element(false)?;
is_tail = elem.tail; is_tail = elem.tail;
quasis.push(elem); quasis.push(elem);
} }

View File

@ -2,7 +2,7 @@
//! //!
//! [babel/babylon]:https://github.com/babel/babel/blob/2d378d076eb0c5fe63234a8b509886005c01d7ee/packages/babylon/src/tokenizer/types.js //! [babel/babylon]:https://github.com/babel/babel/blob/2d378d076eb0c5fe63234a8b509886005c01d7ee/packages/babylon/src/tokenizer/types.js
pub(crate) use self::{AssignOpToken::*, BinOpToken::*, Keyword::*, Token::*}; pub(crate) use self::{AssignOpToken::*, BinOpToken::*, Keyword::*, Token::*};
use crate::error::Error; use crate::{error::Error, lexer::LexResult};
use enum_kind::Kind; use enum_kind::Kind;
use num_bigint::BigInt as BigIntValue; use num_bigint::BigInt as BigIntValue;
use std::{ use std::{
@ -70,7 +70,7 @@ pub enum Token {
BackQuote, BackQuote,
Template { Template {
raw: JsWord, raw: JsWord,
cooked: Option<JsWord>, cooked: LexResult<JsWord>,
has_escape: bool, has_escape: bool,
}, },
/// ':' /// ':'

View File

@ -6,7 +6,7 @@ use swc_ecma_ast::*;
use swc_ecma_transforms_base::{helper, perf::Parallel}; use swc_ecma_transforms_base::{helper, perf::Parallel};
use swc_ecma_transforms_macros::parallel; use swc_ecma_transforms_macros::parallel;
use swc_ecma_utils::{ use swc_ecma_utils::{
is_literal, prepend_stmts, private_ident, quote_ident, ExprFactory, StmtLike, is_literal, prepend_stmts, private_ident, quote_ident, undefined, ExprFactory, StmtLike,
}; };
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
@ -64,10 +64,7 @@ impl VisitMut for TemplateLiteral {
let should_remove_kind = let should_remove_kind =
quasis[0].raw.value.contains('\r') || quasis[0].raw.value.contains('`'); quasis[0].raw.value.contains('\r') || quasis[0].raw.value.contains('`');
let mut s = quasis[0] let mut s = quasis[0].cooked.clone().unwrap();
.cooked
.clone()
.unwrap_or_else(|| quasis[0].raw.clone());
// See https://github.com/swc-project/swc/issues/1488 // See https://github.com/swc-project/swc/issues/1488
// //
@ -319,9 +316,9 @@ impl VisitMut for TemplateLiteral {
elems: quasis elems: quasis
.take() .take()
.into_iter() .into_iter()
.map(|elem| { .map(|elem| match elem.cooked {
Lit::Str(elem.cooked.unwrap_or(elem.raw)) Some(cooked) => Lit::Str(cooked).as_arg(),
.as_arg() None => undefined(DUMMY_SP).as_arg(),
}) })
.map(Some) .map(Some)
.collect(), .collect(),

View File

@ -149,8 +149,6 @@ expect(fn).toThrow(TypeError);"#
); );
test!( test!(
// TODO: Fix parser
ignore,
syntax(), syntax(),
|_| tr(Default::default()), |_| tr(Default::default()),
template_revision, template_revision,
@ -165,99 +163,122 @@ function a() {
var undefined = 4; var undefined = 4;
tag`\01`; tag`\01`;
}"#, }"#,
r#" r#"function _templateObject() {
function _templateObject8() { const data = _taggedTemplateLiteral([
const data = _taggedTemplateLiteral([void 0], ["\\01"]); void 0
], [
_templateObject8 = function () { "\\unicode and \\u{55}"
]);
_templateObject = function() {
return data;
};
return data; return data;
};
return data;
} }
function _templateObject1() {
function _templateObject7() { const data = _taggedTemplateLiteral([
const data = _taggedTemplateLiteral(["left", void 0, "right"], ["left", "\\u{-0}", "right"]); void 0
], [
_templateObject7 = function () { "\\01"
]);
_templateObject1 = function() {
return data;
};
return data; return data;
};
return data;
} }
function _templateObject6() {
const data = _taggedTemplateLiteral(["left", void 0, "right"], ["left", "\\u000g", "right"]);
_templateObject6 = function () {
return data;
};
return data;
}
function _templateObject5() {
const data = _taggedTemplateLiteral(["left", void 0, "right"], ["left", "\\xg", "right"]);
_templateObject5 = function () {
return data;
};
return data;
}
function _templateObject4() {
const data = _taggedTemplateLiteral(["left", void 0], ["left", "\\xg"]);
_templateObject4 = function () {
return data;
};
return data;
}
function _templateObject3() {
const data = _taggedTemplateLiteral([void 0, "right"], ["\\xg", "right"]);
_templateObject3 = function () {
return data;
};
return data;
}
function _templateObject2() { function _templateObject2() {
const data = _taggedTemplateLiteral([void 0], ["\\01"]); const data = _taggedTemplateLiteral([
void 0,
_templateObject2 = function () { "right"
], [
"\\xg",
"right"
]);
_templateObject2 = function() {
return data;
};
return data; return data;
};
return data;
} }
function _templateObject3() {
function _templateObject() { const data = _taggedTemplateLiteral([
const data = _taggedTemplateLiteral([void 0], ["\\unicode and \\u{55}"]); "left",
void 0
_templateObject = function () { ], [
"left",
"\\xg"
]);
_templateObject3 = function() {
return data;
};
return data;
}
function _templateObject4() {
const data = _taggedTemplateLiteral([
"left",
void 0,
"right"
], [
"left",
"\\xg",
"right"
]);
_templateObject4 = function() {
return data;
};
return data;
}
function _templateObject5() {
const data = _taggedTemplateLiteral([
"left",
void 0,
"right"
], [
"left",
"\\u000g",
"right"
]);
_templateObject5 = function() {
return data;
};
return data;
}
function _templateObject6() {
const data = _taggedTemplateLiteral([
"left",
void 0,
"right"
], [
"left",
"\\u{-0}",
"right"
]);
_templateObject6 = function() {
return data;
};
return data;
}
function _templateObject7() {
const data = _taggedTemplateLiteral([
void 0
], [
"\\01"
]);
_templateObject7 = function() {
return data;
};
return data; return data;
};
return data;
} }
tag(_templateObject()); tag(_templateObject());
tag(_templateObject2()); tag(_templateObject1());
tag(_templateObject2(), 0);
tag(_templateObject3(), 0); tag(_templateObject3(), 0);
tag(_templateObject4(), 0); tag(_templateObject4(), 0, 1);
tag(_templateObject5(), 0, 1); tag(_templateObject5(), 0, 1);
tag(_templateObject6(), 0, 1); tag(_templateObject6(), 0, 1);
tag(_templateObject7(), 0, 1);
function a() { function a() {
var undefined = 4; var undefined = 4;
tag(_templateObject8()); tag(_templateObject7());
}"# }"#,
ok_if_code_eq
); );
// default_order_exec // default_order_exec