diff --git a/.changeset/curvy-shoes-kiss.md b/.changeset/curvy-shoes-kiss.md new file mode 100644 index 000000000..3a14b6f29 --- /dev/null +++ b/.changeset/curvy-shoes-kiss.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-regexp": minor +--- + +Add support for v flag to `regexp/prefer-d` rule diff --git a/docs/rules/prefer-d.md b/docs/rules/prefer-d.md index 6e0f0039f..51f5cd2a8 100644 --- a/docs/rules/prefer-d.md +++ b/docs/rules/prefer-d.md @@ -53,6 +53,7 @@ var foo = /[^0-9]/; This option control how character class element equivalent to `\d` will be treated. *Note:* This option does not affect character classes equivalent to `\d`. E.g. `[\d]`, `[0-9]`, and `[0123456789]` are unaffected. +It also does not affect expression non-nested operands equivalent to `\d`. E.g. `[\d&&x]`, and `[\d--x]` are unaffected. - `insideCharacterClass: "d"` (*default*) @@ -88,6 +89,11 @@ This option control how character class element equivalent to `\d` will be treat /* ✗ BAD */ var foo = /[\da-z]/; var foo = /[0-9]/; + + /* Ignore */ + var foo = /[\d--0]/v; + /* ✗ BAD */ + var foo = /[[\da-z]--0]/v; ``` @@ -107,6 +113,7 @@ This option control how character class element equivalent to `\d` will be treat /* ✗ BAD */ var foo = /[0-9]/; + var foo = /[[0-9a-z]--0]/v; ``` diff --git a/lib/rules/prefer-d.ts b/lib/rules/prefer-d.ts index 464e89627..23a9ed5a3 100644 --- a/lib/rules/prefer-d.ts +++ b/lib/rules/prefer-d.ts @@ -6,12 +6,14 @@ import { CP_DIGIT_ZERO, CP_DIGIT_NINE, } from "../utils" -import { Chars, toCharSet } from "regexp-ast-analysis" +import { Chars, toUnicodeSet } from "regexp-ast-analysis" import { mention } from "../utils/mention" import type { + CharacterClass, CharacterClassElement, CharacterClassRange, EscapeCharacterSet, + ExpressionCharacterClass, } from "@eslint-community/regexpp/ast" /** @@ -66,61 +68,66 @@ export default createRule("prefer-d", { getRegexpLocation, fixReplaceNode, }: RegExpContext): RegExpVisitor.Handlers { - return { - onCharacterClassEnter(ccNode) { - // FIXME: TS Error - // @ts-expect-error -- FIXME - const charSet = toCharSet(ccNode, flags) + function verifyCharacterClass( + ccNode: CharacterClass | ExpressionCharacterClass, + ) { + const charSet = toUnicodeSet(ccNode, flags) - let predefined: string | undefined = undefined - if (charSet.equals(Chars.digit(flags))) { - predefined = "\\d" - } else if (charSet.equals(Chars.digit(flags).negate())) { - predefined = "\\D" - } + let predefined: string | undefined = undefined + if (charSet.equals(Chars.digit(flags))) { + predefined = "\\d" + } else if (charSet.equals(Chars.digit(flags).negate())) { + predefined = "\\D" + } + + if (predefined) { + context.report({ + node, + loc: getRegexpLocation(ccNode), + messageId: "unexpected", + data: { + type: "character class", + expr: mention(ccNode), + instead: predefined, + }, + fix: fixReplaceNode(ccNode, predefined), + }) + return + } - if (predefined) { + if ( + insideCharacterClass === "ignore" || + ccNode.type !== "CharacterClass" + ) { + return + } + + const expected = insideCharacterClass === "d" ? "\\d" : "0-9" + + // check the elements in this character class + for (const e of ccNode.elements) { + if (isDigits(e) && e.raw !== expected) { context.report({ node, - loc: getRegexpLocation(ccNode), + loc: getRegexpLocation(e), messageId: "unexpected", data: { - type: "character class", - expr: mention(ccNode), - instead: predefined, + type: + e.type === "CharacterSet" + ? "character set" + : "character class range", + expr: mention(e), + instead: expected, }, - fix: fixReplaceNode(ccNode, predefined), + fix: fixReplaceNode(e, expected), }) - return - } - - if (insideCharacterClass === "ignore") { - return } + } + } - const expected = - insideCharacterClass === "d" ? "\\d" : "0-9" - - // check the elements in this character class - for (const e of ccNode.elements) { - if (isDigits(e) && e.raw !== expected) { - context.report({ - node, - loc: getRegexpLocation(e), - messageId: "unexpected", - data: { - type: - e.type === "CharacterSet" - ? "character set" - : "character class range", - expr: mention(e), - instead: expected, - }, - fix: fixReplaceNode(e, expected), - }) - } - } - }, + return { + onCharacterClassEnter: verifyCharacterClass, + onExpressionCharacterClassEnter: verifyCharacterClass, } } diff --git a/tests/lib/rules/prefer-d.ts b/tests/lib/rules/prefer-d.ts index bdae49002..e01e1e228 100644 --- a/tests/lib/rules/prefer-d.ts +++ b/tests/lib/rules/prefer-d.ts @@ -3,7 +3,7 @@ import rule from "../../../lib/rules/prefer-d" const tester = new RuleTester({ parserOptions: { - ecmaVersion: 2020, + ecmaVersion: "latest", sourceType: "module", }, }) @@ -28,6 +28,14 @@ tester.run("prefer-d", rule as any, { code: String.raw`/[\da-z]/`, options: [{ insideCharacterClass: "d" }], }, + String.raw`/\d/v`, + { + code: String.raw`/[\d--0]/v`, + options: [{ insideCharacterClass: "range" }], + }, + String.raw`/[\q{0|1|2|3|4|5|6|7|8}]/v`, + String.raw`/[\q{0|1|2|3|4|5|6|7|8|9|a}]/v`, + String.raw`/[\q{0|1|2|3|4|5|6|7|8|9|abc}]/v`, ], invalid: [ { @@ -99,5 +107,79 @@ tester.run("prefer-d", rule as any, { options: [{ insideCharacterClass: "range" }], errors: ["Unexpected character set '\\d'. Use '0-9' instead."], }, + { + code: "/[0-9]/v", + output: String.raw`/\d/v`, + errors: [ + { + message: + "Unexpected character class '[0-9]'. Use '\\d' instead.", + column: 2, + endColumn: 7, + }, + ], + }, + { + code: "/[[0-9]--[0-7]]/v", + output: String.raw`/[\d--[0-7]]/v`, + errors: [ + { + message: + "Unexpected character class '[0-9]'. Use '\\d' instead.", + column: 3, + endColumn: 8, + }, + ], + }, + { + code: "/[[0-:]--:]/v", + output: String.raw`/\d/v`, + errors: [ + { + message: + "Unexpected character class '[[0-:]--:]'. Use '\\d' instead.", + column: 2, + endColumn: 12, + }, + ], + }, + { + code: String.raw`/[[\da-z]--0]/v`, + output: String.raw`/[[0-9a-z]--0]/v`, + options: [{ insideCharacterClass: "range" }], + errors: [ + { + message: + "Unexpected character set '\\d'. Use '0-9' instead.", + column: 4, + endColumn: 6, + }, + ], + }, + { + code: String.raw`/[[0-9a-z]--0]/v`, + output: String.raw`/[[\da-z]--0]/v`, + options: [{ insideCharacterClass: "d" }], + errors: [ + { + message: + "Unexpected character class range '0-9'. Use '\\d' instead.", + column: 4, + endColumn: 7, + }, + ], + }, + { + code: String.raw`/[\q{0|1|2|3|4|5|6|7|8|9}]/v`, + output: String.raw`/\d/v`, + errors: [ + { + message: + "Unexpected character class '[\\q{0|1|2|3|4|5|6|7|8|9}]'. Use '\\d' instead.", + column: 2, + endColumn: 27, + }, + ], + }, ], })