Skip to content

Commit e5eab5b

Browse files
authored
Add ignorePartial option to regexp/no-lazy-ends rule (#216)
1 parent 556ba2e commit e5eab5b

File tree

3 files changed

+161
-19
lines changed

3 files changed

+161
-19
lines changed

docs/rules/no-lazy-ends.md

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,78 @@ greedy quantifier. E.g. `a+b{2,4}?` and `a+b{2}` behave the same.
3333
/* eslint regexp/no-lazy-ends: "error" */
3434

3535
/* ✓ GOOD */
36-
var foo = /a+?b*/
37-
var foo = /a??(?:ba+?|c)*/
38-
var foo = /ba*?$/
36+
var foo = /a+?b*/.test(str)
37+
var foo = /a??(?:ba+?|c)*/.test(str)
38+
var foo = /ba*?$/.test(str)
3939

4040
/* ✗ BAD */
41-
var foo = /a??/
42-
var foo = /a+b+?/
43-
var foo = /a(?:c|ab+?)?/
41+
var foo = /a??/.test(str)
42+
var foo = /a+b+?/.test(str)
43+
var foo = /a(?:c|ab+?)?/.test(str)
4444
```
4545

4646
</eslint-code-block>
4747

4848
## :wrench: Options
4949

50-
Nothing.
50+
```json5
51+
{
52+
"regexp/no-lazy-ends": [
53+
"error",
54+
{
55+
"ignorePartial": true,
56+
}
57+
]
58+
}
59+
```
60+
61+
- `ignorePartial`:
62+
63+
Some regexes are used as fragments to build more complex regexes. Example:
64+
65+
```js
66+
const any = /[\s\S]*?/.source;
67+
const pattern = RegExp(`<script(\\s${any})?>(${any})<\\/script>`, "g");
68+
```
69+
70+
In these fragments, seemingly ignored quantifier might not actually be ignored depending on how the fragment is used.
71+
72+
- `true`:
73+
The rule does not check the regexp used as a fragment. This is default.
74+
75+
<eslint-code-block>
76+
77+
```js
78+
/* eslint regexp/no-lazy-ends: ["error", { ignorePartial: true }] */
79+
80+
/* ✓ GOOD */
81+
const any = /[\s\S]*?/.source;
82+
const pattern = RegExp(`<script(\\s${any})?>(${any})<\\/script>`, "g");
83+
84+
/* ✗ BAD */
85+
const foo = /[\s\S]*?/
86+
foo.exec(str)
87+
```
88+
89+
</eslint-code-block>
90+
91+
- `false`:
92+
This rule checks all regular expressions, including those used as fragments.
93+
94+
<eslint-code-block>
95+
96+
```js
97+
/* eslint regexp/no-lazy-ends: ["error", { ignorePartial: false }] */
98+
99+
/* ✗ BAD */
100+
const any = /[\s\S]*?/.source;
101+
const pattern = RegExp(`<script(\\s${any})?>(${any})<\\/script>`, "g");
102+
103+
const foo = /[\s\S]*?/
104+
foo.exec(str)
105+
```
106+
107+
</eslint-code-block>
51108

52109
## :heart: Compatibility
53110

lib/rules/no-lazy-ends.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { RegExpVisitor } from "regexpp/visitor"
22
import type { Alternative, Quantifier } from "regexpp/ast"
33
import type { RegExpContext } from "../utils"
44
import { createRule, defineRegexpVisitor } from "../utils"
5+
import { UsageOfPattern } from "../utils/get-usage-of-pattern"
56

67
/**
78
* Extract lazy end quantifiers
@@ -52,7 +53,15 @@ export default createRule("no-lazy-ends", {
5253
recommended: false,
5354
default: "warn",
5455
},
55-
schema: [],
56+
schema: [
57+
{
58+
type: "object",
59+
properties: {
60+
ignorePartial: { type: "boolean" },
61+
},
62+
additionalProperties: false,
63+
},
64+
],
5665
messages: {
5766
uselessElement:
5867
"The quantifier and the quantified element can be removed because the quantifier is lazy and has a minimum of 0.",
@@ -64,13 +73,24 @@ export default createRule("no-lazy-ends", {
6473
type: "problem",
6574
},
6675
create(context) {
76+
const ignorePartial = context.options[0]?.ignorePartial ?? true
77+
6778
/**
6879
* Create visitor
6980
*/
7081
function createVisitor({
7182
node,
7283
getRegexpLocation,
84+
getUsageOfPattern,
7385
}: RegExpContext): RegExpVisitor.Handlers {
86+
if (ignorePartial) {
87+
const usageOfPattern = getUsageOfPattern()
88+
if (usageOfPattern !== UsageOfPattern.whole) {
89+
// ignore
90+
return {}
91+
}
92+
}
93+
7494
return {
7595
onPatternEnter(pNode) {
7696
for (const lazy of extractLazyEndQuantifiers(

tests/lib/rules/no-lazy-ends.ts

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ const tester = new RuleTester({
1010

1111
tester.run("no-lazy-ends", rule as any, {
1212
valid: [
13-
`/a+?b*/`,
14-
`/a??(?:ba+?|c)*/`,
15-
`/ba*?$/`,
13+
`/a+?b*/.test(str)`,
14+
`/a??(?:ba+?|c)*/.test(str)`,
15+
`/ba*?$/.test(str)`,
16+
`/a??/`, // UsageOfPattern.unknown
1617

17-
`/a{3}?/`, // uselessly lazy but that's not for this rule to correct
18+
`/a{3}?/.test(str)`, // uselessly lazy but that's not for this rule to correct
1819
],
1920
invalid: [
2021
{
21-
code: `/a??/`,
22+
code: `/a??/.test(str)`,
2223
errors: [
2324
{
2425
message:
@@ -29,7 +30,7 @@ tester.run("no-lazy-ends", rule as any, {
2930
],
3031
},
3132
{
32-
code: `/a*?/`,
33+
code: `/a*?/.test(str)`,
3334
errors: [
3435
{
3536
message:
@@ -40,7 +41,7 @@ tester.run("no-lazy-ends", rule as any, {
4041
],
4142
},
4243
{
43-
code: `/a+?/`,
44+
code: `/a+?/.test(str)`,
4445
errors: [
4546
{
4647
message:
@@ -51,7 +52,7 @@ tester.run("no-lazy-ends", rule as any, {
5152
],
5253
},
5354
{
54-
code: `/a{3,7}?/`,
55+
code: `/a{3,7}?/.test(str)`,
5556
errors: [
5657
{
5758
message:
@@ -62,7 +63,7 @@ tester.run("no-lazy-ends", rule as any, {
6263
],
6364
},
6465
{
65-
code: `/a{3,}?/`,
66+
code: `/a{3,}?/.test(str)`,
6667
errors: [
6768
{
6869
message:
@@ -74,7 +75,7 @@ tester.run("no-lazy-ends", rule as any, {
7475
},
7576

7677
{
77-
code: `/(?:a|b(c+?))/`,
78+
code: `/(?:a|b(c+?))/.test(str)`,
7879
errors: [
7980
{
8081
message:
@@ -85,7 +86,7 @@ tester.run("no-lazy-ends", rule as any, {
8586
],
8687
},
8788
{
88-
code: `/a(?:c|ab+?)?/`,
89+
code: `/a(?:c|ab+?)?/.test(str)`,
8990
errors: [
9091
{
9192
message:
@@ -95,5 +96,69 @@ tester.run("no-lazy-ends", rule as any, {
9596
},
9697
],
9798
},
99+
{
100+
code: `
101+
/* ✓ GOOD */
102+
const any = /[\\s\\S]*?/.source;
103+
const pattern = RegExp(\`<script(\\\\s\${any})?>(\${any})<\\/script>\`, "g");
104+
105+
/* ✗ BAD */
106+
const foo = /[\\s\\S]*?/
107+
foo.exec(str)
108+
`,
109+
errors: [
110+
{
111+
message:
112+
"The quantifier and the quantified element can be removed because the quantifier is lazy and has a minimum of 0.",
113+
line: 7,
114+
column: 26,
115+
},
116+
],
117+
},
118+
{
119+
code: `
120+
/* ✓ GOOD */
121+
const any = /[\\s\\S]*?/.source;
122+
const pattern = RegExp(\`<script(\\\\s\${any})?>(\${any})<\\/script>\`, "g");
123+
124+
/* ✗ BAD */
125+
const foo = /[\\s\\S]*?/
126+
foo.exec(str)
127+
`,
128+
options: [{ ignorePartial: true }],
129+
errors: [
130+
{
131+
message:
132+
"The quantifier and the quantified element can be removed because the quantifier is lazy and has a minimum of 0.",
133+
line: 7,
134+
column: 26,
135+
},
136+
],
137+
},
138+
{
139+
code: `
140+
/* ✗ BAD */
141+
const any = /[\\s\\S]*?/.source;
142+
const pattern = RegExp(\`<script(\\\\s\${any})?>(\${any})<\\/script>\`, "g");
143+
144+
const foo = /[\\s\\S]*?/
145+
foo.exec(str)
146+
`,
147+
options: [{ ignorePartial: false }],
148+
errors: [
149+
{
150+
message:
151+
"The quantifier and the quantified element can be removed because the quantifier is lazy and has a minimum of 0.",
152+
line: 3,
153+
column: 26,
154+
},
155+
{
156+
message:
157+
"The quantifier and the quantified element can be removed because the quantifier is lazy and has a minimum of 0.",
158+
line: 6,
159+
column: 26,
160+
},
161+
],
162+
},
98163
],
99164
})

0 commit comments

Comments
 (0)