feat: port rule prefer-named-capture-group - #1829
Conversation
- read \c outside a character class as Annex B's literal backslash when no
control letter follows, and reject it under u/v
- fold a regex literal passed to RegExp() to its own source text, so both the
call and the literal are reported
- reject modifier-group headers with a repeated flag, an overlapping add/remove
pair, or two empty sets, and accept an empty removal set
- reject \q{...} outside a character class under u/v
…re-group scanner Differential-tested against regexpp over ~3.4k pattern/flag combinations. - read the closed u/v escape grammar strictly instead of falling back to an identity escape, and resolve u/v backreferences against the groups found - read a class-body \c against ClassControlLetter, so \c with no control letter no longer swallows the character after it - reject \q outside a character class, and under u inside one - reject a quantifier with no operand, a second quantifier on one atom, and a quantifier on an assertion (lookahead only outside u/v) - reject a group name that isn't an IdentifierName - reject a stray ] or } under u/v
| classesValid := true | ||
| if !IterateRegexCharacterClasses(pattern, flags, func(start, end int) { | ||
| elements, _, parsed := ParseRegexCharacterClassWithEnd(pattern, start, end, flags) | ||
| if !parsed { |
There was a problem hiding this comment.
[P2] Treat failed character-class parsing as invalid
For new RegExp('(a)' + '[a-\\d]', 'u'), ParseRegexCharacterClassWithEnd fails here, but the callback returns without clearing classesValid. ESLint 10.8.1 therefore emits no rule diagnostic, while Rslint reports (a). Failed class parsing—and strict u/v class escapes such as [\\a]—must reject the entire pattern.
| for n < len(rest) && isHex(rest[n]) { | ||
| n++ | ||
| } | ||
| if n > 1 && n < len(rest) && rest[n] == '}' { |
There was a problem hiding this comment.
[P2] Reject out-of-range Unicode code point escapes
This branch accepts any non-empty hexadecimal \u{...} escape without checking that its value is at most 0x10FFFF. For new RegExp('(a)' + '\u{110000}', 'u'), ESLint 10.8.1 rejects the pattern and emits no rule diagnostic, while Rslint reports (a).
| if pos != len(pattern) { | ||
| return nil, false | ||
| } | ||
| if flags.UV() && !unicodeBackrefsResolve(pattern, flags, groups) { |
There was a problem hiding this comment.
[P2] Validate named references outside Unicode mode
Named backreferences must resolve whenever the pattern contains named captures, even without u or v. With new RegExp('(?<x>a)' + '\k<y>(b)'), regexpp rejects the unresolved y reference and ESLint emits nothing, but this flags.UV() gate skips validation and Rslint reports (b).
| continue | ||
| case c == '\\': | ||
| // A `\u` escape spells the code point out; its value isn't checked. | ||
| if i+1 >= len(name) || name[i+1] != 'u' { |
There was a problem hiding this comment.
[P2] Support braced Unicode escapes in capture names
A capture name may contain a braced Unicode escape under Unicode mode. ESLint 10.8.1 reports (b) for new RegExp('(?<\\u{61}>a)' + '(b)', 'u'), but this scanner advances only past \u, rejects the following {, and suppresses the diagnostic entirely.
|
|
||
| switch callee.Kind { | ||
| case ast.KindIdentifier: | ||
| if callee.AsIdentifier().Text != "RegExp" || utils.IsShadowed(callee, "RegExp") { |
There was a problem hiding this comment.
[P2] Stop tracking a reassigned RegExp global
ESLint's ReferenceTracker suppresses all references when the global binding has any write, but this check only detects declarations. Consequently RegExp = custom; RegExp('(a)' + '') produces no ESLint diagnostic while Rslint reports (a) as though the call still targeted the built-in constructor.
| return isKnownGlobalObject(ctx, access.Expression) | ||
| case ast.KindBinaryExpression: | ||
| binary := callee.AsBinaryExpression() | ||
| if binary == nil || binary.OperatorToken == nil || binary.OperatorToken.Kind != ast.KindCommaToken { |
There was a problem hiding this comment.
[P2] Trace all ReferenceTracker pass-through expressions
The callee matcher only follows comma expressions, while ReferenceTracker also propagates references through conditional and logical expressions. ESLint reports prefer-named-capture-group for (true ? RegExp : other)('(a)' + '') and (RegExp || other)('(a)' + ''), but Rslint emits no diagnostic; wrapped global objects such as (0, globalThis).RegExp(...) are missed for the same reason.
Summary
Port the
prefer-named-capture-grouprule from ESLint to rslint.Enforces using named capture groups instead of numbered capture groups in regular expressions, in regex literals and
RegExp()/new RegExp()calls with statically-determinable patterns.Related Links
Checklist