Skip to content

Commit 1ae6e2b

Browse files
Add no-zero-quantifier rule (#200)
* Add `no-zero-quantifier` rule * Fixed code example * Shorter message
1 parent a3bda3e commit 1ae6e2b

File tree

6 files changed

+190
-0
lines changed

6 files changed

+190
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
135135
| [regexp/no-useless-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-quantifier.html) | disallow quantifiers that can be removed | :wrench: |
136136
| [regexp/no-useless-range](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-range.html) | disallow unnecessary range of characters by using a hyphen | :wrench: |
137137
| [regexp/no-useless-two-nums-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-two-nums-quantifier.html) | disallow unnecessary `{n,m}` quantifier | :star::wrench: |
138+
| [regexp/no-zero-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-zero-quantifier.html) | disallow quantifiers with a maximum of zero | |
138139
| [regexp/optimal-lookaround-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/optimal-lookaround-quantifier.html) | disallow the alternatives of lookarounds that end with a non-constant quantifier | |
139140
| [regexp/order-in-character-class](https://ota-meshi.github.io/eslint-plugin-regexp/rules/order-in-character-class.html) | enforces elements order in character class | :wrench: |
140141
| [regexp/prefer-character-class](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-character-class.html) | enforce using character class | :wrench: |

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
4949
| [regexp/no-useless-quantifier](./no-useless-quantifier.md) | disallow quantifiers that can be removed | :wrench: |
5050
| [regexp/no-useless-range](./no-useless-range.md) | disallow unnecessary range of characters by using a hyphen | :wrench: |
5151
| [regexp/no-useless-two-nums-quantifier](./no-useless-two-nums-quantifier.md) | disallow unnecessary `{n,m}` quantifier | :star::wrench: |
52+
| [regexp/no-zero-quantifier](./no-zero-quantifier.md) | disallow quantifiers with a maximum of zero | |
5253
| [regexp/optimal-lookaround-quantifier](./optimal-lookaround-quantifier.md) | disallow the alternatives of lookarounds that end with a non-constant quantifier | |
5354
| [regexp/order-in-character-class](./order-in-character-class.md) | enforces elements order in character class | :wrench: |
5455
| [regexp/prefer-character-class](./prefer-character-class.md) | enforce using character class | :wrench: |

docs/rules/no-zero-quantifier.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "regexp/no-zero-quantifier"
5+
description: "disallow quantifiers with a maximum of zero"
6+
---
7+
# regexp/no-zero-quantifier
8+
9+
> disallow quantifiers with a maximum of zero
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+
This rule reports quantifiers with a maximum of zero. These quantifiers trivially do not affect the pattern is any way and can be removed.
16+
17+
<eslint-code-block>
18+
19+
```js
20+
/* eslint regexp/no-zero-quantifier: "error" */
21+
22+
/* ✓ GOOD */
23+
var foo = /a?/;
24+
var foo = /a{0,}/;
25+
var foo = /a{0,1}/;
26+
27+
/* ✗ BAD */
28+
var foo = /a{0}/;
29+
var foo = /a{0,0}?/;
30+
var foo = /(a){0}/;
31+
```
32+
33+
</eslint-code-block>
34+
35+
## :wrench: Options
36+
37+
Nothing.
38+
39+
## :mag: Implementation
40+
41+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-zero-quantifier.ts)
42+
- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-zero-quantifier.ts)

