Skip to content

Commit d2616a5

Browse files
committed
Add support for ES2025
1 parent ccf9673 commit d2616a5

10 files changed

+253
-55
lines changed

Diff for: lib/rules/no-useless-backreference.ts

+73-13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ import type { RegExpContext } from "../utils"
1515
import { createRule, defineRegexpVisitor } from "../utils"
1616
import { mention } from "../utils/mention"
1717

18+
type MessageId =
19+
| "nested"
20+
| "disjunctive"
21+
| "intoNegativeLookaround"
22+
| "forward"
23+
| "backward"
24+
| "empty"
25+
1826
/**
1927
* Returns whether the list of ancestors from `from` to `to` contains a negated
2028
* lookaround.
@@ -35,16 +43,67 @@ function hasNegatedLookaroundInBetween(
3543
return false
3644
}
3745

46+
/**
47+
* Returns the problem information specifying the reason why the backreference is
48+
* useless.
49+
*/
50+
function getUselessProblem(
51+
backRef: Backreference,
52+
flags: ReadonlyFlags,
53+
): { messageId: MessageId; group: CapturingGroup; otherGroups: string } | null {
54+
const groups = [backRef.resolved].flat()
55+
56+
const problems: { messageId: MessageId; group: CapturingGroup }[] = []
57+
for (const group of groups) {
58+
const messageId = getUselessMessageId(backRef, group, flags)
59+
if (!messageId) {
60+
return null
61+
}
62+
problems.push({ messageId, group })
63+
}
64+
if (problems.length === 0) {
65+
return null
66+
}
67+
68+
let problemsToReport
69+
70+
// Gets problems that appear in the same disjunction.
71+
const problemsInSameDisjunction = problems.filter(
72+
(problem) => problem.messageId !== "disjunctive",
73+
)
74+
75+
if (problemsInSameDisjunction.length) {
76+
// Only report problems that appear in the same disjunction.
77+
problemsToReport = problemsInSameDisjunction
78+
} else {
79+
// If all groups appear in different disjunctions, report it.
80+
problemsToReport = problems
81+
}
82+
83+
const [{ messageId, group }, ...other] = problemsToReport
84+
let otherGroups = ""
85+
86+
if (other.length === 1) {
87+
otherGroups = " and another group"
88+
} else if (other.length > 1) {
89+
otherGroups = ` and other ${other.length} groups`
90+
}
91+
return {
92+
messageId,
93+
group,
94+
otherGroups,
95+
}
96+
}
97+
3898
/**
3999
* Returns the message id specifying the reason why the backreference is
40100
* useless.
41101
*/
42102
function getUselessMessageId(
43103
backRef: Backreference,
104+
group: CapturingGroup,
44105
flags: ReadonlyFlags,
45-
): string | null {
46-
const group = backRef.resolved
47-
106+
): MessageId | null {
48107
const closestAncestor = getClosestAncestor(backRef, group)
49108

50109
if (closestAncestor === group) {
@@ -93,16 +152,16 @@ export default createRule("no-useless-backreference", {
93152
},
94153
schema: [],
95154
messages: {
96-
nested: "Backreference {{ bref }} will be ignored. It references group {{ group }} from within that group.",
155+
nested: "Backreference {{ bref }} will be ignored. It references group {{ group }}{{ otherGroups }} from within that group.",
97156
forward:
98-
"Backreference {{ bref }} will be ignored. It references group {{ group }} which appears later in the pattern.",
157+
"Backreference {{ bref }} will be ignored. It references group {{ group }}{{ otherGroups }} which appears later in the pattern.",
99158
backward:
100-
"Backreference {{ bref }} will be ignored. It references group {{ group }} which appears before in the same lookbehind.",
159+
"Backreference {{ bref }} will be ignored. It references group {{ group }}{{ otherGroups }} which appears before in the same lookbehind.",
101160
disjunctive:
102-
"Backreference {{ bref }} will be ignored. It references group {{ group }} which is in another alternative.",
161+
"Backreference {{ bref }} will be ignored. It references group {{ group }}{{ otherGroups }} which is in another alternative.",
103162
intoNegativeLookaround:
104-
"Backreference {{ bref }} will be ignored. It references group {{ group }} which is in a negative lookaround.",
105-
empty: "Backreference {{ bref }} will be ignored. It references group {{ group }} which always captures zero characters.",
163+
"Backreference {{ bref }} will be ignored. It references group {{ group }}{{ otherGroups }} which is in a negative lookaround.",
164+
empty: "Backreference {{ bref }} will be ignored. It references group {{ group }}{{ otherGroups }} which always captures zero characters.",
106165
},
107166
type: "suggestion", // "problem",
108167
},
@@ -114,16 +173,17 @@ export default createRule("no-useless-backreference", {
114173
}: RegExpContext): RegExpVisitor.Handlers {
115174
return {
116175
onBackreferenceEnter(backRef) {
117-
const messageId = getUselessMessageId(backRef, flags)
176+
const problem = getUselessProblem(backRef, flags)
118177

119-
if (messageId) {
178+
if (problem) {
120179
context.report({
121180
node,
122181
loc: getRegexpLocation(backRef),
123-
messageId,
182+
messageId: problem.messageId,
124183
data: {
125184
bref: mention(backRef),
126-
group: mention(backRef.resolved),
185+
group: mention(problem.group),
186+
otherGroups: problem.otherGroups,
127187
},
128188
})
129189
}

Diff for: lib/rules/prefer-named-backreference.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ export default createRule("prefer-named-backreference", {
2424
}: RegExpContext): RegExpVisitor.Handlers {
2525
return {
2626
onBackreferenceEnter(bNode) {
27-
if (bNode.resolved.name && !bNode.raw.startsWith("\\k<")) {
27+
if (
28+
!bNode.ambiguous &&
29+
bNode.resolved.name &&
30+
!bNode.raw.startsWith("\\k<")
31+
) {
2832
context.report({
2933
node,
3034
loc: getRegexpLocation(bNode),

Diff for: lib/utils/regexp-ast/case-variation.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -139,19 +139,28 @@ export function isCaseVariant(
139139
// case-variant in Unicode mode
140140
return unicodeLike && d.kind === "word"
141141

142-
case "Backreference":
142+
case "Backreference": {
143143
// we need to check whether the associated capturing group
144144
// is case variant
145-
if (hasSomeDescendant(element, d.resolved)) {
145+
146+
const outside = [d.resolved]
147+
.flat()
148+
.filter(
149+
(resolved) => !hasSomeDescendant(element, resolved),
150+
)
151+
if (outside.length === 0) {
146152
// the capturing group is part of the root element, so
147153
// we don't need to make an extra check
148154
return false
149155
}
150156

151157
return (
152158
!isEmptyBackreference(d, flags) &&
153-
isCaseVariant(d.resolved, flags)
159+
outside.some((resolved) =>
160+
isCaseVariant(resolved, flags),
161+
)
154162
)
163+
}
155164

156165
case "Character":
157166
case "CharacterClassRange":

Diff for: package-lock.json

+34-34
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
"assert": "^2.0.0",
8585
"chai": "^5.0.0",
8686
"env-cmd": "^10.1.0",
87-
"eslint": "^9.5.0",
87+
"eslint": "^9.6.0",
8888
"eslint-config-prettier": "^9.1.0",
8989
"eslint-doc-generator": "^1.7.1",
9090
"eslint-import-resolver-typescript": "^3.6.1",
@@ -120,7 +120,7 @@
120120
},
121121
"dependencies": {
122122
"@eslint-community/eslint-utils": "^4.2.0",
123-
"@eslint-community/regexpp": "^4.9.1",
123+
"@eslint-community/regexpp": "^4.11.0",
124124
"comment-parser": "^1.4.0",
125125
"jsdoc-type-pratt-parser": "^4.0.0",
126126
"refa": "^0.12.1",

0 commit comments

Comments
 (0)