Skip to content

Commit 699695e

Browse files
committed
fix
1 parent db79472 commit 699695e

File tree

2 files changed

+103
-45
lines changed

2 files changed

+103
-45
lines changed

lib/rules/no-useless-character-class.ts

+93-45
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
CharacterClass,
66
CharacterClassElement,
77
ExpressionCharacterClass,
8+
UnicodeSetsCharacterClass,
89
} from "@eslint-community/regexpp/ast"
910

1011
const ESCAPES_OUTSIDE_CHARACTER_CLASS = new Set("$()*+./?[{|")
@@ -89,7 +90,7 @@ export default createRule("no-useless-character-class", {
8990
? getEscapedFirstRawIfNeeded(element)
9091
: null) ??
9192
(index === ccNode.elements.length - 1
92-
? getEscapedLatsRawIfNeeded(element)
93+
? getEscapedLastRawIfNeeded(element)
9394
: null) ??
9495
element.raw
9596
)
@@ -103,6 +104,21 @@ export default createRule("no-useless-character-class", {
103104
messageData = {
104105
type: "unnecessary nesting character class",
105106
}
107+
if (!ccNode.elements.length) {
108+
// empty character class
109+
const nextElement =
110+
ccNode.parent.elements[
111+
ccNode.parent.elements.indexOf(
112+
ccNode as UnicodeSetsCharacterClass,
113+
) + 1
114+
]
115+
if (
116+
nextElement &&
117+
isNeedEscapedForFirstElement(nextElement)
118+
) {
119+
unwrapped.push("\\") // Add a backslash to escape the next character.
120+
}
121+
}
106122
} else {
107123
if (ccNode.elements.length !== 1) {
108124
return
@@ -148,7 +164,7 @@ export default createRule("no-useless-character-class", {
148164
}
149165
unwrapped[0] =
150166
getEscapedFirstRawIfNeeded(element.min) ??
151-
getEscapedLatsRawIfNeeded(element.min) ??
167+
getEscapedLastRawIfNeeded(element.min) ??
152168
element.min.raw
153169
} else if (element.type === "ClassStringDisjunction") {
154170
if (!characterClassStack.length) {
@@ -181,82 +197,114 @@ export default createRule("no-useless-character-class", {
181197
})
182198

183199
/**
184-
* Returns the escaped raw text, if the given first element requires escaping.
185-
* Otherwise, returns null.
200+
* Checks whether an escape is required if the given element is placed first
201+
* after character class replacement.
186202
*/
187-
function getEscapedFirstRawIfNeeded(
188-
firstElement: CharacterClassElement,
203+
function isNeedEscapedForFirstElement(
204+
element: CharacterClassElement,
189205
) {
190-
const firstRaw =
191-
firstElement.type === "Character"
192-
? firstElement.raw
193-
: firstElement.type === "CharacterClassRange"
194-
? firstElement.min.raw
206+
const char =
207+
element.type === "Character"
208+
? element.raw
209+
: element.type === "CharacterClassRange"
210+
? element.min.raw
195211
: null
196-
if (firstRaw == null) {
197-
return null
212+
if (char == null) {
213+
return false
198214
}
199215
if (characterClassStack.length) {
200216
// Nesting character class
201217

202218
// Avoid [A&&[&]] => [A&&&]
203219
if (
204220
REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has(
205-
firstRaw,
221+
char,
206222
) &&
207223
// The previous character is the same
208-
pattern[ccNode.start - 1] === firstRaw
224+
pattern[ccNode.start - 1] === char
209225
) {
210-
return `\\${firstElement.raw}`
226+
return true
211227
}
212-
return null
228+
229+
// Avoid [[]^] => [^]
230+
return (
231+
char === "^" &&
232+
ccNode.parent.type === "CharacterClass" &&
233+
ccNode.parent.elements[0] === ccNode
234+
)
213235
}
214236

215237
// Flat character class
216-
if (
217-
(flags.unicode
238+
return (
239+
flags.unicode
218240
? ESCAPES_OUTSIDE_CHARACTER_CLASS_WITH_U
219241
: ESCAPES_OUTSIDE_CHARACTER_CLASS
220-
).has(firstRaw)
221-
) {
222-
return `\\${firstElement.raw}`
223-
}
224-
return null
242+
).has(char)
225243
}
226244

227245
/**
228-
* Returns the escaped raw text, if the given last element requires escaping.
229-
* Otherwise, returns null.
246+
* Checks whether an escape is required if the given element is placed last
247+
* after character class replacement.
230248
*/
231-
function getEscapedLatsRawIfNeeded(
232-
lastElement: CharacterClassElement,
249+
function needEscapedForLastElement(
250+
element: CharacterClassElement,
233251
) {
234-
const lastRaw =
235-
lastElement.type === "Character"
236-
? lastElement.raw
237-
: lastElement.type === "CharacterClassRange"
238-
? lastElement.max.raw
252+
const char =
253+
element.type === "Character"
254+
? element.raw
255+
: element.type === "CharacterClassRange"
256+
? element.max.raw
239257
: null
240-
if (lastRaw == null) {
241-
return null
258+
if (char == null) {
259+
return false
242260
}
243261
if (characterClassStack.length) {
244262
// Nesting character class
245263

246264
// Avoid [A[&]&B] => [A&&B]
247-
if (
265+
return (
248266
REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has(
249-
lastRaw,
267+
char,
250268
) &&
251269
// The next character is the same
252-
pattern[ccNode.end] === lastRaw
253-
) {
254-
const prefix = lastElement.raw.slice(
255-
0,
256-
-lastRaw.length,
257-
)
258-
return `${prefix}\\${lastRaw}`
259-
}
270+
pattern[ccNode.end] === char
271+
)
272+
}
273+
return false
274+
}
275+
276+
/**
277+
* Returns the escaped raw text, if the given first element requires escaping.
278+
* Otherwise, returns null.
279+
*/
280+
function getEscapedFirstRawIfNeeded(
281+
firstElement: CharacterClassElement,
282+
) {
283+
if (isNeedEscapedForFirstElement(firstElement)) {
284+
return `\\${firstElement.raw}`
285+
}
286+
return null
287+
}
288+
289+
/**
290+
* Returns the escaped raw text, if the given last element requires escaping.
291+
* Otherwise, returns null.
292+
*/
293+
function getEscapedLastRawIfNeeded(
294+
lastElement: CharacterClassElement,
295+
) {
296+
if (needEscapedForLastElement(lastElement)) {
297+
const lastRaw =
298+
lastElement.type === "Character"
299+
? lastElement.raw
300+
: lastElement.type === "CharacterClassRange"
301+
? lastElement.max.raw
302+
: "" // never
303+
const prefix = lastElement.raw.slice(
304+
0,
305+
-lastRaw.length,
306+
)
307+
return `${prefix}\\${lastRaw}`
260308
}
261309
return null
262310
}

tests/lib/rules/no-useless-character-class.ts

+10
Original file line numberDiff line numberDiff line change
@@ -257,5 +257,15 @@ tester.run("no-useless-character-class", rule as any, {
257257
// output: String.raw`/[a\-b]/v`,
258258
// errors: 1,
259259
// },
260+
{
261+
code: String.raw`/[[]^]/v`,
262+
output: String.raw`/[\^]/v`,
263+
errors: 1,
264+
},
265+
{
266+
code: String.raw`/[&[]&]/v`,
267+
output: String.raw`/[&\&]/v`,
268+
errors: 1,
269+
},
260270
],
261271
})

0 commit comments

Comments
 (0)