Skip to content

Gate ES2025 regex syntax behind target - #4877

Open
Dayong Lee (dayongkr) wants to merge 6 commits into
microsoft:mainfrom
dayongkr:fix/gate-es2025-regex-syntax
Open

Gate ES2025 regex syntax behind target#4877
Dayong Lee (dayongkr) wants to merge 6 commits into
microsoft:mainfrom
dayongkr:fix/gate-es2025-regex-syntax

Conversation

@dayongkr

@dayongkr Dayong Lee (dayongkr) commented Aug 11, 2026

Copy link
Copy Markdown

Fixes microsoft/TypeScript#63682

Analysis

Pattern modifiers and duplicate named capturing groups are parsed and validated, but never checked against target:

// target: es2022
/[\p{ASCII}]/v                          // TS1501
/(?i:abc)/                              // no error
/(?<y>\d{4})-\d{2}|\d{2}\/(?<y>\d{4})/  // no error

Both are a SyntaxError before ES2025. The v flag and named capturing groups already have version gates. These two were Stage 3 with no edition assigned when regex body validation was written, so they never got one.

Fix

Two checks in internal/scanner/regexp.go:

  • Modifiers: gate in scanDisjunction when the group actually consumed modifier characters. Comparing against flagsStart keeps plain (?: out.
  • Duplicate names: gate in scanGroupName when the name is already in groupSpecifiers but not in scope for any enclosing alternative. Duplicates inside one alternative still report only TS1515.

New messages go in extraDiagnosticMessages.json since the submodule's json is not editable here. 18062 and 18063 are unused upstream.

Test: regularExpressionES2025Syntax.ts at es2022 and es2025, including the cases that must stay clean ((?:, non-duplicated names).

Updated after merging main

#4881 landed in the meantime and fixes the nested scoping Copilot flagged below, so /(?:(?<a>x)|y)(?<a>z)/ now reports TS1515 and never reaches the ES2025 gate. Only the baselines needed updating.

Copilot Checklist

I successfully ran these commands at the end of my session, and they completed without error:

  • npx hereby build
  • npx hereby test
  • npx hereby lint
  • npx hereby format

Lint fails only in internal/nativepath/realpath_darwin_test.go, which is identical to main on this branch. ./internal/scanner/... and ./internal/diagnostics/... report 0 issues.

Disclosure: written with AI assistance (Claude Code). I read the change and will handle review myself.

Pattern modifiers and duplicate named capturing groups are parsed and
validated by the scanner but never checked against target, so they
compile clean at every target even though both are a SyntaxError on
engines that predate ES2025. Report them below ES2025, matching how the
v flag and named capturing groups are already gated.

Fixes microsoft/TypeScript#63682
Copilot AI balanced review requested due to automatic review settings August 11, 2026 15:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds ES2025 target checks for regular-expression pattern modifiers and duplicate named capture groups.

Changes:

  • Adds scanner target diagnostics.
  • Registers diagnostic codes TS18062 and TS18063.
  • Adds compiler tests and baselines for ES2022 and ES2025.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
internal/scanner/regexp.go Implements target checks.
internal/diagnostics/extraDiagnosticMessages.json Defines new diagnostics.
internal/diagnostics/diagnostics_generated.go Adds generated diagnostic mappings.
testdata/tests/cases/compiler/regularExpressionES2025Syntax.ts Adds target-dependent tests.
testdata/baselines/reference/compiler/regularExpressionES2025Syntax(target=es2022).errors.txt Records pre-ES2025 errors.
testdata/baselines/reference/compiler/regularExpressionES2025Syntax(target=es2022).symbols Records ES2022 symbols.
testdata/baselines/reference/compiler/regularExpressionES2025Syntax(target=es2022).types Records ES2022 types.
testdata/baselines/reference/compiler/regularExpressionES2025Syntax(target=es2025).errors.txt Records ES2025 errors.
testdata/baselines/reference/compiler/regularExpressionES2025Syntax(target=es2025).symbols Records ES2025 symbols.
testdata/baselines/reference/compiler/regularExpressionES2025Syntax(target=es2025).types Records ES2025 types.
testdata/baselines/reference/submodule/compiler/regularExpressionScanning(target=es2015).errors.txt Updates submodule baseline.
testdata/baselines/reference/submodule/compiler/regularExpressionScanning(target=es2015).errors.txt.diff Records upstream divergence.
Files not reviewed (1)
  • internal/diagnostics/diagnostics_generated.go: Generated file

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/scanner/regexp.go Outdated
Comment on lines +491 to +494
// The name is not in scope for any enclosing alternative, so a previous definition of it
// can only have come from a mutually exclusive alternative.
if p.groupSpecifiers[p.scanner.tokenValue] && p.scanner.languageVersion() < core.ScriptTargetES2025 {
p.error(diagnostics.Duplicate_named_capturing_groups_are_only_available_when_targeting_0_or_later, p.scanner.tokenStart, p.pos()-p.scanner.tokenStart, strings.ToLower(core.ScriptTargetES2025.String()))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, but I would rather fix it separately.

The missing TS1515 predates this change. scanDisjunction drops an alternative's scope once the alternative ends, in Strada too (topNamedCapturingGroupsScope = namedCapturingGroupsScopeStack.pop()), so the check is never reached for that pattern:

  • tsc 5.9.3 on /(?:(?<a>x)|y)(?<a>z)/: no error. It does report TS1515 for /(?<a>x)(?<a>y)/, so the rule itself works.
  • typescript@7.0.2 without this patch: no error.

What this PR does add is the wrong message for that pattern below ES2025: TS18063 where TS1515 belongs. It is still reported as an error, so nothing valid starts or stops compiling, but the wording points at the wrong cause.

Fixing the scope tracking means 7.x reports an error 6.0 does not, which is the same divergence question as in the description, so it seems better as its own PR with its own test matrix. Happy to open that once this lands.

@dayongkr

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

microsoft#4881 registered this test's diff in submoduleAccepted.txt, so the diff
belongs in submoduleAccepted rather than in submodule.
@dayongkr
Dayong Lee (dayongkr) requested a balanced review from Copilot August 15, 2026 11:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/diagnostics/diagnostics_generated.go: Generated file
Suppressed comments (2)

internal/scanner/regexp.go:278

  • The comment contradicts the condition: p.pos() != flagsStart indicates modifier characters were consumed (i.e., this is not a plain (?: group). Update the comment to reflect that this branch is for modifier-group syntax and (?: is the zero-consumption case.
					// A group that consumed no modifier characters is a plain non-capturing group `(?:`.
					if p.pos() != flagsStart && p.scanner.languageVersion() < core.ScriptTargetES2025 {
						p.error(diagnostics.Regular_expression_pattern_modifiers_are_only_available_when_targeting_0_or_later, flagsStart, p.pos()-flagsStart, strings.ToLower(core.ScriptTargetES2025.String()))
					}

internal/scanner/regexp.go:512

  • This emits TS18063 even when named capturing groups themselves are not supported (i.e., targets < ES2018), which can add redundant/cascading errors (the user already gets the 'Named capturing groups are only available...' diagnostic). Consider gating TS18063 to only fire when languageVersion >= ES2018 && languageVersion < ES2025 so the duplicate-name message only appears when the base feature is available.
		if p.groupSpecifiers[p.scanner.tokenValue] && p.scanner.languageVersion() < core.ScriptTargetES2025 {
			p.error(diagnostics.Duplicate_named_capturing_groups_are_only_available_when_targeting_0_or_later, p.scanner.tokenStart, p.pos()-p.scanner.tokenStart, strings.ToLower(core.ScriptTargetES2025.String()))
		}

Below ES2018 the group itself already reports TS1503, so TS18063 only
stacked a second error on the same construct. Adds es2017 to the test
matrix to pin that.
@dayongkr

Dayong Lee (dayongkr) commented Aug 15, 2026

Copy link
Copy Markdown
Author

Ryan Cavanaugh (@RyanCavanaugh) saw #4881 land, thanks. Merged it in, and the nested case Copilot flagged below is gone.

Also narrowed the new duplicate diagnostic: below ES2018 the group already reports TS1503, so TS18063 now only fires from ES2018 up to below ES2025. Added es2017 to the test matrix to pin it.

Whenever you get a chance to look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ES2025 regex syntax (duplicate named groups, pattern modifiers) is not gated by target

3 participants