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(x_y()`\4321\u\x`);
console.log(String.raw`\4321\u\x`);
console.log(({
y: ()=>String.raw
}).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
let mut has_escape = false;
let mut cooked = Some(String::new());
let mut cooked = Ok(String::new());
let mut raw = String::new();
while let Some(c) = self.cur() {
@ -1002,17 +1002,13 @@ impl<'a, I: Input> Lexer<'a, I> {
let mut wrapped = Raw(Some(raw));
match self.read_escaped_char(&mut wrapped) {
Ok(Some(s)) => {
if let Some(ref mut cooked) = cooked {
if let Ok(ref mut cooked) = cooked {
cooked.extend(s);
}
}
Ok(None) => {}
Err(error) => {
if self.target < EsVersion::Es2018 {
return Err(error);
} else {
cooked = None;
}
cooked = Err(error);
}
}
raw = wrapped.0.unwrap();
@ -1032,13 +1028,13 @@ impl<'a, I: Input> Lexer<'a, I> {
}
};
self.bump();
if let Some(ref mut cooked) = cooked {
if let Ok(ref mut cooked) = cooked {
cooked.push(c);
}
raw.push(c);
} else {
self.bump();
if let Some(ref mut cooked) = cooked {
if let Ok(ref mut cooked) = cooked {
cooked.push(c);
}
raw.push(c);

View File

@ -722,15 +722,6 @@ pub(crate) fn lex_tokens(syntax: Syntax, s: &'static str) -> Vec<Token> {
.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
/// if the lexer fails to recover from it.
#[cfg(test)]

View File

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

View File

@ -332,7 +332,7 @@ impl<'a, I: Tokens> Parser<I> {
tok!('`') => {
// parse template literal
return Ok(Box::new(Expr::Tpl(self.parse_tpl()?)));
return Ok(Box::new(Expr::Tpl(self.parse_tpl(false)?)));
}
tok!('(') => {
@ -847,12 +847,15 @@ impl<'a, I: Tokens> Parser<I> {
}
#[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);
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 quasis = vec![cur_elem];
@ -860,7 +863,7 @@ impl<'a, I: Tokens> Parser<I> {
expect!(self, "${");
exprs.push(self.include_in_expr(true).parse_expr()?);
expect!(self, '}');
let elem = self.parse_tpl_element()?;
let elem = self.parse_tpl_element(is_tagged_tpl)?;
is_tail = elem.tail;
quasis.push(elem);
}
@ -876,7 +879,7 @@ impl<'a, I: Tokens> Parser<I> {
let tagged_tpl_start = tag.span().lo();
trace_cur!(self, parse_tagged_tpl);
let tpl = self.parse_tpl()?;
let tpl = self.parse_tpl(true)?;
let span = span!(self, tagged_tpl_start);
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);
let start = cur_pos!(self);
assert_and_bump!(self, '`');
let (exprs, quasis) = self.parse_tpl_elements()?;
let (exprs, quasis) = self.parse_tpl_elements(is_tagged_tpl)?;
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 (raw, cooked) = match *cur!(self, true)? {
@ -914,24 +917,43 @@ impl<'a, I: Tokens> Parser<I> {
raw,
cooked,
has_escape,
} => (
Str {
span: span!(self, start),
value: raw,
has_escape,
kind: StrKind::Normal {
contains_quote: false,
} => match cooked {
Ok(cooked) => (
Str {
span: span!(self, start),
value: raw,
has_escape,
kind: StrKind::Normal {
contains_quote: false,
},
},
},
cooked.map(|cooked| Str {
span: span!(self, start),
value: cooked,
has_escape,
kind: StrKind::Normal {
contains_quote: false,
},
}),
),
Some(Str {
span: span!(self, start),
value: cooked,
has_escape,
kind: StrKind::Normal {
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!(),
},
_ => unexpected!(self, "template token"),

View File

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

View File

@ -2,7 +2,7 @@
//!
//! [babel/babylon]:https://github.com/babel/babel/blob/2d378d076eb0c5fe63234a8b509886005c01d7ee/packages/babylon/src/tokenizer/types.js
pub(crate) use self::{AssignOpToken::*, BinOpToken::*, Keyword::*, Token::*};
use crate::error::Error;
use crate::{error::Error, lexer::LexResult};
use enum_kind::Kind;
use num_bigint::BigInt as BigIntValue;
use std::{
@ -70,7 +70,7 @@ pub enum Token {
BackQuote,
Template {
raw: JsWord,
cooked: Option<JsWord>,
cooked: LexResult<JsWord>,
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_macros::parallel;
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};
@ -64,10 +64,7 @@ impl VisitMut for TemplateLiteral {
let should_remove_kind =
quasis[0].raw.value.contains('\r') || quasis[0].raw.value.contains('`');
let mut s = quasis[0]
.cooked
.clone()
.unwrap_or_else(|| quasis[0].raw.clone());
let mut s = quasis[0].cooked.clone().unwrap();
// See https://github.com/swc-project/swc/issues/1488
//
@ -319,9 +316,9 @@ impl VisitMut for TemplateLiteral {
elems: quasis
.take()
.into_iter()
.map(|elem| {
Lit::Str(elem.cooked.unwrap_or(elem.raw))
.as_arg()
.map(|elem| match elem.cooked {
Some(cooked) => Lit::Str(cooked).as_arg(),
None => undefined(DUMMY_SP).as_arg(),
})
.map(Some)
.collect(),

View File

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