Skip to content

Commit 4696b89

Browse files
authored
Add regexp/no-empty-string-literal rule (#632)
1 parent 0e61e73 commit 4696b89

File tree

8 files changed

+156
-0
lines changed

8 files changed

+156
-0
lines changed

.changeset/hungry-eels-check.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-regexp": major
3+
---
4+
5+
Add `regexp/no-empty-string-literal` rule

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ The `plugin:regexp/all` config enables all rules. It's meant for testing, not fo
140140
| [control-character-escape](https://ota-meshi.github.io/eslint-plugin-regexp/rules/control-character-escape.html) | enforce consistent escaping of control characters || | 🔧 | |
141141
| [negation](https://ota-meshi.github.io/eslint-plugin-regexp/rules/negation.html) | enforce use of escapes on negation || | 🔧 | |
142142
| [no-dupe-characters-character-class](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-dupe-characters-character-class.html) | disallow duplicate characters in the RegExp character class || | 🔧 | |
143+
| [no-empty-string-literal](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-empty-string-literal.html) | disallow empty string literals in character classes || | | |
143144
| [no-extra-lookaround-assertions](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-extra-lookaround-assertions.html) | disallow unnecessary nested lookaround assertions || | 🔧 | |
144145
| [no-invisible-character](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-invisible-character.html) | disallow invisible raw character || | 🔧 | |
145146
| [no-legacy-features](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-legacy-features.html) | disallow legacy RegExp features || | | |

docs/rules/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ sidebarDepth: 0
4747
| [control-character-escape](control-character-escape.md) | enforce consistent escaping of control characters || | 🔧 | |
4848
| [negation](negation.md) | enforce use of escapes on negation || | 🔧 | |
4949
| [no-dupe-characters-character-class](no-dupe-characters-character-class.md) | disallow duplicate characters in the RegExp character class || | 🔧 | |
50+
| [no-empty-string-literal](no-empty-string-literal.md) | disallow empty string literals in character classes || | | |
5051
| [no-extra-lookaround-assertions](no-extra-lookaround-assertions.md) | disallow unnecessary nested lookaround assertions || | 🔧 | |
5152
| [no-invisible-character](no-invisible-character.md) | disallow invisible raw character || | 🔧 | |
5253
| [no-legacy-features](no-legacy-features.md) | disallow legacy RegExp features || | | |

docs/rules/no-empty-string-literal.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "regexp/no-empty-string-literal"
5+
description: "disallow empty string literals in character classes"
6+
---
7+
# regexp/no-empty-string-literal
8+
9+
💼 This rule is enabled in the ✅ `plugin:regexp/recommended` config.
10+
11+
<!-- end auto-generated rule header -->
12+
13+
> disallow empty string literals in character classes
14+
15+
## :book: Rule Details
16+
17+
This rule reports empty string literals in character classes.
18+
19+
If the empty string literal is supposed to match the empty string, then use a
20+
quantifier instead. For example, `[ab\q{}]` should be written as `[ab]?`.
21+
22+
This rule does not report empty alternatives in string literals. (e.g. `/[\q{a|}]/v`)\
23+
If you want to report empty alternatives in string literals, use the [regexp/no-empty-alternative] rule.
24+
25+
<eslint-code-block>
26+
27+
```js
28+
/* eslint regexp/no-empty-string-literal: "error" */
29+
30+
/* ✓ GOOD */
31+
var foo = /[\q{a}]/v;
32+
var foo = /[\q{abc}]/v;
33+
var foo = /[\q{a|}]/v;
34+
35+
/* ✗ BAD */
36+
var foo = /[\q{}]/v;
37+
var foo = /[\q{|}]/v;
38+
```
39+
40+
</eslint-code-block>
41+
42+
## :wrench: Options
43+
44+
Nothing.
45+
46+
## :couple: Related rules
47+
48+
- [regexp/no-empty-alternative]
49+
50+
[regexp/no-empty-alternative]: ./no-empty-alternative.md
51+
52+
## :rocket: Version
53+
54+
:exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
55+
56+
## :mag: Implementation
57+
58+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/rules/no-empty-string-literal.ts)
59+
- [Test source](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/tests/lib/rules/no-empty-string-literal.ts)

lib/configs/recommended.ts

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const rules = {
2323
"regexp/no-empty-character-class": "error",
2424
"regexp/no-empty-group": "error",
2525
"regexp/no-empty-lookarounds-assertion": "error",
26+
"regexp/no-empty-string-literal": "error",
2627
"regexp/no-escape-backspace": "error",
2728
"regexp/no-extra-lookaround-assertions": "error",
2829
"regexp/no-invalid-regexp": "error",

lib/rules/no-empty-string-literal.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { RegExpVisitor } from "@eslint-community/regexpp/visitor"
2+
import type { RegExpContext } from "../utils"
3+
import { createRule, defineRegexpVisitor } from "../utils"
4+
5+
export default createRule("no-empty-string-literal", {
6+
meta: {
7+
docs: {
8+
description: "disallow empty string literals in character classes",
9+
category: "Best Practices",
10+
recommended: true,
11+
},
12+
schema: [],
13+
messages: {
14+
unexpected: "Unexpected empty string literal.",
15+
},
16+
type: "suggestion", // "problem",
17+
},
18+
create(context) {
19+
function createVisitor(
20+
regexpContext: RegExpContext,
21+
): RegExpVisitor.Handlers {
22+
const { node, getRegexpLocation } = regexpContext
23+
24+
return {
25+
onClassStringDisjunctionEnter(csdNode) {
26+
if (
27+
csdNode.alternatives.every(
28+
(alt) => alt.elements.length === 0,
29+
)
30+
) {
31+
context.report({
32+
node,
33+
loc: getRegexpLocation(csdNode),
34+
messageId: "unexpected",
35+
})
36+
}
37+
},
38+
}
39+
}
40+
41+
return defineRegexpVisitor(context, {
42+
createVisitor,
43+
})
44+
},
45+
})

lib/utils/rules.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import noEmptyCapturingGroup from "../rules/no-empty-capturing-group"
1515
import noEmptyCharacterClass from "../rules/no-empty-character-class"
1616
import noEmptyGroup from "../rules/no-empty-group"
1717
import noEmptyLookaroundsAssertion from "../rules/no-empty-lookarounds-assertion"
18+
import noEmptyStringLiteral from "../rules/no-empty-string-literal"
1819
import noEscapeBackspace from "../rules/no-escape-backspace"
1920
import noExtraLookaroundAssertions from "../rules/no-extra-lookaround-assertions"
2021
import noInvalidRegexp from "../rules/no-invalid-regexp"
@@ -99,6 +100,7 @@ export const rules = [
99100
noEmptyCharacterClass,
100101
noEmptyGroup,
101102
noEmptyLookaroundsAssertion,
103+
noEmptyStringLiteral,
102104
noEscapeBackspace,
103105
noExtraLookaroundAssertions,
104106
noInvalidRegexp,
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { RuleTester } from "eslint"
2+
import rule from "../../../lib/rules/no-empty-string-literal"
3+
4+
const tester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: "latest",
7+
sourceType: "module",
8+
},
9+
})
10+
11+
tester.run("no-empty-string-literal", rule as any, {
12+
valid: [
13+
String.raw`/[\q{a}]/v`,
14+
String.raw`/[\q{abc}]/v`,
15+
String.raw`/[\q{a|}]/v`,
16+
String.raw`/[\q{abc|}]/v`,
17+
String.raw`/[\q{|a}]/v`,
18+
String.raw`/[\q{|abc}]/v`,
19+
],
20+
invalid: [
21+
{
22+
code: String.raw`/[\q{}]/v`,
23+
errors: [
24+
{
25+
message: "Unexpected empty string literal.",
26+
column: 3,
27+
endColumn: 7,
28+
},
29+
],
30+
},
31+
{
32+
code: String.raw`/[\q{|}]/v`,
33+
errors: [
34+
{
35+
message: "Unexpected empty string literal.",
36+
column: 3,
37+
endColumn: 8,
38+
},
39+
],
40+
},
41+
],
42+
})

0 commit comments

Comments
 (0)