mirror of
https://github.com/swc-project/swc.git
synced 2024-12-23 13:51:19 +03:00
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:
parent
a93f1111f9
commit
d8c8641e59
@ -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`);
|
||||
|
@ -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);
|
||||
|
@ -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)]
|
||||
|
@ -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!('`')
|
||||
|
@ -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"),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
/// ':'
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user