Skip to content

Commit 6ca58c6

Browse files
authored
Add regexp/no-empty-alternative rule (#95)
* Add `regexp/no-empty-alternative` rule * update type
1 parent f9a03c8 commit 6ca58c6

File tree

6 files changed

+165
-0
lines changed

6 files changed

+165
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
9090
| [regexp/no-assertion-capturing-group](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-assertion-capturing-group.html) | disallow capturing group that captures assertions. | :star: |
9191
| [regexp/no-dupe-characters-character-class](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-dupe-characters-character-class.html) | disallow duplicate characters in the RegExp character class | :star: |
9292
| [regexp/no-dupe-disjunctions](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-dupe-disjunctions.html) | disallow duplicate disjunctions | |
93+
| [regexp/no-empty-alternative](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-empty-alternative.html) | disallow alternatives without elements | |
9394
| [regexp/no-empty-group](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-empty-group.html) | disallow empty group | :star: |
9495
| [regexp/no-empty-lookarounds-assertion](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-empty-lookarounds-assertion.html) | disallow empty lookahead assertion or empty lookbehind assertion | :star: |
9596
| [regexp/no-escape-backspace](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-escape-backspace.html) | disallow escape backspace (`[\b]`) | :star: |

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
1818
| [regexp/no-assertion-capturing-group](./no-assertion-capturing-group.md) | disallow capturing group that captures assertions. | :star: |
1919
| [regexp/no-dupe-characters-character-class](./no-dupe-characters-character-class.md) | disallow duplicate characters in the RegExp character class | :star: |
2020
| [regexp/no-dupe-disjunctions](./no-dupe-disjunctions.md) | disallow duplicate disjunctions | |
21+
| [regexp/no-empty-alternative](./no-empty-alternative.md) | disallow alternatives without elements | |
2122
| [regexp/no-empty-group](./no-empty-group.md) | disallow empty group | :star: |
2223
| [regexp/no-empty-lookarounds-assertion](./no-empty-lookarounds-assertion.md) | disallow empty lookahead assertion or empty lookbehind assertion | :star: |
2324
| [regexp/no-escape-backspace](./no-escape-backspace.md) | disallow escape backspace (`[\b]`) | :star: |

docs/rules/no-empty-alternative.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "regexp/no-empty-alternative"
5+
description: "disallow alternatives without elements"
6+
---
7+
# regexp/no-empty-alternative
8+
9+
> disallow alternatives without elements
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
13+
## :book: Rule Details
14+
15+
While (re-)writing long regular expressions, it can happen that one forgets to
16+
remove the `|` character of a former alternative. This rule tries to point out
17+
these potential mistakes by reporting all empty alternatives.
18+
19+
<eslint-code-block>
20+
21+
```js
22+
/* eslint regexp/no-empty-alternative: "error" */
23+
24+
/* ✓ GOOD */
25+
var foo = /(?:)/
26+
var foo = /a+|b*/
27+
28+
/* ✗ BAD */
29+
var foo = /a+|b+|/
30+
var foo = /\|\||\|||\|\|\|/
31+
var foo = /a(?:a|bc|def|h||ij|k)/
32+
```
33+
34+
</eslint-code-block>
35+
36+
## :wrench: Options
37+
38+
Nothing.
39+
40+
## :heart: Compatibility
41+
42+
This rule was taken from [eslint-plugin-clean-regex].
43+
This rule is compatible with [clean-regex/no-empty-alternative] rule.
44+
45+
[eslint-plugin-clean-regex]: https://github.com/RunDevelopment/eslint-plugin-clean-regex
46+
[clean-regex/no-empty-alternative]: https://github.com/RunDevelopment/eslint-plugin-clean-regex/blob/master/docs/rules/no-empty-alternative.md
47+
48+
49+
## :mag: Implementation
50+
51+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-empty-alternative.ts)
52+
- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-empty-alternative.ts)

