Skip to content

Commit a483ecb

Browse files
authored
Add regexp/no-potentially-useless-backreference rule (#136)
1 parent 227345b commit a483ecb

File tree

6 files changed

+210
-0
lines changed

6 files changed

+210
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
9898
| [regexp/no-lazy-ends](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-lazy-ends.html) | disallow lazy quantifiers at the end of an expression | |
9999
| [regexp/no-legacy-features](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-legacy-features.html) | disallow legacy RegExp features | |
100100
| [regexp/no-octal](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-octal.html) | disallow octal escape sequence | :star: |
101+
| [regexp/no-potentially-useless-backreference](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-potentially-useless-backreference.html) | disallow backreferences that reference a group that might not be matched | |
101102
| [regexp/no-trivially-nested-assertion](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-trivially-nested-assertion.html) | disallow trivially nested assertions | :wrench: |
102103
| [regexp/no-unused-capturing-group](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-unused-capturing-group.html) | disallow unused capturing group | |
103104
| [regexp/no-useless-backreference](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-backreference.html) | disallow useless backreferences in regular expressions | :star: |

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ The rules with the following star :star: are included in the `plugin:regexp/reco
2626
| [regexp/no-lazy-ends](./no-lazy-ends.md) | disallow lazy quantifiers at the end of an expression | |
2727
| [regexp/no-legacy-features](./no-legacy-features.md) | disallow legacy RegExp features | |
2828
| [regexp/no-octal](./no-octal.md) | disallow octal escape sequence | :star: |
29+
| [regexp/no-potentially-useless-backreference](./no-potentially-useless-backreference.md) | disallow backreferences that reference a group that might not be matched | |
2930
| [regexp/no-trivially-nested-assertion](./no-trivially-nested-assertion.md) | disallow trivially nested assertions | :wrench: |
3031
| [regexp/no-unused-capturing-group](./no-unused-capturing-group.md) | disallow unused capturing group | |
3132
| [regexp/no-useless-backreference](./no-useless-backreference.md) | disallow useless backreferences in regular expressions | :star: |
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "regexp/no-potentially-useless-backreference"
5+
description: "disallow backreferences that reference a group that might not be matched"
6+
---
7+
# regexp/no-potentially-useless-backreference
8+
9+
> disallow backreferences that reference a group that might not be matched
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+
If the referenced group of a backreference is not matched because some other path leads to the backreference, the backreference will trivially accept (e.g. `/(?:(a)|b)\1/`). The same will happen if the captured text of the referenced group was reset before reaching the backreference.
16+
17+
This will not handle backreferences that always trivially accept. Use [regexp/no-useless-backreference] for that.
18+
19+
<eslint-code-block>
20+
21+
```js
22+
/* eslint regexp/no-potentially-useless-backreference: "error" */
23+
24+
/* ✓ GOOD */
25+
var foo = /(a+)b\1/;
26+
var foo = /(a+)b|\1/; // this will be done by regexp/no-useless-backreference
27+
28+
29+
/* ✗ BAD */
30+
var foo = /(?:(a)|b)\1/;
31+
var foo = /(a)?b\1/;
32+
var foo = /((a)|c)+b\2/;
33+
```
34+
35+
</eslint-code-block>
36+
37+
## :wrench: Options
38+
39+
Nothing.
40+
41+
## :couple: Related rules
42+
43+
- [regexp/no-useless-backreference]
44+
45+
[regexp/no-useless-backreference]: ./no-useless-backreference.md
46+
47+
## :heart: Compatibility
48+
49+
This rule was taken from [eslint-plugin-clean-regex].
50+
This rule is compatible with [clean-regex/no-potentially-empty-backreference] rule.
51+
52+
[eslint-plugin-clean-regex]: https://github.com/RunDevelopment/eslint-plugin-clean-regex
53+
[clean-regex/no-potentially-empty-backreference]: https://github.com/RunDevelopment/eslint-plugin-clean-regex/blob/master/docs/rules/no-potentially-empty-backreference.md
54+
55+
## :mag: Implementation
56+
57+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-potentially-useless-backreference.ts)
58+
- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-potentially-useless-backreference.ts)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { Expression } from "estree"
2+
import type { RegExpVisitor } from "regexpp/visitor"
3+
import { createRule, defineRegexpVisitor, getRegexpLocation } from "../utils"
4+
import {
5+
isEmptyBackreference,
6+
isStrictBackreference,
7+
} from "regexp-ast-analysis"
8+
9+
export default createRule("no-potentially-useless-backreference", {
10+
meta: {
11+
docs: {
12+
description:
13+
"disallow backreferences that reference a group that might not be matched",
14+
// TODO Switch to recommended in the major version.
15+
// recommended: true,
16+
recommended: false,
17+
default: "warn",
18+
},
19+
schema: [],
20+
messages: {
21+
potentiallyUselessBackreference:
22+
"Some paths leading to the backreference do not go through the referenced capturing group or the captured text might be reset before reaching the backreference.",
23+
},
24+
type: "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+
onBackreferenceEnter(backreference) {
36+
if (isEmptyBackreference(backreference)) {
37+
// handled by regexp/no-useless-backreference
38+
return
39+
}
40+
41+
if (!isStrictBackreference(backreference)) {
42+
context.report({
43+
node,
44+
loc: getRegexpLocation(
45+
sourceCode,
46+
node,
47+
backreference,
48+
),
49+
messageId: "potentiallyUselessBackreference",
50+
})
51+
}
52+
},
53+
}
54+
}
55+
56+
return defineRegexpVisitor(context, {
57+
createVisitor,
58+
})
59+
},
60+
})

lib/utils/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import noInvisibleCharacter from "../rules/no-invisible-character"
1414
import noLazyEnds from "../rules/no-lazy-ends"
1515
import noLegacyFeatures from "../rules/no-legacy-features"
1616
import noOctal from "../rules/no-octal"
17+
import noPotentiallyUselessBackreference from "../rules/no-potentially-useless-backreference"
1718
import noTriviallyNestedAssertion from "../rules/no-trivially-nested-assertion"
1819
import noUnusedCapturingGroup from "../rules/no-unused-capturing-group"
1920
import noUselessBackreference from "../rules/no-useless-backreference"
@@ -57,6 +58,7 @@ export const rules = [
5758
noLazyEnds,
5859
noLegacyFeatures,
5960
noOctal,
61+
noPotentiallyUselessBackreference,
6062
noTriviallyNestedAssertion,
6163
noUnusedCapturingGroup,
6264
noUselessBackreference,
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-potentially-useless-backreference"
3+
4+
const tester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 2020,
7+
sourceType: "module",
8+
},
9+
})
10+
11+
tester.run("no-potentially-useless-backreference", rule as any, {
12+
valid: [
13+
String.raw`/()\1/`,
14+
String.raw`/(a*)(?:a|\1)/`,
15+
String.raw`/(a)+\1/`,
16+
String.raw`/(?=(a))\1/`,
17+
18+
// done by regexp/no-useless-backreference
19+
String.raw`/(a+)b|\1/`,
20+
],
21+
invalid: [
22+
{
23+
code: String.raw`
24+
var foo = /(a+)b\1/;
25+
26+
var foo = /(a)?b\1/;
27+
var foo = /((a)|c)+b\2/;`,
28+
errors: [
29+
{
30+
message:
31+
"Some paths leading to the backreference do not go through the referenced capturing group or the captured text might be reset before reaching the backreference.",
32+
line: 4,
33+
column: 29,
34+
},
35+
{
36+
message:
37+
"Some paths leading to the backreference do not go through the referenced capturing group or the captured text might be reset before reaching the backreference.",
38+
line: 5,
39+
column: 33,
40+
},
41+
],
42+
},
43+
{
44+
code: String.raw`/(a)?\1/`,
45+
errors: [
46+
{
47+
message:
48+
"Some paths leading to the backreference do not go through the referenced capturing group or the captured text might be reset before reaching the backreference.",
49+
line: 1,
50+
column: 6,
51+
},
52+
],
53+
},
54+
{
55+
code: String.raw`/(a)*\1/`,
56+
errors: [
57+
{
58+
message:
59+
"Some paths leading to the backreference do not go through the referenced capturing group or the captured text might be reset before reaching the backreference.",
60+
line: 1,
61+
column: 6,
62+
},
63+
],
64+
},
65+
{
66+
code: String.raw`/(?:(a)|b)\1/`,
67+
errors: [
68+
{
69+
message:
70+
"Some paths leading to the backreference do not go through the referenced capturing group or the captured text might be reset before reaching the backreference.",
71+
line: 1,
72+
column: 11,
73+
},
74+
],
75+
},
76+
{
77+
code: String.raw`/(?:(a)|b)+\1/`,
78+
errors: [
79+
{
80+
message:
81+
"Some paths leading to the backreference do not go through the referenced capturing group or the captured text might be reset before reaching the backreference.",
82+
line: 1,
83+
column: 12,
84+
},
85+
],
86+
},
87+
],
88+
})

0 commit comments

Comments
 (0)