Skip to content

Commit d690a91

Browse files
Add suggestions for regexp/no-lazy-ends (#624)
* Add suggestions for `regexp/no-lazy-ends` * Create rare-spiders-drop.md * Added a little hint * Update rules.ts --------- Co-authored-by: Yosuke Ota <[email protected]>
1 parent d0b965f commit d690a91

File tree

7 files changed

+94
-5
lines changed

7 files changed

+94
-5
lines changed

.changeset/rare-spiders-drop.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-regexp": minor
3+
---
4+
5+
Add suggestions for `regexp/no-lazy-ends`

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ The `plugin:regexp/all` config enables all rules. It's meant for testing, not fo
119119
| [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 || | | |
120120
| [no-escape-backspace](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-escape-backspace.html) | disallow escape backspace (`[\b]`) || | | 💡 |
121121
| [no-invalid-regexp](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-invalid-regexp.html) | disallow invalid regular expression strings in `RegExp` constructors || | | |
122-
| [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 | || | |
122+
| [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 | || | 💡 |
123123
| [no-misleading-capturing-group](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-misleading-capturing-group.html) | disallow capturing groups that do not behave as one would expect || | | 💡 |
124124
| [no-misleading-unicode-character](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-misleading-unicode-character.html) | disallow multi-code-point characters in character classes and quantifiers || | 🔧 | 💡 |
125125
| [no-missing-g-flag](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-missing-g-flag.html) | disallow missing `g` flag in patterns used in `String#matchAll` and `String#replaceAll` || | 🔧 | |

docs/rules/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ sidebarDepth: 0
2626
| [no-empty-lookarounds-assertion](no-empty-lookarounds-assertion.md) | disallow empty lookahead assertion or empty lookbehind assertion || | | |
2727
| [no-escape-backspace](no-escape-backspace.md) | disallow escape backspace (`[\b]`) || | | 💡 |
2828
| [no-invalid-regexp](no-invalid-regexp.md) | disallow invalid regular expression strings in `RegExp` constructors || | | |
29-
| [no-lazy-ends](no-lazy-ends.md) | disallow lazy quantifiers at the end of an expression | || | |
29+
| [no-lazy-ends](no-lazy-ends.md) | disallow lazy quantifiers at the end of an expression | || | 💡 |
3030
| [no-misleading-capturing-group](no-misleading-capturing-group.md) | disallow capturing groups that do not behave as one would expect || | | 💡 |
3131
| [no-misleading-unicode-character](no-misleading-unicode-character.md) | disallow multi-code-point characters in character classes and quantifiers || | 🔧 | 💡 |
3232
| [no-missing-g-flag](no-missing-g-flag.md) | disallow missing `g` flag in patterns used in `String#matchAll` and `String#replaceAll` || | 🔧 | |

docs/rules/no-lazy-ends.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ since: "v0.8.0"
99

1010
⚠️ This rule _warns_ in the ✅ `plugin:regexp/recommended` config.
1111

12+
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
13+
1214
<!-- end auto-generated rule header -->
1315

1416
> disallow lazy quantifiers at the end of an expression

lib/rules/no-lazy-ends.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { RegExpVisitor } from "@eslint-community/regexpp/visitor"
22
import type { Alternative, Quantifier } from "@eslint-community/regexpp/ast"
33
import type { RegExpContext } from "../utils"
4+
import type { Rule } from "eslint"
45
import { createRule, defineRegexpVisitor } from "../utils"
56
import { UsageOfPattern } from "../utils/get-usage-of-pattern"
67

@@ -57,13 +58,23 @@ export default createRule("no-lazy-ends", {
5758
additionalProperties: false,
5859
},
5960
],
61+
hasSuggestions: true,
6062
messages: {
6163
uselessElement:
6264
"The quantifier and the quantified element can be removed because the quantifier is lazy and has a minimum of 0.",
6365
uselessQuantifier:
6466
"The quantifier can be removed because the quantifier is lazy and has a minimum of 1.",
6567
uselessRange:
6668
"The quantifier can be replaced with '{{{min}}}' because the quantifier is lazy and has a minimum of {{min}}.",
69+
70+
suggestMakeGreedy:
71+
"Make the quantifier greedy. (This changes the behavior of the regex.)",
72+
suggestRemoveElement:
73+
"Remove the quantified element. (This does not changes the behavior of the regex.)",
74+
suggestRemoveQuantifier:
75+
"Remove the quantifier. (This does not changes the behavior of the regex.)",
76+
suggestRange:
77+
"Replace the quantifier with '{{{min}}}'. (This does not changes the behavior of the regex.)",
6778
},
6879
type: "problem",
6980
},
@@ -74,6 +85,7 @@ export default createRule("no-lazy-ends", {
7485
node,
7586
getRegexpLocation,
7687
getUsageOfPattern,
88+
fixReplaceNode,
7789
}: RegExpContext): RegExpVisitor.Handlers {
7890
if (ignorePartial) {
7991
const usageOfPattern = getUsageOfPattern()
@@ -88,17 +100,48 @@ export default createRule("no-lazy-ends", {
88100
for (const lazy of extractLazyEndQuantifiers(
89101
pNode.alternatives,
90102
)) {
103+
const makeGreedy: Rule.SuggestionReportDescriptor = {
104+
messageId: "suggestMakeGreedy",
105+
fix: fixReplaceNode(lazy, lazy.raw.slice(0, -1)),
106+
}
107+
91108
if (lazy.min === 0) {
109+
// we have to replace the quantifier with (?:) to
110+
// avoid creating invalid patterns. E.g. /a??/ -> /(?:)/
111+
const replacement =
112+
pNode.alternatives.length === 1 &&
113+
pNode.alternatives[0].elements.length === 1 &&
114+
pNode.alternatives[0].elements[0] === lazy
115+
? "(?:)"
116+
: ""
117+
92118
context.report({
93119
node,
94120
loc: getRegexpLocation(lazy),
95121
messageId: "uselessElement",
122+
suggest: [
123+
{
124+
messageId: "suggestRemoveElement",
125+
fix: fixReplaceNode(lazy, replacement),
126+
},
127+
makeGreedy,
128+
],
96129
})
97130
} else if (lazy.min === 1) {
98131
context.report({
99132
node,
100133
loc: getRegexpLocation(lazy),
101134
messageId: "uselessQuantifier",
135+
suggest: [
136+
{
137+
messageId: "suggestRemoveQuantifier",
138+
fix: fixReplaceNode(
139+
lazy,
140+
lazy.element.raw,
141+
),
142+
},
143+
makeGreedy,
144+
],
102145
})
103146
} else {
104147
context.report({
@@ -108,6 +151,19 @@ export default createRule("no-lazy-ends", {
108151
data: {
109152
min: String(lazy.min),
110153
},
154+
suggest: [
155+
{
156+
messageId: "suggestRange",
157+
data: {
158+
min: String(lazy.min),
159+
},
160+
fix: fixReplaceNode(
161+
lazy,
162+
`${lazy.element.raw}{${lazy.min}}`,
163+
),
164+
},
165+
makeGreedy,
166+
],
111167
})
112168
}
113169
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ tester.run("no-lazy-ends", rule as any, {
3838
"The quantifier and the quantified element can be removed because the quantifier is lazy and has a minimum of 0.",
3939
line: 1,
4040
column: 2,
41+
suggestions: [
42+
{ output: "/(?:)/.test(str)" },
43+
{ output: "/a?/.test(str)" },
44+
],
4145
},
4246
],
4347
},
@@ -49,6 +53,10 @@ tester.run("no-lazy-ends", rule as any, {
4953
"The quantifier and the quantified element can be removed because the quantifier is lazy and has a minimum of 0.",
5054
line: 1,
5155
column: 2,
56+
suggestions: [
57+
{ output: "/(?:)/.test(str)" },
58+
{ output: "/a*/.test(str)" },
59+
],
5260
},
5361
],
5462
},
@@ -60,6 +68,10 @@ tester.run("no-lazy-ends", rule as any, {
6068
"The quantifier can be removed because the quantifier is lazy and has a minimum of 1.",
6169
line: 1,
6270
column: 2,
71+
suggestions: [
72+
{ output: "/a/.test(str)" },
73+
{ output: "/a+/.test(str)" },
74+
],
6375
},
6476
],
6577
},
@@ -71,6 +83,10 @@ tester.run("no-lazy-ends", rule as any, {
7183
"The quantifier can be replaced with '{3}' because the quantifier is lazy and has a minimum of 3.",
7284
line: 1,
7385
column: 2,
86+
suggestions: [
87+
{ output: "/a{3}/.test(str)" },
88+
{ output: "/a{3,7}/.test(str)" },
89+
],
7490
},
7591
],
7692
},
@@ -82,6 +98,10 @@ tester.run("no-lazy-ends", rule as any, {
8298
"The quantifier can be replaced with '{3}' because the quantifier is lazy and has a minimum of 3.",
8399
line: 1,
84100
column: 2,
101+
suggestions: [
102+
{ output: "/a{3}/.test(str)" },
103+
{ output: "/a{3,}/.test(str)" },
104+
],
85105
},
86106
],
87107
},
@@ -94,6 +114,10 @@ tester.run("no-lazy-ends", rule as any, {
94114
"The quantifier can be removed because the quantifier is lazy and has a minimum of 1.",
95115
line: 1,
96116
column: 9,
117+
suggestions: [
118+
{ output: `/(?:a|b(c))/.test(str)` },
119+
{ output: `/(?:a|b(c+))/.test(str)` },
120+
],
97121
},
98122
],
99123
},
@@ -105,6 +129,10 @@ tester.run("no-lazy-ends", rule as any, {
105129
"The quantifier can be removed because the quantifier is lazy and has a minimum of 1.",
106130
line: 1,
107131
column: 9,
132+
suggestions: [
133+
{ output: `/a(?:c|ab)?/.test(str)` },
134+
{ output: `/a(?:c|ab+)?/.test(str)` },
135+
],
108136
},
109137
],
110138
},

tests/lib/utils/rules.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ describe("Check if the strict of all rules is correct", () => {
6363
it(messageId, () => {
6464
const message = rule.meta.messages[messageId]
6565
assert.ok(
66-
message.endsWith(".") ||
67-
message.endsWith("?") ||
68-
message.endsWith("}}"),
66+
/(?:[.?]|\}\})\)?$/u.test(message),
6967
"Doesn't end with a dot.",
7068
)
7169
})

0 commit comments

Comments
 (0)