Fix TS1515 not reported when duplicate named group is nested inside a group - #4881
Conversation
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
|
Paging graphemecluster (@graphemecluster), this is above my regex paygrade |
There was a problem hiding this comment.
I checked my old fix from microsoft/TypeScript@c435526 (#60249) (yes sorry I should've extracted the change and fired a separate PR in the past) and it implements the same logic (unioning the nested alternatives into the parent when the disjunction closes), so this should be fine.
I also verified using the test case file I made up in the past (microsoft/TypeScript@24c16f9 (#60249)) and it passes. The current PR's test coverage is quite narrow (e.g. it does not test reverse direction), so I would suggest adding my cases in that commit, or at least:
// Named group in non-first alternative
/(?:x|(?<a>y))(?<a>z)/;
// Outer named group before nested named group
/(?<a>x)(?:(?<a>y)|z)/;
// Named group inside modifier group
/(?i:(?<a>x))(?<a>y)/;
// Multiple nesting levels
/(?:(?:(?<a>x)))(?<a>y)/;
// Legal: Outer disjunction separates non-nested and nested named groups
/(?:(?<a>x))|(?<a>y)/;
// Legal: Outer disjunction separates each duplicate pair
/(?<a>x)(?<b>y)|(?<a>z)(?<b>w)/;
On the other side, (?<\u0061>...) currently fails with TS1514 but not normalised to a and participated in the duplicate check. I am porting microsoft/TypeScript#61042 to fix this.
There was a problem hiding this comment.
Pull request overview
Fixes nested named-capture scope propagation so TS1515 is correctly reported without affecting legal alternations.
Changes:
- Unions nested disjunction capture names into the enclosing alternative.
- Adds regression coverage for nested groups and assertions.
- Adds generated compiler baselines confirming five diagnostics.
Show a summary per file
| File | Description |
|---|---|
internal/scanner/regexp.go |
Propagates nested capture names. |
testdata/tests/cases/compiler/regexNamedGroupDuplicateNestedInGroup.ts |
Adds regression cases. |
testdata/baselines/reference/compiler/regexNamedGroupDuplicateNestedInGroup.errors.txt |
Records expected diagnostics. |
testdata/baselines/reference/compiler/regexNamedGroupDuplicateNestedInGroup.js |
Records emitted JavaScript. |
testdata/baselines/reference/compiler/regexNamedGroupDuplicateNestedInGroup.symbols |
Records symbol baseline. |
testdata/baselines/reference/compiler/regexNamedGroupDuplicateNestedInGroup.types |
Records type baseline. |
Review details
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 6/6 changed files
- Comments generated: 0
- Review effort level: Balanced
| // This ensures a name defined inside a nested group (e.g. `(?:(?<a>x))`) is | ||
| // still visible to a duplicate check for a sibling group later in the same | ||
| // enclosing alternative (e.g. `(?:(?<a>x))(?<a>z)`). | ||
| var disjunctionNames map[string]bool |
There was a problem hiding this comment.
This really should have been var disjunctionNames collections.Set[string] but it's whatever I geuss
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
… group (microsoft/typescript-go#4881) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> Co-authored-by: Ryan Cavanaugh <RyanCavanaugh@users.noreply.github.com>
microsoft#4881 registered this test's diff in submoduleAccepted.txt, so the diff belongs in submoduleAccepted rather than in submodule.
scanDisjunctiondropped an alternative's named-capture scope as soon as the alternative (or enclosing group) ended, so a duplicate(?<name>...)defined inside a nested group never reached the enclosing alternative's duplicate check.Root cause
scanDisjunctionpushed a fresh scope per alternative (including alternatives inside nested groups/assertions), but discarded that scope entirely once the alternative or group closed, instead of folding it back into the enclosing alternative.Fix
scanDisjunction(internal/scanner/regexp.go) now unions the names defined across all alternatives of a disjunction, and when that disjunction is the content of a nested group, merges the union up into the parent alternative's scope before returning.|disjunction untouched (still legal per ES2025).Tests
testdata/tests/cases/compiler/regexNamedGroupDuplicateNestedInGroup.tscovering all patterns from the issue, with accepted baselines showing TS1515 now fires for the previously-missed nested cases and remains silent for the legal alternation cases.