From d4f02e4bf1b9ae4e1fc8f2bc4e4851ae3c36a127 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 26 Jul 2023 20:52:17 +0900 Subject: [PATCH] feat: `no-control-regex` support `v` flag (#17405) * feat: `no-control-regex` support v flag * test: add testcases * fix: false positives for `uv` flags --- lib/rules/no-control-regex.js | 17 +++++++++++++++-- tests/lib/rules/no-control-regex.js | 19 ++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/rules/no-control-regex.js b/lib/rules/no-control-regex.js index 086747f3746..dc412fcabd5 100644 --- a/lib/rules/no-control-regex.js +++ b/lib/rules/no-control-regex.js @@ -14,6 +14,16 @@ const collector = new (class { } onPatternEnter() { + + /* + * `RegExpValidator` may parse the pattern twice in one `validatePattern`. + * So `this._controlChars` should be cleared here as well. + * + * For example, the `/(?\x1f)/` regex will parse the pattern twice. + * This is based on the content described in Annex B. + * If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice. + * See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb + */ this._controlChars = []; } @@ -32,10 +42,13 @@ const collector = new (class { collectControlChars(regexpStr, flags) { const uFlag = typeof flags === "string" && flags.includes("u"); + const vFlag = typeof flags === "string" && flags.includes("v"); + + this._controlChars = []; + this._source = regexpStr; try { - this._source = regexpStr; - this._validator.validatePattern(regexpStr, void 0, void 0, uFlag); // Call onCharacter hook + this._validator.validatePattern(regexpStr, void 0, void 0, { unicode: uFlag, unicodeSets: vFlag }); // Call onCharacter hook } catch { // Ignore syntax errors in RegExp. diff --git a/tests/lib/rules/no-control-regex.js b/tests/lib/rules/no-control-regex.js index 14abfbce450..3bfc87bace1 100644 --- a/tests/lib/rules/no-control-regex.js +++ b/tests/lib/rules/no-control-regex.js @@ -33,7 +33,10 @@ ruleTester.run("no-control-regex", rule, { String.raw`new RegExp("\\u{20}", "u")`, String.raw`new RegExp("\\u{1F}")`, String.raw`new RegExp("\\u{1F}", "g")`, - String.raw`new RegExp("\\u{1F}", flags)` // when flags are unknown, this rule assumes there's no `u` flag + String.raw`new RegExp("\\u{1F}", flags)`, // when flags are unknown, this rule assumes there's no `u` flag + String.raw`new RegExp("[\\q{\\u{20}}]", "v")`, + { code: String.raw`/[\u{20}--B]/v`, parserOptions: { ecmaVersion: 2024 } } + ], invalid: [ { code: String.raw`var regex = /\x1f/`, errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }] }, @@ -85,6 +88,20 @@ ruleTester.run("no-control-regex", rule, { { code: String.raw`new RegExp("\\u{1F}", "gui")`, errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }] + }, + { + code: String.raw`new RegExp("[\\q{\\u{1F}}]", "v")`, + errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }] + }, + { + code: String.raw`/[\u{1F}--B]/v`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ messageId: "unexpected", data: { controlChars: "\\x1f" }, type: "Literal" }] + }, + { + code: String.raw`/\x11/; RegExp("foo", "uv");`, + parserOptions: { ecmaVersion: 2024 }, + errors: [{ messageId: "unexpected", data: { controlChars: "\\x11" }, type: "Literal", column: 1 }] } ] });