lib/rules/no-empty-alternative.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { Expression } from "estree"
2+
import type { RegExpVisitor } from "regexpp/visitor"
3+
import type { CapturingGroup, Group, Pattern } from "regexpp/ast"
4+
import { createRule, defineRegexpVisitor, getRegexpLocation } from "../utils"
5+
6+
export default createRule("no-empty-alternative", {
7+
meta: {
8+
docs: {
9+
description: "disallow alternatives without elements",
10+
// TODO Switch to recommended in the major version.
11+
// recommended: true,
12+
recommended: false,
13+
default: "warn",
14+
},
15+
schema: [],
16+
messages: {
17+
empty: "No empty alternatives. Use quantifiers instead.",
18+
},
19+
type: "problem",
20+
},
21+
create(context) {
22+
const sourceCode = context.getSourceCode()
23+
24+
/**
25+
* Create visitor
26+
* @param node
27+
*/
28+
function createVisitor(node: Expression): RegExpVisitor.Handlers {
29+
/**
30+
* Verify alternatives
31+
*/
32+
function verifyAlternatives(
33+
regexpNode: CapturingGroup | Group | Pattern,
34+
) {
35+
if (regexpNode.alternatives.length >= 2) {
36+
// We want to have at least two alternatives because the zero alternatives isn't possible because of
37+
// the parser and one alternative is already handled by other rules.
38+
for (let i = 0; i < regexpNode.alternatives.length; i++) {
39+
const alt = regexpNode.alternatives[i]
40+
if (alt.elements.length === 0) {
41+
context.report({
42+
node,
43+
loc: getRegexpLocation(sourceCode, node, alt),
44+
messageId: "empty",
45+
})
46+
// don't report the same node multiple times
47+
return
48+
}
49+
}
50+
}
51+
}
52+
53+
return {
54+
onGroupEnter: verifyAlternatives,
55+
onCapturingGroupEnter: verifyAlternatives,
56+
onPatternEnter: verifyAlternatives,
57+
}
58+
}
59+
60+
return defineRegexpVisitor(context, {
61+
createVisitor,
62+
})
63+
},
64+
})

lib/utils/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import negation from "../rules/negation"
66
import noAssertionCapturingGroup from "../rules/no-assertion-capturing-group"
77
import noDupeCharactersCharacterClass from "../rules/no-dupe-characters-character-class"
88
import noDupeDisjunctions from "../rules/no-dupe-disjunctions"
9+
import noEmptyAlternative from "../rules/no-empty-alternative"
910
import noEmptyGroup from "../rules/no-empty-group"
1011
import noEmptyLookaroundsAssertion from "../rules/no-empty-lookarounds-assertion"
1112
import noEscapeBackspace from "../rules/no-escape-backspace"
@@ -45,6 +46,7 @@ export const rules = [
4546
noAssertionCapturingGroup,
4647
noDupeCharactersCharacterClass,
4748
noDupeDisjunctions,
49+
noEmptyAlternative,
4850
noEmptyGroup,
4951
noEmptyLookaroundsAssertion,
5052
noEscapeBackspace,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { RuleTester } from "eslint"
2+
import rule from "../../../lib/rules/no-empty-alternative"
3+
4+
const tester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 2020,
7+
sourceType: "module",
8+
},
9+
})
10+
11+
tester.run("no-empty-alternative", rule as any, {
12+
valid: [`/()|(?:)|(?=)/`, `/(?:)/`, `/a*|b+/`],
13+
invalid: [
14+
{
15+
code: `/|||||/`,
16+
errors: [
17+
{
18+
message: "No empty alternatives. Use quantifiers instead.",
19+
line: 1,
20+
column: 2,
21+
},
22+
],
23+
},
24+
{
25+
code: `/(a+|b+|)/`,
26+
errors: [
27+
{
28+
message: "No empty alternatives. Use quantifiers instead.",
29+
line: 1,
30+
column: 9,
31+
},
32+
],
33+
},
34+
{
35+
code: String.raw`/(?:\|\|||\|)/`,
36+
errors: [
37+
{
38+
message: "No empty alternatives. Use quantifiers instead.",
39+
line: 1,
40+
column: 10,
41+
},
42+
],
43+
},
44+
],
45+
})

0 commit comments

Comments
 (0)