Skip to content

Commit f35f9cb

Browse files
authored
Add support for v flag to regexp/prefer-d rule (#602)
1 parent 041bd66 commit f35f9cb

File tree

4 files changed

+148
-47
lines changed

4 files changed

+148
-47
lines changed

.changeset/curvy-shoes-kiss.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-regexp": minor
3+
---
4+
5+
Add support for v flag to `regexp/prefer-d` rule

docs/rules/prefer-d.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ var foo = /[^0-9]/;
5353
This option control how character class element equivalent to `\d` will be treated.
5454

5555
*Note:* This option does not affect character classes equivalent to `\d`. E.g. `[\d]`, `[0-9]`, and `[0123456789]` are unaffected.
56+
It also does not affect expression non-nested operands equivalent to `\d`. E.g. `[\d&&x]`, and `[\d--x]` are unaffected.
5657

5758
- `insideCharacterClass: "d"` (*default*)
5859

@@ -88,6 +89,11 @@ This option control how character class element equivalent to `\d` will be treat
8889
/* ✗ BAD */
8990
var foo = /[\da-z]/;
9091
var foo = /[0-9]/;
92+
93+
/* Ignore */
94+
var foo = /[\d--0]/v;
95+
/* ✗ BAD */
96+
var foo = /[[\da-z]--0]/v;
9197
```
9298

9399
</eslint-code-block>
@@ -107,6 +113,7 @@ This option control how character class element equivalent to `\d` will be treat
107113

108114
/* ✗ BAD */
109115
var foo = /[0-9]/;
116+
var foo = /[[0-9a-z]--0]/v;
110117
```
111118

112119
</eslint-code-block>

lib/rules/prefer-d.ts

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import {
66
CP_DIGIT_ZERO,
77
CP_DIGIT_NINE,
88
} from "../utils"
9-
import { Chars, toCharSet } from "regexp-ast-analysis"
9+
import { Chars, toUnicodeSet } from "regexp-ast-analysis"
1010
import { mention } from "../utils/mention"
1111
import type {
12+
CharacterClass,
1213
CharacterClassElement,
1314
CharacterClassRange,
1415
EscapeCharacterSet,
16+
ExpressionCharacterClass,
1517
} from "@eslint-community/regexpp/ast"
1618

1719
/**
@@ -66,61 +68,66 @@ export default createRule("prefer-d", {
6668
getRegexpLocation,
6769
fixReplaceNode,
6870
}: RegExpContext): RegExpVisitor.Handlers {
69-
return {
70-
onCharacterClassEnter(ccNode) {
71-
// FIXME: TS Error
72-
// @ts-expect-error -- FIXME
73-
const charSet = toCharSet(ccNode, flags)
71+
function verifyCharacterClass(
72+
ccNode: CharacterClass | ExpressionCharacterClass,
73+
) {
74+
const charSet = toUnicodeSet(ccNode, flags)
7475

75-
let predefined: string | undefined = undefined
76-
if (charSet.equals(Chars.digit(flags))) {
77-
predefined = "\\d"
78-
} else if (charSet.equals(Chars.digit(flags).negate())) {
79-
predefined = "\\D"
80-
}
76+
let predefined: string | undefined = undefined
77+
if (charSet.equals(Chars.digit(flags))) {
78+
predefined = "\\d"
79+
} else if (charSet.equals(Chars.digit(flags).negate())) {
80+
predefined = "\\D"
81+
}
82+
83+
if (predefined) {
84+
context.report({
85+
node,
86+
loc: getRegexpLocation(ccNode),
87+
messageId: "unexpected",
88+
data: {
89+
type: "character class",
90+
expr: mention(ccNode),
91+
instead: predefined,
92+
},
93+
fix: fixReplaceNode(ccNode, predefined),
94+
})
95+
return
96+
}
8197

82-
if (predefined) {
98+
if (
99+
insideCharacterClass === "ignore" ||
100+
ccNode.type !== "CharacterClass"
101+
) {
102+
return
103+
}
104+
105+
const expected = insideCharacterClass === "d" ? "\\d" : "0-9"
106+
107+
// check the elements in this character class
108+
for (const e of ccNode.elements) {
109+
if (isDigits(e) && e.raw !== expected) {
83110
context.report({
84111
node,
85-
loc: getRegexpLocation(ccNode),
112+
loc: getRegexpLocation(e),
86113
messageId: "unexpected",
87114
data: {
88-
type: "character class",
89-
expr: mention(ccNode),
90-
instead: predefined,
115+
type:
116+
e.type === "CharacterSet"
117+
? "character set"
118+
: "character class range",
119+
expr: mention(e),
120+
instead: expected,
91121
},
92-
fix: fixReplaceNode(ccNode, predefined),
122+
fix: fixReplaceNode(e, expected),
93123
})
94-
return
95-
}
96-
97-
if (insideCharacterClass === "ignore") {
98-
return
99124
}
125+
}
126+
}
100127

101-
const expected =
102-
insideCharacterClass === "d" ? "\\d" : "0-9"
103-
104-
// check the elements in this character class
105-
for (const e of ccNode.elements) {
106-
if (isDigits(e) && e.raw !== expected) {
107-
context.report({
108-
node,
109-
loc: getRegexpLocation(e),
110-
messageId: "unexpected",
111-
data: {
112-
type:
113-
e.type === "CharacterSet"
114-
? "character set"
115-
: "character class range",
116-
expr: mention(e),
117-
instead: expected,
118-
},
119-
fix: fixReplaceNode(e, expected),
120-
})
121-
}
122-
}
123-
},
128+
return {
129+
onCharacterClassEnter: verifyCharacterClass,
130+
onExpressionCharacterClassEnter: verifyCharacterClass,
124131
}
125132
}
126133

tests/lib/rules/prefer-d.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/prefer-d"
33

44
const tester = new RuleTester({
55
parserOptions: {
6-
ecmaVersion: 2020,
6+
ecmaVersion: "latest",
77
sourceType: "module",
88
},
99
})
@@ -28,6 +28,14 @@ tester.run("prefer-d", rule as any, {
2828
code: String.raw`/[\da-z]/`,
2929
options: [{ insideCharacterClass: "d" }],
3030
},
31+
String.raw`/\d/v`,
32+
{
33+
code: String.raw`/[\d--0]/v`,
34+
options: [{ insideCharacterClass: "range" }],
35+
},
36+
String.raw`/[\q{0|1|2|3|4|5|6|7|8}]/v`,
37+
String.raw`/[\q{0|1|2|3|4|5|6|7|8|9|a}]/v`,
38+
String.raw`/[\q{0|1|2|3|4|5|6|7|8|9|abc}]/v`,
3139
],
3240
invalid: [
3341
{
@@ -99,5 +107,79 @@ tester.run("prefer-d", rule as any, {
99107
options: [{ insideCharacterClass: "range" }],
100108
errors: ["Unexpected character set '\\d'. Use '0-9' instead."],
101109
},
110+
{
111+
code: "/[0-9]/v",
112+
output: String.raw`/\d/v`,
113+
errors: [
114+
{
115+
message:
116+
"Unexpected character class '[0-9]'. Use '\\d' instead.",
117+
column: 2,
118+
endColumn: 7,
119+
},
120+
],
121+
},
122+
{
123+
code: "/[[0-9]--[0-7]]/v",
124+
output: String.raw`/[\d--[0-7]]/v`,
125+
errors: [
126+
{
127+
message:
128+
"Unexpected character class '[0-9]'. Use '\\d' instead.",
129+
column: 3,
130+
endColumn: 8,
131+
},
132+
],
133+
},
134+
{
135+
code: "/[[0-:]--:]/v",
136+
output: String.raw`/\d/v`,
137+
errors: [
138+
{
139+
message:
140+
"Unexpected character class '[[0-:]--:]'. Use '\\d' instead.",
141+
column: 2,
142+
endColumn: 12,
143+
},
144+
],
145+
},
146+
{
147+
code: String.raw`/[[\da-z]--0]/v`,
148+
output: String.raw`/[[0-9a-z]--0]/v`,
149+
options: [{ insideCharacterClass: "range" }],
150+
errors: [
151+
{
152+
message:
153+
"Unexpected character set '\\d'. Use '0-9' instead.",
154+
column: 4,
155+
endColumn: 6,
156+
},
157+
],
158+
},
159+
{
160+
code: String.raw`/[[0-9a-z]--0]/v`,
161+
output: String.raw`/[[\da-z]--0]/v`,
162+
options: [{ insideCharacterClass: "d" }],
163+
errors: [
164+
{
165+
message:
166+
"Unexpected character class range '0-9'. Use '\\d' instead.",
167+
column: 4,
168+
endColumn: 7,
169+
},
170+
],
171+
},
172+
{
173+
code: String.raw`/[\q{0|1|2|3|4|5|6|7|8|9}]/v`,
174+
output: String.raw`/\d/v`,
175+
errors: [
176+
{
177+
message:
178+
"Unexpected character class '[\\q{0|1|2|3|4|5|6|7|8|9}]'. Use '\\d' instead.",
179+
column: 2,
180+
endColumn: 27,
181+
},
182+
],
183+
},
102184
],
103185
})

0 commit comments

Comments
 (0)