diff --git a/.changeset/cold-nails-teach.md b/.changeset/cold-nails-teach.md new file mode 100644 index 000000000..f46eec0da --- /dev/null +++ b/.changeset/cold-nails-teach.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-regexp": minor +--- + +Add support for string literal to `regexp/no-empty-alternative` diff --git a/docs/rules/no-empty-alternative.md b/docs/rules/no-empty-alternative.md index eb4b10c8b..ab77a2cd6 100644 --- a/docs/rules/no-empty-alternative.md +++ b/docs/rules/no-empty-alternative.md @@ -37,6 +37,7 @@ var foo = /a+|b*/ var foo = /a+|b+|/ var foo = /\|\||\|||\|\|\|/ var foo = /a(?:a|bc|def|h||ij|k)/ +var foo = /[abc\q{def|}]/v ``` diff --git a/lib/rules/no-empty-alternative.ts b/lib/rules/no-empty-alternative.ts index 67c794f63..f831b530b 100644 --- a/lib/rules/no-empty-alternative.ts +++ b/lib/rules/no-empty-alternative.ts @@ -2,6 +2,7 @@ import type { RegExpVisitor } from "@eslint-community/regexpp/visitor" import type { Alternative, CapturingGroup, + ClassStringDisjunction, Group, Pattern, } from "@eslint-community/regexpp/ast" @@ -73,8 +74,15 @@ export default createRule("no-empty-alternative", { getRegexpLocation, fixReplaceNode, }: RegExpContext): RegExpVisitor.Handlers { - function verifyAlternatives( - regexpNode: CapturingGroup | Group | Pattern, + function verifyAlternatives< + N extends + | CapturingGroup + | Group + | Pattern + | ClassStringDisjunction, + >( + regexpNode: N, + suggestFixer: (alt: N["alternatives"][number]) => string | null, ) { if (regexpNode.alternatives.length >= 2) { // We want to have at least two alternatives because the zero alternatives isn't possible because of @@ -96,7 +104,7 @@ export default createRule("no-empty-alternative", { end: index + 1, }) - const fixed = getFixedNode(regexpNode, alt) + const fixed = suggestFixer(alt) context.report({ node, @@ -122,9 +130,20 @@ export default createRule("no-empty-alternative", { } return { - onGroupEnter: verifyAlternatives, - onCapturingGroupEnter: verifyAlternatives, - onPatternEnter: verifyAlternatives, + onGroupEnter: (gNode) => + verifyAlternatives(gNode, (alt) => + getFixedNode(gNode, alt), + ), + onCapturingGroupEnter: (cgNode) => + verifyAlternatives(cgNode, (alt) => + getFixedNode(cgNode, alt), + ), + onPatternEnter: (pNode) => + verifyAlternatives(pNode, (alt) => + getFixedNode(pNode, alt), + ), + onClassStringDisjunctionEnter: (csdNode) => + verifyAlternatives(csdNode, () => null), } } diff --git a/tests/lib/rules/no-empty-alternative.ts b/tests/lib/rules/no-empty-alternative.ts index 010d8e7c7..7cfc9b25d 100644 --- a/tests/lib/rules/no-empty-alternative.ts +++ b/tests/lib/rules/no-empty-alternative.ts @@ -3,13 +3,13 @@ import rule from "../../../lib/rules/no-empty-alternative" const tester = new RuleTester({ parserOptions: { - ecmaVersion: 2020, + ecmaVersion: "latest", sourceType: "module", }, }) tester.run("no-empty-alternative", rule as any, { - valid: [`/()|(?:)|(?=)/`, `/(?:)/`, `/a*|b+/`], + valid: [`/()|(?:)|(?=)/`, `/(?:)/`, `/a*|b+/`, String.raw`/[\q{a|b}]/v`], invalid: [ { code: `/|||||/`, @@ -68,5 +68,35 @@ tester.run("no-empty-alternative", rule as any, { }, ], }, + { + code: String.raw`/[\q{a|}]/v`, + errors: [ + { + messageId: "empty", + column: 7, + suggestions: [], + }, + ], + }, + { + code: String.raw`/[\q{|a}]/v`, + errors: [ + { + messageId: "empty", + column: 6, + suggestions: [], + }, + ], + }, + { + code: String.raw`/[\q{a||b}]/v`, + errors: [ + { + messageId: "empty", + column: 8, + suggestions: [], + }, + ], + }, ], })