Skip to content

Commit 1f020fa

Browse files
authored
Add regexp/no-useless-non-capturing-group rule (#43)
1 parent 98adf16 commit 1f020fa

File tree

7 files changed

+225
-1
lines changed

7 files changed

+225
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
9292
| [regexp/no-useless-backreference](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-backreference.html) | disallow useless backreferences in regular expressions | |
9393
| [regexp/no-useless-character-class](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-character-class.html) | disallow character class with one character | :wrench: |
9494
| [regexp/no-useless-exactly-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-exactly-quantifier.html) | disallow unnecessary exactly quantifier | :star: |
95+
| [regexp/no-useless-non-capturing-group](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-non-capturing-group.html) | disallow unnecessary Non-capturing group | :wrench: |
9596
| [regexp/no-useless-non-greedy](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-non-greedy.html) | disallow unnecessary quantifier non-greedy (`?`) | :wrench: |
9697
| [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: |
9798
| [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: |

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
2424
| [regexp/no-useless-backreference](./no-useless-backreference.md) | disallow useless backreferences in regular expressions | |
2525
| [regexp/no-useless-character-class](./no-useless-character-class.md) | disallow character class with one character | :wrench: |
2626
| [regexp/no-useless-exactly-quantifier](./no-useless-exactly-quantifier.md) | disallow unnecessary exactly quantifier | :star: |
27+
| [regexp/no-useless-non-capturing-group](./no-useless-non-capturing-group.md) | disallow unnecessary Non-capturing group | :wrench: |
2728
| [regexp/no-useless-non-greedy](./no-useless-non-greedy.md) | disallow unnecessary quantifier non-greedy (`?`) | :wrench: |
2829
| [regexp/no-useless-range](./no-useless-range.md) | disallow unnecessary range of characters by using a hyphen | :wrench: |
2930
| [regexp/no-useless-two-nums-quantifier](./no-useless-two-nums-quantifier.md) | disallow unnecessary `{n,m}` quantifier | :star: |
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "regexp/no-useless-non-capturing-group"
5+
description: "disallow unnecessary Non-capturing group"
6+
---
7+
# regexp/no-useless-non-capturing-group
8+
9+
> disallow unnecessary Non-capturing group
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+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
13+
14+
## :book: Rule Details
15+
16+
This rule reports unnecessary Non-capturing group
17+
18+
<eslint-code-block fix>
19+
20+
```js
21+
/* eslint regexp/no-useless-non-capturing-group: "error" */
22+
23+
/* ✓ GOOD */
24+
var foo = /(?:abcd)?/
25+
var foo = /(?:ab|cd)/
26+
27+
/* ✗ BAD */
28+
var foo = /(?:abcd)/
29+
var foo = /(?:[a-d])/
30+
```
31+
32+
</eslint-code-block>
33+
34+
## :wrench: Options
35+
36+
Nothing.
37+
38+
## :mag: Implementation
39+
40+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-useless-non-capturing-group.ts)
41+
- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-useless-non-capturing-group.ts)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import type { Expression } from "estree"
2+
import type { RegExpVisitor } from "regexpp/visitor"
3+
import {
4+
canUnwrapped,
5+
createRule,
6+
defineRegexpVisitor,
7+
getRegexpLocation,
8+
getRegexpRange,
9+
} from "../utils"
10+
11+
export default createRule("no-useless-non-capturing-group", {
12+
meta: {
13+
docs: {
14+
description: "disallow unnecessary Non-capturing group",
15+
// TODO In the major version
16+
// recommended: true,
17+
recommended: false,
18+
},
19+
fixable: "code",
20+
schema: [],
21+
messages: {
22+
unexpected: "Unexpected quantifier Non-capturing group.",
23+
},
24+
type: "suggestion", // "problem",
25+
},
26+
create(context) {
27+
const sourceCode = context.getSourceCode()
28+
29+
/**
30+
* Create visitor
31+
* @param node
32+
*/
33+
function createVisitor(node: Expression): RegExpVisitor.Handlers {
34+
return {
35+
onGroupEnter(gNode) {
36+
if (gNode.alternatives.length !== 1) {
37+
// Useful when using disjunctions.
38+
// e.g. /(?:a|b)/
39+
return
40+
}
41+
const alt = gNode.alternatives[0]
42+
if (alt.elements.length === 0) {
43+
// Ignore empty groups. You can check with another rule.
44+
// e.g. /(?:)/
45+
return
46+
}
47+
const parent = gNode.parent
48+
if (
49+
parent.type === "Quantifier" &&
50+
alt.elements.length > 1
51+
) {
52+
// e.g. /(?:ab)?/
53+
return
54+
}
55+
if (!canUnwrapped(gNode, alt.raw)) {
56+
return
57+
}
58+
59+
context.report({
60+
node,
61+
loc: getRegexpLocation(sourceCode, node, gNode),
62+
messageId: "unexpected",
63+
fix(fixer) {
64+
const groupRange = getRegexpRange(
65+
sourceCode,
66+
node,
67+
gNode,
68+
)
69+
const altRange = getRegexpRange(
70+
sourceCode,
71+
node,
72+
alt,
73+
)
74+
if (!groupRange || !altRange) {
75+
return null
76+
}
77+
return [
78+
fixer.removeRange([groupRange[0], altRange[0]]),
79+
fixer.removeRange([altRange[1], groupRange[1]]),
80+
]
81+
},
82+
})
83+
},
84+
}
85+
}
86+
87+
return defineRegexpVisitor(context, {
88+
createVisitor,
89+
})
90+
},
91+
})

lib/utils/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import noOctal from "../rules/no-octal"
1212
import noUselessBackreference from "../rules/no-useless-backreference"
1313
import noUselessCharacterClass from "../rules/no-useless-character-class"
1414
import noUselessExactlyQuantifier from "../rules/no-useless-exactly-quantifier"
15+
import noUselessNonCapturingGroup from "../rules/no-useless-non-capturing-group"
1516
import noUselessNonGreedy from "../rules/no-useless-non-greedy"
1617
import noUselessRange from "../rules/no-useless-range"
1718
import noUselessTwoNumsQuantifier from "../rules/no-useless-two-nums-quantifier"
@@ -42,6 +43,7 @@ export const rules = [
4243
noUselessBackreference,
4344
noUselessCharacterClass,
4445
noUselessExactlyQuantifier,
46+
noUselessNonCapturingGroup,
4547
noUselessNonGreedy,
4648
noUselessRange,
4749
noUselessTwoNumsQuantifier,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"test:base": "mocha --require ts-node/register \"tests/**/*.ts\" --reporter dot --timeout 60000",
1717
"test": "npm run test:nyc",
1818
"test:nyc": "nyc --reporter=lcov npm run test:base",
19-
"test:debug": "mocha --require ts-node/register/transpile-only \"tests/**/*.ts\" --reporter dot",
19+
"test:debug": "mocha --require ts-node/register/transpile-only \"tests/**/*.ts\" --reporter dot --timeout 60000",
2020
"test:watch": "npm run test:base -- --watch",
2121
"update": "ts-node --transpile-only ./tools/update.ts && npm run eslint-fix && npm run test:nyc",
2222
"new": "ts-node ./tools/new-rule.ts",
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { RuleTester } from "eslint"
2+
import rule from "../../../lib/rules/no-useless-non-capturing-group"
3+
4+
const tester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 2020,
7+
sourceType: "module",
8+
},
9+
})
10+
11+
tester.run("no-useless-non-capturing-group", rule as any, {
12+
valid: [
13+
`/(?:abcd)?/`,
14+
`/(?:ab|cd)/`,
15+
`/(?:a|b)/`,
16+
`/(?:)/`,
17+
String.raw`/()\1(?:0)/`,
18+
String.raw`/\1(?:0)/`,
19+
String.raw`/\0(?:1)/`,
20+
],
21+
invalid: [
22+
{
23+
code: `/(?:abcd)/`,
24+
output: `/abcd/`,
25+
errors: [
26+
{
27+
message: "Unexpected quantifier Non-capturing group.",
28+
line: 1,
29+
column: 2,
30+
endLine: 1,
31+
endColumn: 10,
32+
},
33+
],
34+
},
35+
{
36+
code: `/(?:[abcd])/`,
37+
output: `/[abcd]/`,
38+
errors: [
39+
{
40+
message: "Unexpected quantifier Non-capturing group.",
41+
line: 1,
42+
column: 2,
43+
endLine: 1,
44+
endColumn: 12,
45+
},
46+
],
47+
},
48+
{
49+
code: `/(?:[abcd]+?)/`,
50+
output: `/[abcd]+?/`,
51+
errors: [
52+
{
53+
message: "Unexpected quantifier Non-capturing group.",
54+
line: 1,
55+
column: 2,
56+
},
57+
],
58+
},
59+
{
60+
code: String.raw`/(?:0)/; /\1(?:0)/; /(?:1)/; /\1(?:1)/`,
61+
output: String.raw`/0/; /\1(?:0)/; /1/; /\1(?:1)/`,
62+
errors: [
63+
{
64+
message: "Unexpected quantifier Non-capturing group.",
65+
line: 1,
66+
column: 2,
67+
},
68+
{
69+
message: "Unexpected quantifier Non-capturing group.",
70+
line: 1,
71+
column: 22,
72+
},
73+
],
74+
},
75+
{
76+
code: String.raw`/(?:a\n)/`,
77+
output: String.raw`/a\n/`,
78+
errors: ["Unexpected quantifier Non-capturing group."],
79+
},
80+
{
81+
code: String.raw`
82+
const s = "(?:a\\n)"
83+
new RegExp(s)`,
84+
output: null,
85+
errors: ["Unexpected quantifier Non-capturing group."],
86+
},
87+
],
88+
})

0 commit comments

Comments
 (0)