Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/rules/prefer-w.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var foo = /\W/;
var foo = /[0-9a-zA-Z_]/;
var foo = /[^0-9a-zA-Z_]/;
var foo = /[0-9a-z_]/i;
var foo = /[0-9a-z_-]/i;
```

</eslint-code-block>
Expand Down
126 changes: 67 additions & 59 deletions lib/rules/prefer-w.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {
CP_DIGIT_NINE,
CP_LOW_LINE,
} from "../utils"
import { Chars } from "regexp-ast-analysis"

/**
* Checks if small letter char class range
* @param node The node to check
*/
function isSmallLetterCharacterClassRange(node: CharacterClassElement) {
function isSmallLetterRange(node: CharacterClassElement) {
return (
node.type === "CharacterClassRange" &&
node.min.value === CP_SMALL_A &&
Expand All @@ -30,7 +31,7 @@ function isSmallLetterCharacterClassRange(node: CharacterClassElement) {
* Checks if capital letter char class range
* @param node The node to check
*/
function isCapitalLetterCharacterClassRange(node: CharacterClassElement) {
function isCapitalLetterRange(node: CharacterClassElement) {
return (
node.type === "CharacterClassRange" &&
node.min.value === CP_CAPITAL_A &&
Expand All @@ -42,7 +43,7 @@ function isCapitalLetterCharacterClassRange(node: CharacterClassElement) {
* Checks if digit char class
* @param node The node to check
*/
function isDigitCharacterClass(node: CharacterClassElement) {
function isDigitRangeOrSet(node: CharacterClassElement) {
return (
(node.type === "CharacterClassRange" &&
node.min.value === CP_DIGIT_ZERO &&
Expand All @@ -55,7 +56,7 @@ function isDigitCharacterClass(node: CharacterClassElement) {
* Checks if includes `_`
* @param node The node to check
*/
function includesLowLineCharacterClass(node: CharacterClassElement) {
function isUnderscoreCharacter(node: CharacterClassElement) {
return node.type === "Character" && node.value === CP_LOW_LINE
}

Expand Down Expand Up @@ -84,93 +85,100 @@ export default createRule("prefer-w", {
fixReplaceNode,
getRegexpRange,
fixerApplyEscape,
toCharSet,
}: RegExpContext): RegExpVisitor.Handlers {
return {
onCharacterClassEnter(ccNode: CharacterClass) {
const charSet = toCharSet(ccNode)

let predefined: string | undefined = undefined
if (charSet.equals(Chars.word(flags))) {
predefined = "\\w"
} else if (charSet.equals(Chars.word(flags).negate())) {
predefined = "\\W"
}

if (predefined) {
context.report({
node,
loc: getRegexpLocation(ccNode),
messageId: "unexpected",
data: {
type: "character class",
expr: ccNode.raw,
instead: predefined,
},
fix: fixReplaceNode(ccNode, predefined),
})
return
}

const lowerAToZ: CharacterClassElement[] = []
const capitalAToZ: CharacterClassElement[] = []
const digit: CharacterClassElement[] = []
const lowLine: CharacterClassElement[] = []
const underscore: CharacterClassElement[] = []
for (const element of ccNode.elements) {
if (isSmallLetterCharacterClassRange(element)) {
if (isSmallLetterRange(element)) {
lowerAToZ.push(element)
if (flags.ignoreCase) {
capitalAToZ.push(element)
}
} else if (
isCapitalLetterCharacterClassRange(element)
) {
} else if (isCapitalLetterRange(element)) {
capitalAToZ.push(element)
if (flags.ignoreCase) {
lowerAToZ.push(element)
}
} else if (isDigitCharacterClass(element)) {
} else if (isDigitRangeOrSet(element)) {
digit.push(element)
} else if (includesLowLineCharacterClass(element)) {
lowLine.push(element)
} else if (isUnderscoreCharacter(element)) {
underscore.push(element)
}
}

if (
lowerAToZ.length &&
capitalAToZ.length &&
digit.length &&
lowLine.length
underscore.length
) {
const unexpectedElements = [
...new Set([
...lowerAToZ,
...capitalAToZ,
...digit,
...lowLine,
...underscore,
]),
].sort((a, b) => a.start - b.start)

if (
ccNode.elements.length === unexpectedElements.length
) {
const instead = ccNode.negate ? "\\W" : "\\w"
context.report({
node,
loc: getRegexpLocation(ccNode),
messageId: "unexpected",
data: {
type: "character class",
expr: ccNode.raw,
instead,
},
fix: fixReplaceNode(ccNode, instead),
})
} else {
context.report({
node,
loc: getRegexpLocation(ccNode),
messageId: "unexpected",
data: {
type: "character class ranges",
expr: `[${unexpectedElements
.map((e) => e.raw)
.join("")}]`,
instead: "\\w",
},
*fix(fixer: Rule.RuleFixer) {
const range = getRegexpRange(ccNode)
if (range == null) {
return
}
yield fixer.replaceTextRange(
getRegexpRange(
unexpectedElements.shift()!,
)!,
fixerApplyEscape("\\w"),
context.report({
node,
loc: getRegexpLocation(ccNode),
messageId: "unexpected",
data: {
type: "character class ranges",
expr: `[${unexpectedElements
.map((e) => e.raw)
.join("")}]`,
instead: "\\w",
},
*fix(fixer: Rule.RuleFixer) {
const range = getRegexpRange(ccNode)
if (range == null) {
return
}
yield fixer.replaceTextRange(
getRegexpRange(
unexpectedElements.shift()!,
)!,
fixerApplyEscape("\\w"),
)
for (const element of unexpectedElements) {
yield fixer.removeRange(
getRegexpRange(element)!,
)
for (const element of unexpectedElements) {
yield fixer.removeRange(
getRegexpRange(element)!,
)
}
},
})
}
}
},
})
}
},
}
Expand Down
17 changes: 15 additions & 2 deletions tests/lib/rules/prefer-w.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ tester.run("prefer-w", rule as any, {
new RegExp(s, 'i')
`,
output: `
const s = "[\\\\wc]"
const s = "\\\\w"
new RegExp(s, 'i')
`,
errors: [
"Unexpected character class ranges '[0-9A-Z_]'. Use '\\w' instead.",
"Unexpected character class '[0-9A-Z_c]'. Use '\\w' instead.",
],
},
{
Expand All @@ -98,6 +98,19 @@ tester.run("prefer-w", rule as any, {
new RegExp(s, 'i')
`,
output: null,
errors: [
"Unexpected character class '[0-9A-Z_c]'. Use '\\w' instead.",
],
},
{
code: `
const s = "[0-9A-Z_-]"
new RegExp(s, 'i')
`,
output: `
const s = "[\\\\w-]"
new RegExp(s, 'i')
`,
errors: [
"Unexpected character class ranges '[0-9A-Z_]'. Use '\\w' instead.",
],
Expand Down