Skip to content

Commit

Permalink
Add support for ES2025 RegExp Duplicate named capturing groups
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Apr 14, 2024
1 parent ed4a7a1 commit f03e191
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 3 deletions.
2 changes: 1 addition & 1 deletion acorn/src/acorn.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ export function tokenizer(input: string, options: Options): {
[Symbol.iterator](): Iterator<Token>
}

export type ecmaVersion = 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | "latest"
export type ecmaVersion = 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 | "latest"

export interface Options {
/**
Expand Down
37 changes: 36 additions & 1 deletion acorn/src/regexp.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export class RegExpValidationState {
this.numCapturingParens = 0
this.maxBackReference = 0
this.groupNames = []
this.groupNamesInDisjunction = []
this.groupNamesInAlternative = []
this.backReferenceNames = []
}

Expand Down Expand Up @@ -169,6 +171,8 @@ pp.regexp_pattern = function(state) {
state.numCapturingParens = 0
state.maxBackReference = 0
state.groupNames.length = 0
state.groupNamesInDisjunction.length = 0
state.groupNamesInAlternative.length = 0
state.backReferenceNames.length = 0

this.regexp_disjunction(state)
Expand All @@ -194,11 +198,27 @@ pp.regexp_pattern = function(state) {

// https://www.ecma-international.org/ecma-262/8.0/#prod-Disjunction
pp.regexp_disjunction = function(state) {
let upperGroupNamesInDisjunction
if (this.options.ecmaVersion >= 16) {
upperGroupNamesInDisjunction = state.groupNamesInDisjunction
state.groupNamesInDisjunction = []
}

this.regexp_alternative(state)
while (state.eat(0x7C /* | */)) {
this.regexp_alternative(state)
}

if (this.options.ecmaVersion >= 16) {
// Adds the group name that appears in current Disjunction
// as the group name of the current Alternative and upper Disjunction.
for (const groupName of state.groupNamesInDisjunction) {
upperGroupNamesInDisjunction.push(groupName)
state.groupNamesInAlternative.push(groupName)
}
state.groupNamesInDisjunction = upperGroupNamesInDisjunction
}

// Make the same message as V8.
if (this.regexp_eatQuantifier(state, true)) {
state.raise("Nothing to repeat")
Expand All @@ -210,8 +230,18 @@ pp.regexp_disjunction = function(state) {

// https://www.ecma-international.org/ecma-262/8.0/#prod-Alternative
pp.regexp_alternative = function(state) {
let upperGroupNamesInAlternative
if (this.options.ecmaVersion >= 16) {
upperGroupNamesInAlternative = state.groupNamesInAlternative
state.groupNamesInAlternative = [...state.groupNamesInAlternative]
}

while (state.pos < state.source.length && this.regexp_eatTerm(state))
;

if (this.options.ecmaVersion >= 16) {
state.groupNamesInAlternative = upperGroupNamesInAlternative
}
}

// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-Term
Expand Down Expand Up @@ -448,10 +478,15 @@ pp.regexp_eatExtendedPatternCharacter = function(state) {
pp.regexp_groupSpecifier = function(state) {
if (state.eat(0x3F /* ? */)) {
if (this.regexp_eatGroupName(state)) {
if (state.groupNames.indexOf(state.lastStringValue) !== -1) {
const groupNames = this.options.ecmaVersion >= 16 ? state.groupNamesInAlternative : state.groupNames
if (groupNames.indexOf(state.lastStringValue) !== -1) {
state.raise("Duplicate capture group name")
}
state.groupNames.push(state.lastStringValue)
if (this.options.ecmaVersion >= 16) {
state.groupNamesInAlternative.push(state.lastStringValue)
state.groupNamesInDisjunction.push(state.lastStringValue)
}
return
}
state.raise("Invalid group")
Expand Down
1 change: 0 additions & 1 deletion bin/test262.unsupported-features
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
decorators
import-assertions
regexp-duplicate-named-groups
1 change: 1 addition & 0 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require("./tests-regexp-2020.js");
require("./tests-regexp-2022.js");
require("./tests-regexp-2024.js");
require("./tests-regexp-2025.js");
require("./tests-json-superset.js");
require("./tests-optional-catch-binding.js");
require("./tests-bigint.js");
Expand Down
17 changes: 17 additions & 0 deletions test/tests-regexp-2025.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
if (typeof exports !== "undefined") {
var test = require("./driver.js").test
var testFail = require("./driver.js").testFail
}

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 })
test("/(?:(?<x>a)|(?<x>b))\\k<x>/", {}, { ecmaVersion: 2025 })
testFail("/(?:(?<x>a)|(?<x>b))\\k<x>/", "Invalid regular expression: /(?:(?<x>a)|(?<x>b))\\k<x>/: Duplicate capture group name (1:1)", { ecmaVersion: 2024 })
testFail("/(?:(?<x>a)(?<x>b))\\k<x>/", "Invalid regular expression: /(?:(?<x>a)(?<x>b))\\k<x>/: Duplicate capture group name (1:1)", { ecmaVersion: 2025 })
test("/(?<y>a)(?<x>a)|(?<x>b)(?<y>b)/", {}, { ecmaVersion: 2025 })
test("/(?<x>a)|(?<x>b)|(?<x>c)/", {}, { ecmaVersion: 2025 })
test("/(?<x>a)|\\k<x>/", {}, { 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 })
testFail("/(?:(?:(?<x>a)|(?<x>b))|(?:))(?<x>c)/", "Invalid regular expression: /(?:(?:(?<x>a)|(?<x>b))|(?:))(?<x>c)/: Duplicate capture group name (1:1)", { ecmaVersion: 2025 })

0 comments on commit f03e191

Please sign in to comment.