Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ES2025 RegExp Modifiers #1324

Merged
merged 2 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions acorn/src/regexp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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) {
Expand Down
1 change: 0 additions & 1 deletion bin/test262.unsupported-features
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
decorators
explicit-resource-management
regexp-modifiers
source-phase-imports
29 changes: 29 additions & 0 deletions test/tests-regexp-2025.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ if (typeof exports !== "undefined") {
var testFail = require("./driver.js").testFail
}

// Duplicate named capture groups
test("/(?<x>a)|(?<x>b)/", {}, {ecmaVersion: 2025})
testFail("/(?<x>a)|(?<x>b)/", "Invalid regular expression: /(?<x>a)|(?<x>b)/: Duplicate capture group name (1:1)", {ecmaVersion: 2024 })
testFail("/(?<x>a)(?<x>b)/", "Invalid regular expression: /(?<x>a)(?<x>b)/: Duplicate capture group name (1:1)", {ecmaVersion: 2025})
Expand All @@ -16,3 +17,31 @@ testFail("/(?<x>a)|(?<x>b)(?<x>c)/", "Invalid regular expression: /(?<x>a)|(?<x>
testFail("/(?:(?<x>a)|(?<x>b))(?<x>c)/", "Invalid regular expression: /(?:(?<x>a)|(?<x>b))(?<x>c)/: Duplicate capture group name (1:1)", {ecmaVersion: 2025})
testFail("/(?<x>a)(?:(?<x>b)|(?<x>c))/", "Invalid regular expression: /(?<x>a)(?:(?<x>b)|(?<x>c))/: Duplicate capture group name (1:1)", {ecmaVersion: 2025})
testFail("/(?:(?:(?<x>a)|(?<x>b))|(?:))(?<x>c)/", "Invalid regular expression: /(?:(?:(?<x>a)|(?<x>b))|(?:))(?<x>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})