From ac3bd47e1841f9585cd8a64e97d6fcc56923a088 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Sun, 27 Oct 2024 20:16:22 +0900 Subject: [PATCH] Add support for ES2025 RegExp Modifiers FEATURE: Support ES2025 RegExp modifiers. Closes https://github.com/acornjs/acorn/issues/1323 --- acorn/src/regexp.js | 56 +++++++++++++++++++++++++++++--- bin/test262.unsupported-features | 1 - test/tests-regexp-2025.js | 29 +++++++++++++++++ 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/acorn/src/regexp.js b/acorn/src/regexp.js index 65e5dc019..fe91f941c 100644 --- a/acorn/src/regexp.js +++ b/acorn/src/regexp.js @@ -378,12 +378,41 @@ pp.regexp_eatReverseSolidusAtomEscape = function(state) { pp.regexp_eatUncapturingGroup = function(state) { const start = state.pos if (state.eat(0x28 /* ( */)) { - if (state.eat(0x3F /* ? */) && state.eat(0x3A /* : */)) { - this.regexp_disjunction(state) - if (state.eat(0x29 /* ) */)) { - return true + if (state.eat(0x3F /* ? */)) { + if (this.options.ecmaVersion >= 16) { + const addModifiers = this.regexp_eatModifiers(state) + const hasHyphen = state.eat(0x2D /* - */) + if (addModifiers || hasHyphen) { + for (let i = 0; i < addModifiers.length; i++) { + const modifier = addModifiers.charAt(i) + if (addModifiers.indexOf(modifier, i + 1) > -1) { + state.raise("Duplicate regular expression modifiers") + } + } + if (hasHyphen) { + const removeModifiers = this.regexp_eatModifiers(state) + if (!addModifiers && !removeModifiers && state.current() === 0x3A /* : */) { + state.raise("Invalid regular expression modifiers") + } + for (let i = 0; i < removeModifiers.length; i++) { + const modifier = removeModifiers.charAt(i) + if ( + removeModifiers.indexOf(modifier, i + 1) > -1 || + addModifiers.indexOf(modifier) > -1 + ) { + state.raise("Duplicate regular expression modifiers") + } + } + } + } + } + if (state.eat(0x3A /* : */)) { + this.regexp_disjunction(state) + if (state.eat(0x29 /* ) */)) { + return true + } + state.raise("Unterminated group") } - state.raise("Unterminated group") } state.pos = start } @@ -405,6 +434,23 @@ pp.regexp_eatCapturingGroup = function(state) { } return false } +// RegularExpressionModifiers :: +// [empty] +// RegularExpressionModifiers RegularExpressionModifier +pp.regexp_eatModifiers = function(state) { + let modifiers = "" + let ch = 0 + while ((ch = state.current()) !== -1 && isRegularExpressionModifier(ch)) { + modifiers += codePointToString(ch) + state.advance() + } + return modifiers +} +// RegularExpressionModifier :: one of +// `i` `m` `s` +function isRegularExpressionModifier(ch) { + return ch === 0x69 /* i */ || ch === 0x6d /* m */ || ch === 0x73 /* s */ +} // https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-ExtendedAtom pp.regexp_eatExtendedAtom = function(state) { diff --git a/bin/test262.unsupported-features b/bin/test262.unsupported-features index 3eb8b1525..8a8d9c5ba 100644 --- a/bin/test262.unsupported-features +++ b/bin/test262.unsupported-features @@ -1,4 +1,3 @@ decorators explicit-resource-management -regexp-modifiers source-phase-imports diff --git a/test/tests-regexp-2025.js b/test/tests-regexp-2025.js index 3929ceb51..995b90108 100644 --- a/test/tests-regexp-2025.js +++ b/test/tests-regexp-2025.js @@ -3,6 +3,7 @@ if (typeof exports !== "undefined") { var testFail = require("./driver.js").testFail } +// Duplicate named capture groups test("/(?a)|(?b)/", {}, {ecmaVersion: 2025}) testFail("/(?a)|(?b)/", "Invalid regular expression: /(?a)|(?b)/: Duplicate capture group name (1:1)", {ecmaVersion: 2024 }) testFail("/(?a)(?b)/", "Invalid regular expression: /(?a)(?b)/: Duplicate capture group name (1:1)", {ecmaVersion: 2025}) @@ -16,3 +17,31 @@ testFail("/(?a)|(?b)(?c)/", "Invalid regular expression: /(?a)|(? testFail("/(?:(?a)|(?b))(?c)/", "Invalid regular expression: /(?:(?a)|(?b))(?c)/: Duplicate capture group name (1:1)", {ecmaVersion: 2025}) testFail("/(?a)(?:(?b)|(?c))/", "Invalid regular expression: /(?a)(?:(?b)|(?c))/: Duplicate capture group name (1:1)", {ecmaVersion: 2025}) testFail("/(?:(?:(?a)|(?b))|(?:))(?c)/", "Invalid regular expression: /(?:(?:(?a)|(?b))|(?:))(?c)/: Duplicate capture group name (1:1)", {ecmaVersion: 2025}) + +// Modifiers +test("/(?i-m:p)?/", {}, {ecmaVersion: 2025}) +test("/(?i-m:p)?/u", {}, {ecmaVersion: 2025}) +test("/(?ims:p)?/", {}, {ecmaVersion: 2025}) +test("/(?ims-:p)?/", {}, {ecmaVersion: 2025}) +test("/(?-ims:p)?/", {}, {ecmaVersion: 2025}) +test("/(?:no modifiers)?/", {}, {ecmaVersion: 2025}) +// In ES2024 +testFail("/(?i-m:p)?/", "Invalid regular expression: /(?i-m:p)?/: Invalid group (1:1)", {ecmaVersion: 2024}) +testFail("/(?ims:p)?/", "Invalid regular expression: /(?ims:p)?/: Invalid group (1:1)", {ecmaVersion: 2024}) +testFail("/(?ims-:p)?/", "Invalid regular expression: /(?ims-:p)?/: Invalid group (1:1)", {ecmaVersion: 2024}) +testFail("/(?-ims:p)?/", "Invalid regular expression: /(?-ims:p)?/: Invalid group (1:1)", {ecmaVersion: 2024}) +// It is a Syntax Error if the first modifiers and the second modifiers are both empty. +testFail("/(?-:p)?/", "Invalid regular expression: /(?-:p)?/: Invalid regular expression modifiers (1:1)", {ecmaVersion: 2025}) +// It is a Syntax Error if the first modifiers contains the same code point more than once. +testFail("/(?ii:p)?/", "Invalid regular expression: /(?ii:p)?/: Duplicate regular expression modifiers (1:1)", {ecmaVersion: 2025}) +// It is a Syntax Error if the second modifiers contains the same code point more than once. +testFail("/(?-ii:p)?/", "Invalid regular expression: /(?-ii:p)?/: Duplicate regular expression modifiers (1:1)", {ecmaVersion: 2025}) +testFail("/(?i-mm:p)?/", "Invalid regular expression: /(?i-mm:p)?/: Duplicate regular expression modifiers (1:1)", {ecmaVersion: 2025}) +// It is a Syntax Error if any code point in the first modifiers is also contained in the second modifiers. +testFail("/(?i-i:p)?/", "Invalid regular expression: /(?i-i:p)?/: Duplicate regular expression modifiers (1:1)", {ecmaVersion: 2025}) +// Not modifiers +testFail("/(?u:p)?/", "Invalid regular expression: /(?u:p)?/: Invalid group (1:1)", {ecmaVersion: 2025}) +testFail("/(?u-:p)?/", "Invalid regular expression: /(?u-:p)?/: Invalid group (1:1)", {ecmaVersion: 2025}) +testFail("/(?u-i:p)?/", "Invalid regular expression: /(?u-i:p)?/: Invalid group (1:1)", {ecmaVersion: 2025}) +testFail("/(?-u:p)?/", "Invalid regular expression: /(?-u:p)?/: Invalid group (1:1)", {ecmaVersion: 2025}) +testFail("/(?i-u:p)?/", "Invalid regular expression: /(?i-u:p)?/: Invalid group (1:1)", {ecmaVersion: 2025})