Skip to content

Commit 2f4dc5d

Browse files
authored
Fix regexp/prefer-question-quantifier rule changing effect of alternative order. (#89)
1 parent eecb6e3 commit 2f4dc5d

File tree

2 files changed

+76
-19
lines changed

2 files changed

+76
-19
lines changed

lib/rules/prefer-question-quantifier.ts

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Expression } from "estree"
2+
import type { Group, Quantifier } from "regexpp/ast"
23
import type { RegExpVisitor } from "regexpp/visitor"
34
import {
45
createRule,
@@ -74,32 +75,61 @@ export default createRule("prefer-question-quantifier", {
7475
}
7576
},
7677
onGroupEnter(gNode) {
77-
const nonEmpties = []
78-
const empties = []
79-
for (const alt of gNode.alternatives) {
80-
if (alt.elements.length === 0) {
81-
empties.push(alt)
82-
} else {
83-
nonEmpties.push(alt)
78+
const lastAlt =
79+
gNode.alternatives[gNode.alternatives.length - 1]
80+
if (!lastAlt.elements.length) {
81+
// last alternative is empty. e.g /(?:a|)/, /(?:a|b|)/
82+
const alternatives = gNode.alternatives.slice(0, -1)
83+
while (alternatives.length > 0) {
84+
if (
85+
!alternatives[alternatives.length - 1].elements
86+
.length
87+
) {
88+
// last alternative is empty.
89+
alternatives.pop()
90+
continue
91+
}
92+
break
8493
}
85-
}
86-
if (empties.length && nonEmpties.length) {
87-
const instead = `(?:${nonEmpties
94+
if (!alternatives.length) {
95+
// all empty
96+
return
97+
}
98+
99+
let reportNode: Group | Quantifier = gNode
100+
const instead = `(?:${alternatives
88101
.map((ne) => ne.raw)
89102
.join("|")})?`
103+
if (gNode.parent.type === "Quantifier") {
104+
if (
105+
gNode.parent.greedy &&
106+
gNode.parent.min === 0 &&
107+
gNode.parent.max === 1
108+
) {
109+
reportNode = gNode.parent
110+
} else {
111+
// It is possible to use group `(?:)` and `?`,
112+
// but we will not report this as it makes the regex more complicated.
113+
return
114+
}
115+
}
90116
context.report({
91117
node,
92-
loc: getRegexpLocation(sourceCode, node, gNode),
118+
loc: getRegexpLocation(
119+
sourceCode,
120+
node,
121+
reportNode,
122+
),
93123
messageId: "unexpectedGroup",
94124
data: {
95-
expr: gNode.raw,
125+
expr: reportNode.raw,
96126
instead,
97127
},
98128
fix(fixer) {
99129
const range = getRegexpRange(
100130
sourceCode,
101131
node,
102-
gNode,
132+
reportNode,
103133
)
104134
if (range == null) {
105135
return null

tests/lib/rules/prefer-question-quantifier.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ tester.run("prefer-question-quantifier", rule as any, {
1717
"/[a{0,1}]/",
1818
"/a{0,}/",
1919
"/(?:a|b)/",
20+
"/a(?:|a)/",
21+
"/(?:abc||def)/",
22+
"/(?:)/",
23+
"/(?:||)/",
24+
"/(?:abc|def|)+/",
25+
"/(?:abc|def|)??/",
2026
],
2127
invalid: [
2228
{
@@ -76,17 +82,38 @@ tester.run("prefer-question-quantifier", rule as any, {
7682
],
7783
},
7884
{
79-
code: "/(?:abc||def)/",
85+
code: "/(?:abc|def|)/",
8086
output: "/(?:abc|def)?/",
8187
errors: [
8288
{
8389
message:
84-
'Unexpected group "(?:abc||def)". Use "(?:abc|def)?" instead.',
90+
'Unexpected group "(?:abc|def|)". Use "(?:abc|def)?" instead.',
8591
column: 2,
8692
endColumn: 14,
8793
},
8894
],
8995
},
96+
{
97+
code: "/(?:abc||def|)/",
98+
output: "/(?:abc||def)?/",
99+
errors: [
100+
'Unexpected group "(?:abc||def|)". Use "(?:abc||def)?" instead.',
101+
],
102+
},
103+
{
104+
code: "/(?:abc|def||)/",
105+
output: "/(?:abc|def)?/",
106+
errors: [
107+
'Unexpected group "(?:abc|def||)". Use "(?:abc|def)?" instead.',
108+
],
109+
},
110+
{
111+
code: "/(?:abc|def|)?/",
112+
output: "/(?:abc|def)?/",
113+
errors: [
114+
'Unexpected group "(?:abc|def|)?". Use "(?:abc|def)?" instead.',
115+
],
116+
},
90117
{
91118
code: `
92119
const s = "a{0,1}"
@@ -108,25 +135,25 @@ tester.run("prefer-question-quantifier", rule as any, {
108135
},
109136
{
110137
code: `
111-
const s = "(?:abc||def)"
138+
const s = "(?:abc|def|)"
112139
new RegExp(s)
113140
`,
114141
output: `
115142
const s = "(?:abc|def)?"
116143
new RegExp(s)
117144
`,
118145
errors: [
119-
'Unexpected group "(?:abc||def)". Use "(?:abc|def)?" instead.',
146+
'Unexpected group "(?:abc|def|)". Use "(?:abc|def)?" instead.',
120147
],
121148
},
122149
{
123150
code: `
124-
const s = "(?:abc|"+"|def)"
151+
const s = "(?:abc|"+"def|)"
125152
new RegExp(s)
126153
`,
127154
output: null,
128155
errors: [
129-
'Unexpected group "(?:abc||def)". Use "(?:abc|def)?" instead.',
156+
'Unexpected group "(?:abc|def|)". Use "(?:abc|def)?" instead.',
130157
],
131158
},
132159
],

0 commit comments

Comments
 (0)