Skip to content

Commit d4f02e4

Browse files
authored
feat: no-control-regex support v flag (#17405)
* feat: `no-control-regex` support v flag * test: add testcases * fix: false positives for `uv` flags
1 parent 9254a6c commit d4f02e4

2 files changed

Lines changed: 33 additions & 3 deletions

File tree

lib/rules/no-control-regex.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ const collector = new (class {
1414
}
1515

1616
onPatternEnter() {
17+
18+
/*
19+
* `RegExpValidator` may parse the pattern twice in one `validatePattern`.
20+
* So `this._controlChars` should be cleared here as well.
21+
*
22+
* For example, the `/(?<a>\x1f)/` regex will parse the pattern twice.
23+
* This is based on the content described in Annex B.
24+
* If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice.
25+
* See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb
26+
*/
1727
this._controlChars = [];
1828
}
1929

@@ -32,10 +42,13 @@ const collector = new (class {
3242

3343
collectControlChars(regexpStr, flags) {
3444
const uFlag = typeof flags === "string" && flags.includes("u");
45+
const vFlag = typeof flags === "string" && flags.includes("v");
46+
47+
this._controlChars = [];
48+
this._source = regexpStr;
3549

3650
try {
37-
this._source = regexpStr;
38-
this._validator.validatePattern(regexpStr, void 0, void 0, uFlag); // Call onCharacter hook
51+
this._validator.validatePattern(regexpStr, void 0, void 0, { unicode: uFlag, unicodeSets: vFlag }); // Call onCharacter hook
3952
} catch {
4053

4154
// Ignore syntax errors in RegExp.

tests/lib/rules/no-control-regex.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ ruleTester.run("no-control-regex", rule, {
3333
String.raw`new RegExp("\\u{20}", "u")`,
3434
String.raw`new RegExp("\\u{1F}")`,
3535
String.raw`new RegExp("\\u{1F}", "g")`,
36-
String.raw`new RegExp("\\u{1F}", flags)` // when flags are unknown, this rule assumes there's no `u` flag
36+
String.raw`new RegExp("\\u{1F}", flags)`, // when flags are unknown, this rule assumes there's no `u` flag
37+
String.raw`new RegExp("[\\q{\\u{20}}]", "v")`,
38+
{ code: String.raw`/[\u{20}--B]/v`, parserOptions: { ecmaVersion: 2024 } }
39+
3740
],
3841
invalid: [
3942
{ code: String.raw`var regex = /\x1f/`, errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }] },
@@ -85,6 +88,20 @@ ruleTester.run("no-control-regex", rule, {
8588
{
8689
code: String.raw`new RegExp("\\u{1F}", "gui")`,
8790
errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }]
91+
},
92+
{
93+
code: String.raw`new RegExp("[\\q{\\u{1F}}]", "v")`,
94+
errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }]
95+
},
96+
{
97+
code: String.raw`/[\u{1F}--B]/v`,
98+
parserOptions: { ecmaVersion: 2024 },
99+
errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }]
100+
},
101+
{
102+
code: String.raw`/\x11/; RegExp("foo", "uv");`,
103+
parserOptions: { ecmaVersion: 2024 },
104+
errors: [{ messageId: "unexpected", data: { controlChars: "\\x11" }, type: "Literal", column: 1 }]
88105
}
89106
]
90107
});

0 commit comments

Comments
 (0)