diff --git a/crates/swc_ecma_minifier/tests/terser/compress/template-string/tagged_call_with_invalid_escape_2/output.js b/crates/swc_ecma_minifier/tests/terser/compress/template-string/tagged_call_with_invalid_escape_2/output.js index 08397782e01..0e7fd3a3442 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/template-string/tagged_call_with_invalid_escape_2/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/template-string/tagged_call_with_invalid_escape_2/output.js @@ -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`); diff --git a/crates/swc_ecma_parser/src/lexer/mod.rs b/crates/swc_ecma_parser/src/lexer/mod.rs index 1f0245b91f8..90a21804936 100644 --- a/crates/swc_ecma_parser/src/lexer/mod.rs +++ b/crates/swc_ecma_parser/src/lexer/mod.rs @@ -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); diff --git a/crates/swc_ecma_parser/src/lexer/state.rs b/crates/swc_ecma_parser/src/lexer/state.rs index 1dbdb40d2ea..67061f85726 100644 --- a/crates/swc_ecma_parser/src/lexer/state.rs +++ b/crates/swc_ecma_parser/src/lexer/state.rs @@ -722,15 +722,6 @@ pub(crate) fn lex_tokens(syntax: Syntax, s: &'static str) -> Vec { .unwrap() } -#[cfg(test)] -pub(crate) fn lex_tokens_with_target( - syntax: Syntax, - target: EsVersion, - s: &'static str, -) -> Vec { - 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)] diff --git a/crates/swc_ecma_parser/src/lexer/tests.rs b/crates/swc_ecma_parser/src/lexer/tests.rs index af34611d00d..13d653ba82e 100644 --- a/crates/swc_ecma_parser/src/lexer/tests.rs +++ b/crates/swc_ecma_parser/src/lexer/tests.rs @@ -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) -> 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: "".into(), - cooked: Some("".into()), + cooked: Ok("".into()), has_escape: false, }, tok!('`') diff --git a/crates/swc_ecma_parser/src/parser/expr.rs b/crates/swc_ecma_parser/src/parser/expr.rs index 49eacd6b78e..f058cbc3ec7 100644 --- a/crates/swc_ecma_parser/src/parser/expr.rs +++ b/crates/swc_ecma_parser/src/parser/expr.rs @@ -332,7 +332,7 @@ impl<'a, I: Tokens> Parser { 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 { } #[allow(clippy::vec_box)] - fn parse_tpl_elements(&mut self) -> PResult<(Vec>, Vec)> { + fn parse_tpl_elements( + &mut self, + is_tagged_tpl: bool, + ) -> PResult<(Vec>, Vec)> { 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 { 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 { 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 { }) } - pub(super) fn parse_tpl(&mut self) -> PResult { + pub(super) fn parse_tpl(&mut self, is_tagged_tpl: bool) -> PResult { 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 { }) } - pub(super) fn parse_tpl_element(&mut self) -> PResult { + pub(super) fn parse_tpl_element(&mut self, is_tagged_tpl: bool) -> PResult { let start = cur_pos!(self); let (raw, cooked) = match *cur!(self, true)? { @@ -914,24 +917,43 @@ impl<'a, I: Tokens> Parser { 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"), diff --git a/crates/swc_ecma_parser/src/parser/typescript.rs b/crates/swc_ecma_parser/src/parser/typescript.rs index f72f3ed9c38..9b60f65795c 100644 --- a/crates/swc_ecma_parser/src/parser/typescript.rs +++ b/crates/swc_ecma_parser/src/parser/typescript.rs @@ -1771,7 +1771,7 @@ impl Parser { 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 Parser { 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); } diff --git a/crates/swc_ecma_parser/src/token.rs b/crates/swc_ecma_parser/src/token.rs index 6f0a1e4c694..20d24d49ed1 100644 --- a/crates/swc_ecma_parser/src/token.rs +++ b/crates/swc_ecma_parser/src/token.rs @@ -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, + cooked: LexResult, has_escape: bool, }, /// ':' diff --git a/crates/swc_ecma_transforms_compat/src/es2015/template_literal.rs b/crates/swc_ecma_transforms_compat/src/es2015/template_literal.rs index 2bd7872c9c0..62f0b06789a 100644 --- a/crates/swc_ecma_transforms_compat/src/es2015/template_literal.rs +++ b/crates/swc_ecma_transforms_compat/src/es2015/template_literal.rs @@ -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(), diff --git a/crates/swc_ecma_transforms_compat/tests/es2015_template_literals.rs b/crates/swc_ecma_transforms_compat/tests/es2015_template_literals.rs index 0d147a58306..8e48d44df12 100644 --- a/crates/swc_ecma_transforms_compat/tests/es2015_template_literals.rs +++ b/crates/swc_ecma_transforms_compat/tests/es2015_template_literals.rs @@ -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