diff --git a/acorn/src/acorn.d.ts b/acorn/src/acorn.d.ts index cf72b3704..4f7b383a3 100644 --- a/acorn/src/acorn.d.ts +++ b/acorn/src/acorn.d.ts @@ -573,7 +573,7 @@ export function tokenizer(input: string, options: Options): { [Symbol.iterator](): Iterator } -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 { /** diff --git a/acorn/src/regexp.js b/acorn/src/regexp.js index 71babf7ae..c44ea7b84 100644 --- a/acorn/src/regexp.js +++ b/acorn/src/regexp.js @@ -23,6 +23,7 @@ export class RegExpValidationState { this.numCapturingParens = 0 this.maxBackReference = 0 this.groupNames = [] + this.groupNamesToAddToUpperScope = [] this.backReferenceNames = [] } @@ -169,6 +170,7 @@ pp.regexp_pattern = function(state) { state.numCapturingParens = 0 state.maxBackReference = 0 state.groupNames.length = 0 + state.groupNamesToAddToUpperScope.length = 0 state.backReferenceNames.length = 0 this.regexp_disjunction(state) @@ -194,11 +196,28 @@ pp.regexp_pattern = function(state) { // https://www.ecma-international.org/ecma-262/8.0/#prod-Disjunction pp.regexp_disjunction = function(state) { + let groupNamesToAddToUpperUpperScope + if (this.options.ecmaVersion >= 16) { + groupNamesToAddToUpperUpperScope = state.groupNamesToAddToUpperScope + // Clear groupNamesToAddToUpperScope to store the groupName added in this Disjunction. + state.groupNamesToAddToUpperScope = [] + } + this.regexp_alternative(state) while (state.eat(0x7C /* | */)) { this.regexp_alternative(state) } + if (this.options.ecmaVersion >= 16) { + for (const groupName of state.groupNamesToAddToUpperScope) { + // Adds the groupName added in Disjunction to groupNames. + state.groupNames.push(groupName) + // Adds the groupName added in Disjunction to the upper scope. + groupNamesToAddToUpperUpperScope.push(groupName) + } + state.groupNamesToAddToUpperScope = groupNamesToAddToUpperUpperScope + } + // Make the same message as V8. if (this.regexp_eatQuantifier(state, true)) { state.raise("Nothing to repeat") @@ -210,8 +229,24 @@ pp.regexp_disjunction = function(state) { // https://www.ecma-international.org/ecma-262/8.0/#prod-Alternative pp.regexp_alternative = function(state) { + let upperGroupNames + if (this.options.ecmaVersion >= 16) { + upperGroupNames = [...state.groupNames] + } + while (state.pos < state.source.length && this.regexp_eatTerm(state)) ; + + if (this.options.ecmaVersion >= 16) { + // Adds the groupName added in Alternative to the upper scope. + for (const groupName of state.groupNames) { + if (upperGroupNames.indexOf(groupName) === -1) { + state.groupNamesToAddToUpperScope.push(groupName) + } + } + // Reverts the groupNames so that the next adjacent Alternative does not report duplicates. + state.groupNames = upperGroupNames + } } // https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-Term diff --git a/bin/test262.unsupported-features b/bin/test262.unsupported-features index 5ab02064c..383077264 100644 --- a/bin/test262.unsupported-features +++ b/bin/test262.unsupported-features @@ -1,3 +1,2 @@ decorators import-assertions -regexp-duplicate-named-groups diff --git a/test/run.js b/test/run.js index 05087c483..0587bc571 100644 --- a/test/run.js +++ b/test/run.js @@ -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"); diff --git a/test/tests-regexp-2025.js b/test/tests-regexp-2025.js new file mode 100644 index 000000000..6eedc49e9 --- /dev/null +++ b/test/tests-regexp-2025.js @@ -0,0 +1,18 @@ +if (typeof exports !== "undefined") { + var test = require("./driver.js").test + var testFail = require("./driver.js").testFail +} + +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 }) +test("/(?:(?a)|(?b))\\k/", {}, { ecmaVersion: 2025 }) +testFail("/(?:(?a)|(?b))\\k/", "Invalid regular expression: /(?:(?a)|(?b))\\k/: Duplicate capture group name (1:1)", { ecmaVersion: 2024 }) +testFail("/(?:(?a)(?b))\\k/", "Invalid regular expression: /(?:(?a)(?b))\\k/: Duplicate capture group name (1:1)", { ecmaVersion: 2025 }) +test("/(?a)(?a)|(?b)(?b)/", {}, { ecmaVersion: 2025 }) +test("/(?a)|(?b)|(?c)/", {}, { ecmaVersion: 2025 }) +test("/(?a)|\\k/", {}, { 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 }) +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 })