lib/rules/no-zero-quantifier.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import type { Rule } from "eslint"
2+
import { hasSomeDescendant } from "regexp-ast-analysis"
3+
import type { RegExpVisitor } from "regexpp/visitor"
4+
import type { RegExpContext } from "../utils"
5+
import { canUnwrapped, createRule, defineRegexpVisitor } from "../utils"
6+
7+
export default createRule("no-zero-quantifier", {
8+
meta: {
9+
docs: {
10+
description: "disallow quantifiers with a maximum of zero",
11+
// TODO Switch to recommended in the major version.
12+
// recommended: true,
13+
recommended: false,
14+
},
15+
schema: [],
16+
messages: {
17+
unexpected:
18+
"Unexpected zero quantifier. The quantifier and its quantified element can be removed without affecting the pattern.",
19+
withCapturingGroup:
20+
"Unexpected zero quantifier. The quantifier and its quantified element do not affecting the pattern. Try to remove the elements but be careful because it contains at least one capturing group.",
21+
22+
// suggestions
23+
remove: "Remove this zero quantifier.",
24+
},
25+
type: "suggestion", // "problem",
26+
},
27+
create(context) {
28+
/**
29+
* Create visitor
30+
*/
31+
function createVisitor(
32+
regexpContext: RegExpContext,
33+
): RegExpVisitor.Handlers {
34+
const {
35+
node,
36+
getRegexpLocation,
37+
fixReplaceNode,
38+
patternAst,
39+
} = regexpContext
40+
41+
return {
42+
onQuantifierEnter(qNode) {
43+
if (qNode.max === 0) {
44+
const containCapturingGroup = hasSomeDescendant(
45+
qNode,
46+
(n) => n.type === "CapturingGroup",
47+
)
48+
49+
if (containCapturingGroup) {
50+
context.report({
51+
node,
52+
loc: getRegexpLocation(qNode),
53+
messageId: "withCapturingGroup",
54+
})
55+
} else {
56+
const suggest: Rule.SuggestionReportDescriptor[] = []
57+
if (patternAst.raw === qNode.raw) {
58+
suggest.push({
59+
messageId: "remove",
60+
fix: fixReplaceNode(qNode, "(?:)"),
61+
})
62+
} else if (canUnwrapped(qNode, "")) {
63+
suggest.push({
64+
messageId: "remove",
65+
fix: fixReplaceNode(qNode, ""),
66+
})
67+
}
68+
69+
context.report({
70+
node,
71+
loc: getRegexpLocation(qNode),
72+
messageId: "unexpected",
73+
suggest,
74+
})
75+
}
76+
}
77+
},
78+
}
79+
}
80+
81+
return defineRegexpVisitor(context, {
82+
createVisitor,
83+
})
84+
},
85+
})

lib/utils/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import noUselessNonGreedy from "../rules/no-useless-non-greedy"
3737
import noUselessQuantifier from "../rules/no-useless-quantifier"
3838
import noUselessRange from "../rules/no-useless-range"
3939
import noUselessTwoNumsQuantifier from "../rules/no-useless-two-nums-quantifier"
40+
import noZeroQuantifier from "../rules/no-zero-quantifier"
4041
import optimalLookaroundQuantifier from "../rules/optimal-lookaround-quantifier"
4142
import orderInCharacterClass from "../rules/order-in-character-class"
4243
import preferCharacterClass from "../rules/prefer-character-class"
@@ -96,6 +97,7 @@ export const rules = [
9697
noUselessQuantifier,
9798
noUselessRange,
9899
noUselessTwoNumsQuantifier,
100+
noZeroQuantifier,
99101
optimalLookaroundQuantifier,
100102
orderInCharacterClass,
101103
preferCharacterClass,

tests/lib/rules/no-zero-quantifier.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { RuleTester } from "eslint"
2+
import rule from "../../../lib/rules/no-zero-quantifier"
3+
4+
const tester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 2020,
7+
sourceType: "module",
8+
},
9+
})
10+
11+
tester.run("no-zero-quantifier", rule as any, {
12+
valid: [`/a{0,1}/`, `/a{0,}/`],
13+
invalid: [
14+
{
15+
code: `/a{0}/`,
16+
errors: [
17+
{
18+
messageId: "unexpected",
19+
column: 2,
20+
endColumn: 6,
21+
suggestions: [{ output: `/(?:)/` }],
22+
},
23+
],
24+
},
25+
{
26+
code: `/a{0,0}/`,
27+
errors: [
28+
{
29+
messageId: "unexpected",
30+
column: 2,
31+
endColumn: 8,
32+
suggestions: [{ output: `/(?:)/` }],
33+
},
34+
],
35+
},
36+
{
37+
code: `/a{0,0}?b/`,
38+
errors: [
39+
{
40+
messageId: "unexpected",
41+
column: 2,
42+
endColumn: 9,
43+
suggestions: [{ output: `/b/` }],
44+
},
45+
],
46+
},
47+
{
48+
code: `/(a){0}/`,
49+
errors: [
50+
{
51+
messageId: "withCapturingGroup",
52+
column: 2,
53+
endColumn: 8,
54+
suggestions: [],
55+
},
56+
],
57+
},
58+
],
59+
})

0 commit comments

Comments
 (0)