diff --git a/.changeset/regexp-flags-unicode-escape.md b/.changeset/regexp-flags-unicode-escape.md new file mode 100644 index 000000000000..b97208ea1c29 --- /dev/null +++ b/.changeset/regexp-flags-unicode-escape.md @@ -0,0 +1,6 @@ +--- +swc_core: patch +swc_ecma_parser: patch +--- + +fix(es/parser): reject unicode escapes in RegExp flags diff --git a/crates/swc_ecma_parser/src/error.rs b/crates/swc_ecma_parser/src/error.rs index 327936244382..b05226aa6d29 100644 --- a/crates/swc_ecma_parser/src/error.rs +++ b/crates/swc_ecma_parser/src/error.rs @@ -213,6 +213,8 @@ pub enum SyntaxError { DuplicatedRegExpFlags(char), UnknownRegExpFlags, + /// IdentifierPart in RegExp flags must not contain a UnicodeEscapeSequence. + UnicodeEscapeInRegExpFlags, TS1003, TS1005, @@ -591,6 +593,9 @@ impl SyntaxError { format!("Duplicated regular expression flag '{flag}'.").into() } SyntaxError::UnknownRegExpFlags => "Unknown regular expression flags.".into(), + SyntaxError::UnicodeEscapeInRegExpFlags => { + "Regular expression flags cannot contain unicode escapes.".into() + } SyntaxError::TS1003 => "Expected an identifier".into(), SyntaxError::TS1005 => "Expected a semicolon".into(), diff --git a/crates/swc_ecma_parser/src/lexer/mod.rs b/crates/swc_ecma_parser/src/lexer/mod.rs index 35d55a10297e..50fd9fbc4d1a 100644 --- a/crates/swc_ecma_parser/src/lexer/mod.rs +++ b/crates/swc_ecma_parser/src/lexer/mod.rs @@ -1870,16 +1870,21 @@ impl<'a> Lexer<'a> { self.bump(1); // '/' // Spec says "It is a Syntax Error if IdentifierPart contains a Unicode escape - // sequence." TODO: check for escape - - // Need to use `read_word` because '\uXXXX' sequences are allowed - // here (don't ask). - // let flags_start = self.cur_pos(); + // sequence." + // Need to use `read_word` because '\uXXXX' sequences are accepted by the + // scanner — we still reject them as an early error below. + // Use `cur_as_char` so non-ASCII IdentifierPart flags (e.g. `/a/π\u0067`) + // are scanned; `cur()` only yields the first UTF-8 byte and would miss them. let flags = { - match self.cur() { - Some(c) if c.is_ident_start() => self - .read_word_as_str_with() - .map(|(s, _)| Some(self.atom(s))), + match self.cur_as_char() { + Some(c) if c == '\\' || c.is_ident_part() => { + let (s, has_escape) = self.read_word_as_str_with()?; + if has_escape { + let span = self.span(start); + self.emit_error_span(span, SyntaxError::UnicodeEscapeInRegExpFlags); + } + Ok(Some(self.atom(s))) + } _ => Ok(None), } }?; diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/escaped-among-ascii/input.js b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/escaped-among-ascii/input.js new file mode 100644 index 000000000000..cd618fa329a8 --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/escaped-among-ascii/input.js @@ -0,0 +1,3 @@ +/pattern/g\u0069m; +/pattern/\u0067im; +/pattern/gi\u006d; diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/escaped-among-ascii/input.js.swc-stderr b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/escaped-among-ascii/input.js.swc-stderr new file mode 100644 index 000000000000..176feb8c97c6 --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/escaped-among-ascii/input.js.swc-stderr @@ -0,0 +1,19 @@ + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/escaped-among-ascii/input.js:1:1] + 1 | /pattern/g\u0069m; + : ^^^^^^^^^^^^^^^^^ + 2 | /pattern/\u0067im; + `---- + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/escaped-among-ascii/input.js:2:1] + 1 | /pattern/g\u0069m; + 2 | /pattern/\u0067im; + : ^^^^^^^^^^^^^^^^^ + 3 | /pattern/gi\u006d; + `---- + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/escaped-among-ascii/input.js:3:1] + 2 | /pattern/\u0067im; + 3 | /pattern/gi\u006d; + : ^^^^^^^^^^^^^^^^^ + `---- diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/mixed-valid-invalid/input.js b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/mixed-valid-invalid/input.js new file mode 100644 index 000000000000..bbdb901b9e2a --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/mixed-valid-invalid/input.js @@ -0,0 +1,4 @@ +/ok/gimuy; +/bad/g\u0069m; +/also/\u0067; +/leading/\u{79}; diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/mixed-valid-invalid/input.js.swc-stderr b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/mixed-valid-invalid/input.js.swc-stderr new file mode 100644 index 000000000000..d4d31566dab9 --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/mixed-valid-invalid/input.js.swc-stderr @@ -0,0 +1,20 @@ + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/mixed-valid-invalid/input.js:2:1] + 1 | /ok/gimuy; + 2 | /bad/g\u0069m; + : ^^^^^^^^^^^^^ + 3 | /also/\u0067; + `---- + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/mixed-valid-invalid/input.js:3:1] + 2 | /bad/g\u0069m; + 3 | /also/\u0067; + : ^^^^^^^^^^^^ + 4 | /leading/\u{79}; + `---- + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/mixed-valid-invalid/input.js:4:1] + 3 | /also/\u0067; + 4 | /leading/\u{79}; + : ^^^^^^^^^^^^^^^ + `---- diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/non-ascii-start/input.js b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/non-ascii-start/input.js new file mode 100644 index 000000000000..58a862e22d6f --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/non-ascii-start/input.js @@ -0,0 +1 @@ +/a/π\u0067; diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/non-ascii-start/input.js.swc-stderr b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/non-ascii-start/input.js.swc-stderr new file mode 100644 index 000000000000..142cc49c2b94 --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/non-ascii-start/input.js.swc-stderr @@ -0,0 +1,10 @@ + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/non-ascii-start/input.js:1:1] + 1 | /a/π\u0067; + : ^^^^^^^^^^ + `---- + x Unknown regular expression flags. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/non-ascii-start/input.js:1:1] + 1 | /a/π\u0067; + : ^^^^^^^^^^ + `---- diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/only-escaped-flag/input.js b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/only-escaped-flag/input.js new file mode 100644 index 000000000000..2bacbb69edb9 --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/only-escaped-flag/input.js @@ -0,0 +1,2 @@ +/x/\u0067; +/x/\u{0069}; diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/only-escaped-flag/input.js.swc-stderr b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/only-escaped-flag/input.js.swc-stderr new file mode 100644 index 000000000000..c30f13ed137d --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/only-escaped-flag/input.js.swc-stderr @@ -0,0 +1,12 @@ + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/only-escaped-flag/input.js:1:1] + 1 | /x/\u0067; + : ^^^^^^^^^ + 2 | /x/\u{0069}; + `---- + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/only-escaped-flag/input.js:2:1] + 1 | /x/\u0067; + 2 | /x/\u{0069}; + : ^^^^^^^^^^^ + `---- diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-brace-escape/input.js b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-brace-escape/input.js new file mode 100644 index 000000000000..18d7999c83fd --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-brace-escape/input.js @@ -0,0 +1,3 @@ +/a/\u{69}; +/foo/g\u{0069}; +/\w+/\u{006d}\u{0073}; diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-brace-escape/input.js.swc-stderr b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-brace-escape/input.js.swc-stderr new file mode 100644 index 000000000000..bbf97b8c50e3 --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-brace-escape/input.js.swc-stderr @@ -0,0 +1,19 @@ + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/u-brace-escape/input.js:1:1] + 1 | /a/\u{69}; + : ^^^^^^^^^ + 2 | /foo/g\u{0069}; + `---- + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/u-brace-escape/input.js:2:1] + 1 | /a/\u{69}; + 2 | /foo/g\u{0069}; + : ^^^^^^^^^^^^^^ + 3 | /\w+/\u{006d}\u{0073}; + `---- + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/u-brace-escape/input.js:3:1] + 2 | /foo/g\u{0069}; + 3 | /\w+/\u{006d}\u{0073}; + : ^^^^^^^^^^^^^^^^^^^^^ + `---- diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-escape/input.js b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-escape/input.js new file mode 100644 index 000000000000..5513ef7adfbd --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-escape/input.js @@ -0,0 +1,2 @@ +/a/g\u0069; +/\w+/m\u0073; diff --git a/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-escape/input.js.swc-stderr b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-escape/input.js.swc-stderr new file mode 100644 index 000000000000..bd30b641f1fc --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/regexp-flags-unicode-escape/u-escape/input.js.swc-stderr @@ -0,0 +1,12 @@ + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/u-escape/input.js:1:1] + 1 | /a/g\u0069; + : ^^^^^^^^^^ + 2 | /\w+/m\u0073; + `---- + x Regular expression flags cannot contain unicode escapes. + ,-[$DIR/tests/errors/regexp-flags-unicode-escape/u-escape/input.js:2:1] + 1 | /a/g\u0069; + 2 | /\w+/m\u0073; + : ^^^^^^^^^^^^ + `---- diff --git a/crates/swc_ecma_parser/tests/test262-error-references/fail/6a96389a0cce57e9.js.swc-stderr b/crates/swc_ecma_parser/tests/test262-error-references/fail/6a96389a0cce57e9.js.swc-stderr index bfdbd1d76028..fcac49fb42d4 100644 --- a/crates/swc_ecma_parser/tests/test262-error-references/fail/6a96389a0cce57e9.js.swc-stderr +++ b/crates/swc_ecma_parser/tests/test262-error-references/fail/6a96389a0cce57e9.js.swc-stderr @@ -3,8 +3,3 @@ 1 | var x = /[a-z]/\ux : ^ `---- - x Expected a semicolon - ,-[$DIR/tests/test262-parser/fail/6a96389a0cce57e9.js:1:1] - 1 | var x = /[a-z]/\ux - : ^^ - `---- diff --git a/crates/swc_ecma_parser/tests/test262-error-references/fail/94535dc25ef762ee.js.swc-stderr b/crates/swc_ecma_parser/tests/test262-error-references/fail/94535dc25ef762ee.js.swc-stderr index 0f20beef763f..872bcfb096aa 100644 --- a/crates/swc_ecma_parser/tests/test262-error-references/fail/94535dc25ef762ee.js.swc-stderr +++ b/crates/swc_ecma_parser/tests/test262-error-references/fail/94535dc25ef762ee.js.swc-stderr @@ -3,8 +3,3 @@ 1 | var x = /[a-z]/\\ux : ^ `---- - x Expected a semicolon - ,-[$DIR/tests/test262-parser/fail/94535dc25ef762ee.js:1:1] - 1 | var x = /[a-z]/\\ux - : ^ - `---- diff --git a/crates/swc_ecma_parser/tests/test262-error-references/fail/e7087ec92f0f3c44.js.swc-stderr b/crates/swc_ecma_parser/tests/test262-error-references/fail/e7087ec92f0f3c44.js.swc-stderr index 6e453e9c59a3..e627bf9d0d53 100644 --- a/crates/swc_ecma_parser/tests/test262-error-references/fail/e7087ec92f0f3c44.js.swc-stderr +++ b/crates/swc_ecma_parser/tests/test262-error-references/fail/e7087ec92f0f3c44.js.swc-stderr @@ -3,8 +3,3 @@ 1 | var x = /[P QR]/\\u0067 : ^ `---- - x Expected a semicolon - ,-[$DIR/tests/test262-parser/fail/e7087ec92f0f3c44.js:1:1] - 1 | var x = /[P QR]/\\u0067 - : ^ - `----