Skip to content

Commit ac3bd47

Browse files
authored
Add support for ES2025 RegExp Modifiers
FEATURE: Support ES2025 RegExp modifiers. Closes #1323
1 parent f55d97c commit ac3bd47

File tree

3 files changed

+80
-6
lines changed

3 files changed

+80
-6
lines changed

Diff for: acorn/src/regexp.js

+51-5
Original file line numberDiff line numberDiff line change
@@ -378,12 +378,41 @@ pp.regexp_eatReverseSolidusAtomEscape = function(state) {
378378
pp.regexp_eatUncapturingGroup = function(state) {
379379
const start = state.pos
380380
if (state.eat(0x28 /* ( */)) {
381-
if (state.eat(0x3F /* ? */) && state.eat(0x3A /* : */)) {
382-
this.regexp_disjunction(state)
383-
if (state.eat(0x29 /* ) */)) {
384-
return true
381+
if (state.eat(0x3F /* ? */)) {
382+
if (this.options.ecmaVersion >= 16) {
383+
const addModifiers = this.regexp_eatModifiers(state)
384+
const hasHyphen = state.eat(0x2D /* - */)
385+
if (addModifiers || hasHyphen) {
386+
for (let i = 0; i < addModifiers.length; i++) {
387+
const modifier = addModifiers.charAt(i)
388+
if (addModifiers.indexOf(modifier, i + 1) > -1) {
389+
state.raise("Duplicate regular expression modifiers")
390+
}
391+
}
392+
if (hasHyphen) {
393+
const removeModifiers = this.regexp_eatModifiers(state)
394+
if (!addModifiers && !removeModifiers && state.current() === 0x3A /* : */) {
395+
state.raise("Invalid regular expression modifiers")
396+
}
397+
for (let i = 0; i < removeModifiers.length; i++) {
398+
const modifier = removeModifiers.charAt(i)
399+
if (
400+
removeModifiers.indexOf(modifier, i + 1) > -1 ||
401+
addModifiers.indexOf(modifier) > -1
402+
) {
403+
state.raise("Duplicate regular expression modifiers")
404+
}
405+
}
406+
}
407+
}
408+
}
409+
if (state.eat(0x3A /* : */)) {
410+
this.regexp_disjunction(state)
411+
if (state.eat(0x29 /* ) */)) {
412+
return true
413+
}
414+
state.raise("Unterminated group")
385415
}
386-
state.raise("Unterminated group")
387416
}
388417
state.pos = start
389418
}
@@ -405,6 +434,23 @@ pp.regexp_eatCapturingGroup = function(state) {
405434
}
406435
return false
407436
}
437+
// RegularExpressionModifiers ::
438+
// [empty]
439+
// RegularExpressionModifiers RegularExpressionModifier
440+
pp.regexp_eatModifiers = function(state) {
441+
let modifiers = ""
442+
let ch = 0
443+
while ((ch = state.current()) !== -1 && isRegularExpressionModifier(ch)) {
444+
modifiers += codePointToString(ch)
445+
state.advance()
446+
}
447+
return modifiers
448+
}
449+
// RegularExpressionModifier :: one of
450+
// `i` `m` `s`
451+
function isRegularExpressionModifier(ch) {
452+
return ch === 0x69 /* i */ || ch === 0x6d /* m */ || ch === 0x73 /* s */
453+
}
408454

409455
// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-ExtendedAtom
410456
pp.regexp_eatExtendedAtom = function(state) {

Diff for: bin/test262.unsupported-features

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
decorators
22
explicit-resource-management
3-
regexp-modifiers
43
source-phase-imports

Diff for: test/tests-regexp-2025.js

+29
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ if (typeof exports !== "undefined") {
33
var testFail = require("./driver.js").testFail
44
}
55

6+
// Duplicate named capture groups
67
test("/(?<x>a)|(?<x>b)/", {}, {ecmaVersion: 2025})
78
testFail("/(?<x>a)|(?<x>b)/", "Invalid regular expression: /(?<x>a)|(?<x>b)/: Duplicate capture group name (1:1)", {ecmaVersion: 2024 })
89
testFail("/(?<x>a)(?<x>b)/", "Invalid regular expression: /(?<x>a)(?<x>b)/: Duplicate capture group name (1:1)", {ecmaVersion: 2025})
@@ -16,3 +17,31 @@ testFail("/(?<x>a)|(?<x>b)(?<x>c)/", "Invalid regular expression: /(?<x>a)|(?<x>
1617
testFail("/(?:(?<x>a)|(?<x>b))(?<x>c)/", "Invalid regular expression: /(?:(?<x>a)|(?<x>b))(?<x>c)/: Duplicate capture group name (1:1)", {ecmaVersion: 2025})
1718
testFail("/(?<x>a)(?:(?<x>b)|(?<x>c))/", "Invalid regular expression: /(?<x>a)(?:(?<x>b)|(?<x>c))/: Duplicate capture group name (1:1)", {ecmaVersion: 2025})
1819
testFail("/(?:(?:(?<x>a)|(?<x>b))|(?:))(?<x>c)/", "Invalid regular expression: /(?:(?:(?<x>a)|(?<x>b))|(?:))(?<x>c)/: Duplicate capture group name (1:1)", {ecmaVersion: 2025})
20+
21+
// Modifiers
22+
test("/(?i-m:p)?/", {}, {ecmaVersion: 2025})
23+
test("/(?i-m:p)?/u", {}, {ecmaVersion: 2025})
24+
test("/(?ims:p)?/", {}, {ecmaVersion: 2025})
25+
test("/(?ims-:p)?/", {}, {ecmaVersion: 2025})
26+
test("/(?-ims:p)?/", {}, {ecmaVersion: 2025})
27+
test("/(?:no modifiers)?/", {}, {ecmaVersion: 2025})
28+
// In ES2024
29+
testFail("/(?i-m:p)?/", "Invalid regular expression: /(?i-m:p)?/: Invalid group (1:1)", {ecmaVersion: 2024})
30+
testFail("/(?ims:p)?/", "Invalid regular expression: /(?ims:p)?/: Invalid group (1:1)", {ecmaVersion: 2024})
31+
testFail("/(?ims-:p)?/", "Invalid regular expression: /(?ims-:p)?/: Invalid group (1:1)", {ecmaVersion: 2024})
32+
testFail("/(?-ims:p)?/", "Invalid regular expression: /(?-ims:p)?/: Invalid group (1:1)", {ecmaVersion: 2024})
33+
// It is a Syntax Error if the first modifiers and the second modifiers are both empty.
34+
testFail("/(?-:p)?/", "Invalid regular expression: /(?-:p)?/: Invalid regular expression modifiers (1:1)", {ecmaVersion: 2025})
35+
// It is a Syntax Error if the first modifiers contains the same code point more than once.
36+
testFail("/(?ii:p)?/", "Invalid regular expression: /(?ii:p)?/: Duplicate regular expression modifiers (1:1)", {ecmaVersion: 2025})
37+
// It is a Syntax Error if the second modifiers contains the same code point more than once.
38+
testFail("/(?-ii:p)?/", "Invalid regular expression: /(?-ii:p)?/: Duplicate regular expression modifiers (1:1)", {ecmaVersion: 2025})
39+
testFail("/(?i-mm:p)?/", "Invalid regular expression: /(?i-mm:p)?/: Duplicate regular expression modifiers (1:1)", {ecmaVersion: 2025})
40+
// It is a Syntax Error if any code point in the first modifiers is also contained in the second modifiers.
41+
testFail("/(?i-i:p)?/", "Invalid regular expression: /(?i-i:p)?/: Duplicate regular expression modifiers (1:1)", {ecmaVersion: 2025})
42+
// Not modifiers
43+
testFail("/(?u:p)?/", "Invalid regular expression: /(?u:p)?/: Invalid group (1:1)", {ecmaVersion: 2025})
44+
testFail("/(?u-:p)?/", "Invalid regular expression: /(?u-:p)?/: Invalid group (1:1)", {ecmaVersion: 2025})
45+
testFail("/(?u-i:p)?/", "Invalid regular expression: /(?u-i:p)?/: Invalid group (1:1)", {ecmaVersion: 2025})
46+
testFail("/(?-u:p)?/", "Invalid regular expression: /(?-u:p)?/: Invalid group (1:1)", {ecmaVersion: 2025})
47+
testFail("/(?i-u:p)?/", "Invalid regular expression: /(?i-u:p)?/: Invalid group (1:1)", {ecmaVersion: 2025})

0 commit comments

Comments
 (0)