Skip to content

Commit 1471ea5

Browse files
authored
Add support for v flag to regexp/no-misleading-unicode-character rule (#584)
1 parent f35f9cb commit 1471ea5

File tree

3 files changed

+96
-8
lines changed

3 files changed

+96
-8
lines changed

.changeset/smart-chefs-poke.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/no-misleading-unicode-character` rule

lib/rules/no-misleading-unicode-character.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ function getProblem(grapheme: string, flags: ReadonlyFlags): Problem | null {
3535
(grapheme.length === 2 && !startsWithSurrogate(grapheme))
3636
) {
3737
return "Multi"
38-
} else if (!flags.unicode && startsWithSurrogate(grapheme)) {
38+
} else if (
39+
!flags.unicode &&
40+
!flags.unicodeSets &&
41+
startsWithSurrogate(grapheme)
42+
) {
3943
return "Surrogate"
4044
}
4145
return null
@@ -84,6 +88,13 @@ function getGraphemeProblems(
8488
): GraphemeProblem[] {
8589
let offset = cc.negate ? 2 : 1
8690

91+
const ignoreElements = cc.elements.filter(
92+
(element) =>
93+
element.type === "CharacterClass" || // Nesting CharacterClass
94+
element.type === "ExpressionCharacterClass" || // Nesting ExpressionCharacterClass
95+
element.type === "ClassStringDisjunction",
96+
)
97+
8798
const graphemes = splitter.splitGraphemes(cc.raw.slice(offset, -1))
8899
const problems: GraphemeProblem[] = []
89100

@@ -93,6 +104,14 @@ function getGraphemeProblems(
93104
const start = offset + cc.start
94105
const end = start + grapheme.length
95106

107+
if (
108+
ignoreElements.some(
109+
(ignore) => ignore.start <= start && end <= ignore.end,
110+
)
111+
) {
112+
continue
113+
}
114+
96115
problems.push({
97116
grapheme,
98117
problem,
@@ -113,6 +132,7 @@ function getGraphemeProblems(
113132
function getGraphemeProblemsFix(
114133
problems: readonly GraphemeProblem[],
115134
cc: CharacterClass,
135+
flags: ReadonlyFlags,
116136
): string | null {
117137
if (cc.negate) {
118138
// we can't fix a negated character class
@@ -131,21 +151,25 @@ function getGraphemeProblemsFix(
131151
}
132152

133153
// The prefix of graphemes
134-
const prefix = problems
135-
.map((p) => p.grapheme)
136-
.sort((a, b) => b.length - a.length)
137-
.join("|")
154+
const prefixGraphemes = problems.map((p) => p.grapheme)
138155

139156
// The rest of the character class
140157
let ccRaw = cc.raw
141158
for (let i = problems.length - 1; i >= 0; i--) {
142159
const { start, end } = problems[i]
143160
ccRaw = ccRaw.slice(0, start - cc.start) + ccRaw.slice(end - cc.start)
144161
}
162+
163+
if (flags.unicodeSets) {
164+
const prefix = prefixGraphemes.join("|")
165+
return `[\\q{${prefix}}${ccRaw.slice(1, -1)}]`
166+
}
167+
145168
if (ccRaw.startsWith("[^")) {
146169
ccRaw = `[\\${ccRaw.slice(1)}`
147170
}
148171

172+
const prefix = prefixGraphemes.sort((a, b) => b.length - a.length).join("|")
149173
let fix = prefix
150174
let singleAlternative = problems.length === 1
151175
if (ccRaw !== "[]") {
@@ -238,8 +262,7 @@ export default createRule("no-misleading-unicode-character", {
238262
start: problems[0].start,
239263
end: problems[problems.length - 1].end,
240264
}
241-
242-
const fix = getGraphemeProblemsFix(problems, ccNode)
265+
const fix = getGraphemeProblemsFix(problems, ccNode, flags)
243266

244267
const graphemes = problems
245268
.map((p) => mention(p.grapheme))

tests/lib/rules/no-misleading-unicode-character.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import rule from "../../../lib/rules/no-misleading-unicode-character"
33

44
const tester = new RuleTester({
55
parserOptions: {
6-
ecmaVersion: 2020,
6+
ecmaVersion: "latest",
77
sourceType: "module",
88
},
99
})
@@ -46,6 +46,13 @@ tester.run("no-misleading-unicode-character", rule as any, {
4646

4747
// Ignore escaped symbols because it's obvious they aren't together
4848
`/[\\uD83D\\uDC4D]/`,
49+
50+
// ES2024
51+
"var r = /[👍]/v",
52+
String.raw`var r = /^[\q{👶🏻}]$/v`,
53+
String.raw`var r = /[🇯\q{abc}🇵]/v`,
54+
"var r = /[🇯[A]🇵]/v",
55+
"var r = /[🇯[A--B]🇵]/v",
4956
],
5057
invalid: [
5158
{
@@ -262,5 +269,58 @@ tester.run("no-misleading-unicode-character", rule as any, {
262269
options: [{ fixable: true }],
263270
errors: [{ messageId: "characterClass" }],
264271
},
272+
273+
// ES2024
274+
{
275+
code: String.raw`/[[👶🏻]]/v`,
276+
output: String.raw`/[[\q{👶🏻}]]/v`,
277+
options: [{ fixable: true }],
278+
errors: [{ messageId: "characterClass" }],
279+
},
280+
{
281+
code: String.raw`/[👶🏻[👨‍👩‍👦]]/v`,
282+
output: String.raw`/[\q{👶🏻}[👨‍👩‍👦]]/v`,
283+
options: [{ fixable: true }],
284+
errors: [
285+
{ messageId: "characterClass", column: 3 },
286+
{ messageId: "characterClass", column: 8 },
287+
],
288+
},
289+
{
290+
code: String.raw`/[👶🏻👨‍👩‍👦]/v`,
291+
output: String.raw`/[\q{👶🏻|👨‍👩‍👦}]/v`,
292+
options: [{ fixable: true }],
293+
errors: [{ messageId: "characterClass" }],
294+
},
295+
{
296+
code: String.raw`/[👶🏻&👨‍👩‍👦]/v`,
297+
output: String.raw`/[\q{👶🏻|👨‍👩‍👦}&]/v`,
298+
options: [{ fixable: true }],
299+
errors: [{ messageId: "characterClass" }],
300+
},
301+
{
302+
code: String.raw`/[^👶🏻&👨‍👩‍👦]/v`,
303+
output: null,
304+
options: [{ fixable: true }],
305+
errors: [{ messageId: "characterClass" }],
306+
},
307+
{
308+
code: String.raw`/[^👨‍👩‍👦]/v`,
309+
output: null,
310+
options: [{ fixable: true }],
311+
errors: [{ messageId: "characterClass" }],
312+
},
313+
{
314+
code: String.raw`new RegExp("[👨‍👩‍👦]", "v")`,
315+
output: String.raw`new RegExp("[\\q{👨‍👩‍👦}]", "v")`,
316+
options: [{ fixable: true }],
317+
errors: [{ messageId: "characterClass" }],
318+
},
319+
{
320+
code: `/👨‍👩‍👦+/v`,
321+
output: `/(?:👨‍👩‍👦)+/v`,
322+
options: [{ fixable: true }],
323+
errors: [{ messageId: "quantifierMulti" }],
324+
},
265325
],
266326
})

0 commit comments

Comments
 (0)