From af13e12730dda21d9a5c4f2d777e2b1d0d023d2e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 21 Mar 2023 17:13:29 -0400 Subject: [PATCH 1/9] feat: `require-unicode-regexp` add suggestions --- lib/rules/require-unicode-regexp.js | 44 +++++++- tests/lib/rules/require-unicode-regexp.js | 130 +++++++++++++++++++--- 2 files changed, 159 insertions(+), 15 deletions(-) diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index efac2519fe1..47320826756 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -31,7 +31,10 @@ module.exports = { url: "https://eslint.org/docs/rules/require-unicode-regexp" }, + hasSuggestions: true, + messages: { + addUFlag: "Add the 'u' flag.", requireUFlag: "Use the 'u' flag." }, @@ -44,12 +47,24 @@ module.exports = { const flags = node.regex.flags || ""; if (!flags.includes("u")) { - context.report({ node, messageId: "requireUFlag" }); + context.report({ + messageId: "requireUFlag", + node, + suggest: [ + { + fix(fixer) { + return fixer.insertTextAfter(node, "u"); + }, + messageId: "addUFlag" + } + ] + }); } }, Program() { const scope = context.getScope(); + const sourceCode = context.getSourceCode(); const tracker = new ReferenceTracker(scope); const trackMap = { RegExp: { [CALL]: true, [CONSTRUCT]: true } @@ -60,7 +75,32 @@ module.exports = { const flags = getStringIfConstant(flagsNode, scope); if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) { - context.report({ node, messageId: "requireUFlag" }); + context.report({ + messageId: "requireUFlag", + node, + suggest: [ + { + fix(fixer) { + if (flagsNode) { + if (flagsNode.type === "Literal") { + const flagsNodeText = sourceCode.getText(flagsNode); + + return fixer.replaceText(flagsNode, [ + flagsNodeText.slice(0, flagsNodeText.length - 1), + flagsNodeText.slice(flagsNodeText.length - 1) + ].join("u")); + } + + return fixer.insertTextAfter(node.arguments[1], " + \"u\""); + } + + return fixer.insertTextAfter(node.arguments[0], ", \"u\""); + + }, + messageId: "addUFlag" + } + ] + }); } } } diff --git a/tests/lib/rules/require-unicode-regexp.js b/tests/lib/rules/require-unicode-regexp.js index 16b6be4ff11..637d0d903ab 100644 --- a/tests/lib/rules/require-unicode-regexp.js +++ b/tests/lib/rules/require-unicode-regexp.js @@ -46,58 +46,162 @@ ruleTester.run("require-unicode-regexp", rule, { invalid: [ { code: "/foo/", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "/foo/u" + } + ] + }] }, { code: "/foo/gimy", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "/foo/gimyu" + } + ] + }] }, { code: "RegExp('foo')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', \"u\")" + } + ] + }] }, { code: "RegExp('foo', '')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', 'u')" + } + ] + }] }, { code: "RegExp('foo', 'gimy')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', 'gimyu')" + } + ] + }] }, { code: "new RegExp('foo')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', \"u\")" + } + ] + }] }, { code: "new RegExp('foo', '')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', 'u')" + } + ] + }] }, { code: "new RegExp('foo', 'gimy')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', 'gimyu')" + } + ] + }] }, { code: "const flags = 'gi'; new RegExp('foo', flags)", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "const flags = 'gi'; new RegExp('foo', flags + \"u\")" + } + ] + }] }, { code: "const flags = 'gimu'; new RegExp('foo', flags[0])", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "const flags = 'gimu'; new RegExp('foo', flags[0] + \"u\")" + } + ] + }] }, { code: "new window.RegExp('foo')", env: { browser: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new window.RegExp('foo', \"u\")" + } + ] + }] }, { code: "new global.RegExp('foo')", env: { node: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new global.RegExp('foo', \"u\")" + } + ] + }] }, { code: "new globalThis.RegExp('foo')", env: { es2020: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new globalThis.RegExp('foo', \"u\")" + } + ] + }] } ] }); From 238ea0dc819d24e1fa452011f3d05fe66692b2ff Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 22 Mar 2023 13:29:30 -0400 Subject: [PATCH 2/9] Review fixups: invalid patterns; sequence expressions --- lib/rules/no-misleading-character-class.js | 37 ++-------- lib/rules/prefer-regex-literals.js | 3 +- lib/rules/require-unicode-regexp.js | 71 +++++++++++-------- .../utils/unicode/regular-expressions.js | 33 +++++++++ tests/lib/rules/require-unicode-regexp.js | 50 +++++++++++++ 5 files changed, 130 insertions(+), 64 deletions(-) create mode 100644 lib/rules/utils/unicode/regular-expressions.js diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 9aa7079e538..1bac169e4b8 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -4,16 +4,15 @@ "use strict"; const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); -const { RegExpValidator, RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); +const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); const astUtils = require("./utils/ast-utils.js"); +const isValidWithUnicodeFlag = require("./utils/unicode/regular-expressions"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const REGEXPP_LATEST_ECMA_VERSION = 2022; - /** * Iterate character sequences of a given nodes. * @@ -185,38 +184,10 @@ module.exports = { } } - /** - * Checks if the given regular expression pattern would be valid with the `u` flag. - * @param {string} pattern The regular expression pattern to verify. - * @returns {boolean} `true` if the pattern would be valid with the `u` flag. - * `false` if the pattern would be invalid with the `u` flag or the configured - * ecmaVersion doesn't support the `u` flag. - */ - function isValidWithUnicodeFlag(pattern) { - const { ecmaVersion } = context.languageOptions; - - // ecmaVersion <= 5 doesn't support the 'u' flag - if (ecmaVersion <= 5) { - return false; - } - - const validator = new RegExpValidator({ - ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION) - }); - - try { - validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); - } catch { - return false; - } - - return true; - } - return { "Literal[regex]"(node) { verify(node, node.regex.pattern, node.regex.flags, fixer => { - if (!isValidWithUnicodeFlag(node.regex.pattern)) { + if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) { return null; } @@ -242,7 +213,7 @@ module.exports = { if (typeof pattern === "string") { verify(node, pattern, flags || "", fixer => { - if (!isValidWithUnicodeFlag(pattern)) { + if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) { return null; } diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index c9948658cb1..0c762efb506 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -13,13 +13,12 @@ const astUtils = require("./utils/ast-utils"); const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("@eslint-community/eslint-utils"); const { RegExpValidator, visitRegExpAST, RegExpParser } = require("@eslint-community/regexpp"); const { canTokensBeAdjacent } = require("./utils/ast-utils"); +const { REGEXPP_LATEST_ECMA_VERSION } = require("./utils/unicode/regular-expressions"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const REGEXPP_LATEST_ECMA_VERSION = 2022; - /** * Determines whether the given node is a string literal. * @param {ASTNode} node Node to check. diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index 47320826756..d4f4335fa69 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -15,6 +15,8 @@ const { ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); +const astUtils = require("./utils/ast-utils.js"); +const isValidWithUnicodeFlag = require("./utils/unicode/regular-expressions"); //------------------------------------------------------------------------------ // Rule Definition @@ -50,14 +52,16 @@ module.exports = { context.report({ messageId: "requireUFlag", node, - suggest: [ - { - fix(fixer) { - return fixer.insertTextAfter(node, "u"); - }, - messageId: "addUFlag" - } - ] + suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern) + ? [ + { + fix(fixer) { + return fixer.insertTextAfter(node, "u"); + }, + messageId: "addUFlag" + } + ] + : null }); } }, @@ -71,35 +75,44 @@ module.exports = { }; for (const { node } of tracker.iterateGlobalReferences(trackMap)) { - const flagsNode = node.arguments[1]; + const [patternNode, flagsNode] = node.arguments; + const pattern = getStringIfConstant(patternNode, scope); const flags = getStringIfConstant(flagsNode, scope); if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) { context.report({ messageId: "requireUFlag", node, - suggest: [ - { - fix(fixer) { - if (flagsNode) { - if (flagsNode.type === "Literal") { - const flagsNodeText = sourceCode.getText(flagsNode); - - return fixer.replaceText(flagsNode, [ - flagsNodeText.slice(0, flagsNodeText.length - 1), - flagsNodeText.slice(flagsNodeText.length - 1) - ].join("u")); + suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern) + ? [ + { + fix(fixer) { + if (flagsNode) { + if (flagsNode.type === "Literal") { + const flagsNodeText = sourceCode.getText(flagsNode); + + return fixer.replaceText(flagsNode, [ + flagsNodeText.slice(0, flagsNodeText.length - 1), + flagsNodeText.slice(flagsNodeText.length - 1) + ].join("u")); + } + + return fixer.insertTextAfter(node.arguments[1], " + \"u\""); } - return fixer.insertTextAfter(node.arguments[1], " + \"u\""); - } - - return fixer.insertTextAfter(node.arguments[0], ", \"u\""); - - }, - messageId: "addUFlag" - } - ] + const penultimateToken = sourceCode.getLastToken(node, { skip: 1 }); // skip closing parenthesis + + return fixer.insertTextAfter( + penultimateToken, + astUtils.isCommaToken(penultimateToken) + ? ' "u",' + : ', "u"' + ); + }, + messageId: "addUFlag" + } + ] + : null }); } } diff --git a/lib/rules/utils/unicode/regular-expressions.js b/lib/rules/utils/unicode/regular-expressions.js new file mode 100644 index 00000000000..cf06e12d571 --- /dev/null +++ b/lib/rules/utils/unicode/regular-expressions.js @@ -0,0 +1,33 @@ +"use strict"; + +const { RegExpValidator } = require("@eslint-community/regexpp"); + +const REGEXPP_LATEST_ECMA_VERSION = 2022; + +module.exports.REGEXPP_LATEST_ECMA_VERSION = REGEXPP_LATEST_ECMA_VERSION; + +/** + * Checks if the given regular expression pattern would be valid with the `u` flag. + * @param {number} ecmaVersion ECMAScript version to parse in. + * @param {string} pattern The regular expression pattern to verify. + * @returns {boolean} `true` if the pattern would be valid with the `u` flag. + * `false` if the pattern would be invalid with the `u` flag or the configured + * ecmaVersion doesn't support the `u` flag. + */ +module.exports = function isValidWithUnicodeFlag(ecmaVersion, pattern) { + if (ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag + return false; + } + + const validator = new RegExpValidator({ + ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION) + }); + + try { + validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); + } catch { + return false; + } + + return true; +}; diff --git a/tests/lib/rules/require-unicode-regexp.js b/tests/lib/rules/require-unicode-regexp.js index 637d0d903ab..adb3a1cb186 100644 --- a/tests/lib/rules/require-unicode-regexp.js +++ b/tests/lib/rules/require-unicode-regexp.js @@ -44,6 +44,13 @@ ruleTester.run("require-unicode-regexp", rule, { { code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } } ], invalid: [ + { + code: "/\\a/", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, { code: "/foo/", errors: [{ @@ -80,6 +87,13 @@ ruleTester.run("require-unicode-regexp", rule, { ] }] }, + { + code: "RegExp('\\\\a')", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, { code: "RegExp('foo', '')", errors: [{ @@ -140,6 +154,30 @@ ruleTester.run("require-unicode-regexp", rule, { ] }] }, + { + code: "new RegExp(('foo'))", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp(('foo'), \"u\")" + } + ] + }] + }, + { + code: "new RegExp(('unrelated', 'foo'))", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp(('unrelated', 'foo'), \"u\")" + } + ] + }] + }, { code: "const flags = 'gi'; new RegExp('foo', flags)", errors: [{ @@ -152,6 +190,18 @@ ruleTester.run("require-unicode-regexp", rule, { ] }] }, + { + code: "const flags = 'gi'; new RegExp('foo', ('unrelated', flags))", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "const flags = 'gi'; new RegExp('foo', ('unrelated', flags + \"u\"))" + } + ] + }] + }, { code: "const flags = 'gimu'; new RegExp('foo', flags[0])", errors: [{ From 114c58a833abbeb1f0198b1b65f49507a5dad531 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 22 Mar 2023 13:45:25 -0400 Subject: [PATCH 3/9] I promise I know how CJS modules work --- lib/rules/no-misleading-character-class.js | 2 +- lib/rules/require-unicode-regexp.js | 2 +- lib/rules/utils/unicode/regular-expressions.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 1bac169e4b8..de0a71534c3 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -7,7 +7,7 @@ const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@esl const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); const astUtils = require("./utils/ast-utils.js"); -const isValidWithUnicodeFlag = require("./utils/unicode/regular-expressions"); +const { isValidWithUnicodeFlag } = require("./utils/unicode/regular-expressions"); //------------------------------------------------------------------------------ // Helpers diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index d4f4335fa69..033ef4c9c85 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -16,7 +16,7 @@ const { getStringIfConstant } = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils.js"); -const isValidWithUnicodeFlag = require("./utils/unicode/regular-expressions"); +const { isValidWithUnicodeFlag } = require("./utils/unicode/regular-expressions"); //------------------------------------------------------------------------------ // Rule Definition diff --git a/lib/rules/utils/unicode/regular-expressions.js b/lib/rules/utils/unicode/regular-expressions.js index cf06e12d571..a259899612d 100644 --- a/lib/rules/utils/unicode/regular-expressions.js +++ b/lib/rules/utils/unicode/regular-expressions.js @@ -4,7 +4,7 @@ const { RegExpValidator } = require("@eslint-community/regexpp"); const REGEXPP_LATEST_ECMA_VERSION = 2022; -module.exports.REGEXPP_LATEST_ECMA_VERSION = REGEXPP_LATEST_ECMA_VERSION; +module.exports.REGEXPP_LATEST_ECMA_VERSION = 2022; /** * Checks if the given regular expression pattern would be valid with the `u` flag. @@ -14,7 +14,7 @@ module.exports.REGEXPP_LATEST_ECMA_VERSION = REGEXPP_LATEST_ECMA_VERSION; * `false` if the pattern would be invalid with the `u` flag or the configured * ecmaVersion doesn't support the `u` flag. */ -module.exports = function isValidWithUnicodeFlag(ecmaVersion, pattern) { +module.exports.isValidWithUnicodeFlag = function isValidWithUnicodeFlag(ecmaVersion, pattern) { if (ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag return false; } From bfc0bae56f301cd092bd44bb2031aaacacaa9e94 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 22 Mar 2023 14:55:37 -0400 Subject: [PATCH 4/9] Handled non-string literals --- lib/rules/require-unicode-regexp.js | 2 +- tests/lib/rules/require-unicode-regexp.js | 24 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index 033ef4c9c85..69939e6fcd5 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -88,7 +88,7 @@ module.exports = { { fix(fixer) { if (flagsNode) { - if (flagsNode.type === "Literal") { + if (flagsNode.type === "Literal" && typeof flagsNode.value === "string") { const flagsNodeText = sourceCode.getText(flagsNode); return fixer.replaceText(flagsNode, [ diff --git a/tests/lib/rules/require-unicode-regexp.js b/tests/lib/rules/require-unicode-regexp.js index adb3a1cb186..3fb760cba87 100644 --- a/tests/lib/rules/require-unicode-regexp.js +++ b/tests/lib/rules/require-unicode-regexp.js @@ -130,6 +130,30 @@ ruleTester.run("require-unicode-regexp", rule, { ] }] }, + { + code: "new RegExp('foo', false)", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', false + \"u\")" + } + ] + }] + }, + { + code: "new RegExp('foo', 1)", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', 1 + \"u\")" + } + ] + }] + }, { code: "new RegExp('foo', '')", errors: [{ From 99876d70b7487df44c43eb599b3f17481b748fd9 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 23 Mar 2023 08:25:50 -0400 Subject: [PATCH 5/9] Update lib/rules/require-unicode-regexp.js Co-authored-by: Milos Djermanovic --- lib/rules/require-unicode-regexp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index 69939e6fcd5..08aa284c4a8 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -88,7 +88,7 @@ module.exports = { { fix(fixer) { if (flagsNode) { - if (flagsNode.type === "Literal" && typeof flagsNode.value === "string") { + if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") { const flagsNodeText = sourceCode.getText(flagsNode); return fixer.replaceText(flagsNode, [ From adb304ef3c88073c0e45290b02e93a832a6fec8f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 23 Mar 2023 08:30:57 -0400 Subject: [PATCH 6/9] Don't concatenate + 'u' --- lib/rules/no-misleading-character-class.js | 2 +- lib/rules/prefer-regex-literals.js | 2 +- lib/rules/require-unicode-regexp.js | 5 +-- .../{unicode => }/regular-expressions.js | 0 tests/lib/rules/require-unicode-regexp.js | 35 +++---------------- 5 files changed, 10 insertions(+), 34 deletions(-) rename lib/rules/utils/{unicode => }/regular-expressions.js (100%) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index de0a71534c3..e40bafa3d3b 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -7,7 +7,7 @@ const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@esl const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); const astUtils = require("./utils/ast-utils.js"); -const { isValidWithUnicodeFlag } = require("./utils/unicode/regular-expressions"); +const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Helpers diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index 0c762efb506..3be5f540bec 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -13,7 +13,7 @@ const astUtils = require("./utils/ast-utils"); const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("@eslint-community/eslint-utils"); const { RegExpValidator, visitRegExpAST, RegExpParser } = require("@eslint-community/regexpp"); const { canTokensBeAdjacent } = require("./utils/ast-utils"); -const { REGEXPP_LATEST_ECMA_VERSION } = require("./utils/unicode/regular-expressions"); +const { REGEXPP_LATEST_ECMA_VERSION } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Helpers diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index 08aa284c4a8..ae79ac21cd2 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -16,7 +16,7 @@ const { getStringIfConstant } = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils.js"); -const { isValidWithUnicodeFlag } = require("./utils/unicode/regular-expressions"); +const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Rule Definition @@ -97,7 +97,8 @@ module.exports = { ].join("u")); } - return fixer.insertTextAfter(node.arguments[1], " + \"u\""); + // We intentionally don't suggest concatenating + "u" to non-literals + return null; } const penultimateToken = sourceCode.getLastToken(node, { skip: 1 }); // skip closing parenthesis diff --git a/lib/rules/utils/unicode/regular-expressions.js b/lib/rules/utils/regular-expressions.js similarity index 100% rename from lib/rules/utils/unicode/regular-expressions.js rename to lib/rules/utils/regular-expressions.js diff --git a/tests/lib/rules/require-unicode-regexp.js b/tests/lib/rules/require-unicode-regexp.js index 3fb760cba87..34efaa6d601 100644 --- a/tests/lib/rules/require-unicode-regexp.js +++ b/tests/lib/rules/require-unicode-regexp.js @@ -134,24 +134,14 @@ ruleTester.run("require-unicode-regexp", rule, { code: "new RegExp('foo', false)", errors: [{ messageId: "requireUFlag", - suggestions: [ - { - messageId: "addUFlag", - output: "new RegExp('foo', false + \"u\")" - } - ] + suggestions: null }] }, { code: "new RegExp('foo', 1)", errors: [{ messageId: "requireUFlag", - suggestions: [ - { - messageId: "addUFlag", - output: "new RegExp('foo', 1 + \"u\")" - } - ] + suggestions: null }] }, { @@ -206,36 +196,21 @@ ruleTester.run("require-unicode-regexp", rule, { code: "const flags = 'gi'; new RegExp('foo', flags)", errors: [{ messageId: "requireUFlag", - suggestions: [ - { - messageId: "addUFlag", - output: "const flags = 'gi'; new RegExp('foo', flags + \"u\")" - } - ] + suggestions: null }] }, { code: "const flags = 'gi'; new RegExp('foo', ('unrelated', flags))", errors: [{ messageId: "requireUFlag", - suggestions: [ - { - messageId: "addUFlag", - output: "const flags = 'gi'; new RegExp('foo', ('unrelated', flags + \"u\"))" - } - ] + suggestions: null }] }, { code: "const flags = 'gimu'; new RegExp('foo', flags[0])", errors: [{ messageId: "requireUFlag", - suggestions: [ - { - messageId: "addUFlag", - output: "const flags = 'gimu'; new RegExp('foo', flags[0] + \"u\")" - } - ] + suggestions: null }] }, { From 20e4feb126b44f4bb004be59e5e21bad153ea9a1 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 23 Mar 2023 08:36:08 -0400 Subject: [PATCH 7/9] Test the new cases --- tests/lib/rules/require-unicode-regexp.js | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/lib/rules/require-unicode-regexp.js b/tests/lib/rules/require-unicode-regexp.js index 34efaa6d601..22ada7aacf6 100644 --- a/tests/lib/rules/require-unicode-regexp.js +++ b/tests/lib/rules/require-unicode-regexp.js @@ -25,8 +25,10 @@ ruleTester.run("require-unicode-regexp", rule, { "/foo/u", "/foo/gimuy", "RegExp('', 'u')", + "RegExp('', `u`)", "new RegExp('', 'u')", "RegExp('', 'gimuy')", + "RegExp('', `gimuy`)", "new RegExp('', 'gimuy')", "const flags = 'u'; new RegExp('', flags)", "const flags = 'g'; new RegExp('', flags + 'u')", @@ -118,6 +120,18 @@ ruleTester.run("require-unicode-regexp", rule, { ] }] }, + { + code: "RegExp('foo', `gimy`)", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', `gimyu`)" + } + ] + }] + }, { code: "new RegExp('foo')", errors: [{ @@ -206,6 +220,20 @@ ruleTester.run("require-unicode-regexp", rule, { suggestions: null }] }, + { + code: "let flags; new RegExp('foo', flags = 'g')", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "const flags = `gi`; new RegExp(`foo`, (`unrelated`, flags))", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, { code: "const flags = 'gimu'; new RegExp('foo', flags[0])", errors: [{ From c1243c9e569c69dc3d04a091d795fc2b13d476cc Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 23 Mar 2023 13:17:58 -0400 Subject: [PATCH 8/9] Touch up regular-expressions.js templating --- lib/rules/utils/regular-expressions.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/rules/utils/regular-expressions.js b/lib/rules/utils/regular-expressions.js index a259899612d..5eb16cd667d 100644 --- a/lib/rules/utils/regular-expressions.js +++ b/lib/rules/utils/regular-expressions.js @@ -1,11 +1,14 @@ +/** + * @fileoverview Common utils for regular expressions. + * @author Toru Nagashima + */ + "use strict"; const { RegExpValidator } = require("@eslint-community/regexpp"); const REGEXPP_LATEST_ECMA_VERSION = 2022; -module.exports.REGEXPP_LATEST_ECMA_VERSION = 2022; - /** * Checks if the given regular expression pattern would be valid with the `u` flag. * @param {number} ecmaVersion ECMAScript version to parse in. @@ -14,7 +17,7 @@ module.exports.REGEXPP_LATEST_ECMA_VERSION = 2022; * `false` if the pattern would be invalid with the `u` flag or the configured * ecmaVersion doesn't support the `u` flag. */ -module.exports.isValidWithUnicodeFlag = function isValidWithUnicodeFlag(ecmaVersion, pattern) { +function isValidWithUnicodeFlag(ecmaVersion, pattern) { if (ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag return false; } @@ -30,4 +33,9 @@ module.exports.isValidWithUnicodeFlag = function isValidWithUnicodeFlag(ecmaVers } return true; +} + +module.exports = { + isValidWithUnicodeFlag, + REGEXPP_LATEST_ECMA_VERSION }; From 36c348400049c88b5964e6b6ffc2c3b1ea02f21b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 27 Mar 2023 21:46:37 -0400 Subject: [PATCH 9/9] Add myself as author to regular-expressions.js --- lib/rules/utils/regular-expressions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rules/utils/regular-expressions.js b/lib/rules/utils/regular-expressions.js index 5eb16cd667d..234a1cb8b11 100644 --- a/lib/rules/utils/regular-expressions.js +++ b/lib/rules/utils/regular-expressions.js @@ -1,5 +1,6 @@ /** * @fileoverview Common utils for regular expressions. + * @author Josh Goldberg * @author Toru Nagashima */