Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ee68d1d

Browse files
authored
feat: no-empty-character-class support v flag (#17419)
Reports nested character classes Refs #17223
1 parent 853d32b commit ee68d1d

File tree

3 files changed

+75
-14
lines changed

3 files changed

+75
-14
lines changed

docs/src/rules/no-empty-character-class.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,23 @@ Examples of **incorrect** code for this rule:
2424

2525
/^abc[]/.test("abcdefg"); // false
2626
"abcdefg".match(/^abc[]/); // null
27+
28+
/^abc[[]]/v.test("abcdefg"); // false
29+
"abcdefg".match(/^abc[[]]/v); // null
30+
31+
/^abc[[]--[x]]/v.test("abcdefg"); // false
32+
"abcdefg".match(/^abc[[]--[x]]/v); // null
33+
34+
/^abc[[d]&&[]]/v.test("abcdefg"); // false
35+
"abcdefg".match(/^abc[[d]&&[]]/v); // null
36+
37+
const regex = /^abc[d[]]/v;
38+
regex.test("abcdefg"); // true, the nested `[]` has no effect
39+
"abcdefg".match(regex); // ["abcd"]
40+
regex.test("abcefg"); // false, the nested `[]` has no effect
41+
"abcefg".match(regex); // null
42+
regex.test("abc"); // false, the nested `[]` has no effect
43+
"abc".match(regex); // null
2744
```
2845

2946
:::
@@ -40,6 +57,9 @@ Examples of **correct** code for this rule:
4057

4158
/^abc[a-z]/.test("abcdefg"); // true
4259
"abcdefg".match(/^abc[a-z]/); // ["abcd"]
60+
61+
/^abc[^]/.test("abcdefg"); // true
62+
"abcdefg".match(/^abc[^]/); // ["abcd"]
4363
```
4464

4565
:::

lib/rules/no-empty-character-class.js

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,18 @@
55

66
"use strict";
77

8+
//------------------------------------------------------------------------------
9+
// Requirements
10+
//------------------------------------------------------------------------------
11+
12+
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
13+
814
//------------------------------------------------------------------------------
915
// Helpers
1016
//------------------------------------------------------------------------------
1117

12-
/*
13-
* plain-English description of the following regexp:
14-
* 0. `^` fix the match at the beginning of the string
15-
* 1. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following
16-
* 1.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes)
17-
* 1.1. `\\.`: an escape sequence
18-
* 1.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty
19-
* 2. `$`: fix the match at the end of the string
20-
*/
21-
const regex = /^([^\\[]|\\.|\[([^\\\]]|\\.)+\])*$/u;
18+
const parser = new RegExpParser();
19+
const QUICK_TEST_REGEX = /\[\]/u;
2220

2321
//------------------------------------------------------------------------------
2422
// Rule Definition
@@ -45,9 +43,32 @@ module.exports = {
4543
create(context) {
4644
return {
4745
"Literal[regex]"(node) {
48-
if (!regex.test(node.regex.pattern)) {
49-
context.report({ node, messageId: "unexpected" });
46+
const { pattern, flags } = node.regex;
47+
48+
if (!QUICK_TEST_REGEX.test(pattern)) {
49+
return;
5050
}
51+
52+
let regExpAST;
53+
54+
try {
55+
regExpAST = parser.parsePattern(pattern, 0, pattern.length, {
56+
unicode: flags.includes("u"),
57+
unicodeSets: flags.includes("v")
58+
});
59+
} catch {
60+
61+
// Ignore regular expressions that regexpp cannot parse
62+
return;
63+
}
64+
65+
visitRegExpAST(regExpAST, {
66+
onCharacterClassEnter(characterClass) {
67+
if (!characterClass.negate && characterClass.elements.length === 0) {
68+
context.report({ node, messageId: "unexpected" });
69+
}
70+
}
71+
});
5172
}
5273
};
5374

tests/lib/rules/no-empty-character-class.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,26 @@ ruleTester.run("no-empty-character-class", rule, {
2525
"var foo = /^abc/;",
2626
"var foo = /[\\[]/;",
2727
"var foo = /[\\]]/;",
28+
"var foo = /\\[][\\]]/;",
2829
"var foo = /[a-zA-Z\\[]/;",
2930
"var foo = /[[]/;",
3031
"var foo = /[\\[a-z[]]/;",
3132
"var foo = /[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\^\\$\\|]/g;",
3233
"var foo = /\\s*:\\s*/gim;",
34+
"var foo = /[^]/;", // this rule allows negated empty character classes
35+
"var foo = /\\[][^]/;",
3336
{ code: "var foo = /[\\]]/uy;", parserOptions: { ecmaVersion: 6 } },
3437
{ code: "var foo = /[\\]]/s;", parserOptions: { ecmaVersion: 2018 } },
3538
{ code: "var foo = /[\\]]/d;", parserOptions: { ecmaVersion: 2022 } },
36-
"var foo = /\\[]/"
39+
"var foo = /\\[]/",
40+
{ code: "var foo = /[[^]]/v;", parserOptions: { ecmaVersion: 2024 } },
41+
{ code: "var foo = /[[\\]]]/v;", parserOptions: { ecmaVersion: 2024 } },
42+
{ code: "var foo = /[[\\[]]/v;", parserOptions: { ecmaVersion: 2024 } },
43+
{ code: "var foo = /[a--b]/v;", parserOptions: { ecmaVersion: 2024 } },
44+
{ code: "var foo = /[a&&b]/v;", parserOptions: { ecmaVersion: 2024 } },
45+
{ code: "var foo = /[[a][b]]/v;", parserOptions: { ecmaVersion: 2024 } },
46+
{ code: "var foo = /[\\q{}]/v;", parserOptions: { ecmaVersion: 2024 } },
47+
{ code: "var foo = /[[^]--\\p{ASCII}]/v;", parserOptions: { ecmaVersion: 2024 } }
3748
],
3849
invalid: [
3950
{ code: "var foo = /^abc[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] },
@@ -43,6 +54,15 @@ ruleTester.run("no-empty-character-class", rule, {
4354
{ code: "var foo = /[]]/;", errors: [{ messageId: "unexpected", type: "Literal" }] },
4455
{ code: "var foo = /\\[[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] },
4556
{ code: "var foo = /\\[\\[\\]a-z[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] },
46-
{ code: "var foo = /[]]/d;", parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpected", type: "Literal" }] }
57+
{ code: "var foo = /[]]/d;", parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
58+
{ code: "var foo = /[(]\\u{0}*[]/u;", parserOptions: { ecmaVersion: 2015 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
59+
{ code: "var foo = /[]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
60+
{ code: "var foo = /[[]]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
61+
{ code: "var foo = /[[a][]]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
62+
{ code: "var foo = /[a[[b[]c]]d]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
63+
{ code: "var foo = /[a--[]]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
64+
{ code: "var foo = /[[]--b]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
65+
{ code: "var foo = /[a&&[]]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
66+
{ code: "var foo = /[[]&&b]/v;", parserOptions: { ecmaVersion: 2024 }, errors: [{ messageId: "unexpected", type: "Literal" }] }
4767
]
4868
});

0 commit comments

Comments
 (0)