From 6a1355d9eff0812ac1db5c4ca7f47a28da0d8069 Mon Sep 17 00:00:00 2001 From: Mathias Rasmussen Date: Tue, 9 Nov 2021 08:57:08 +0100 Subject: [PATCH 01/13] refactor: Simplify kinds tracking with a Set --- lib/rules/no-misleading-character-class.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 94b28784a10..fefce434883 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -130,14 +130,6 @@ module.exports = { * @returns {void} */ function verify(node, pattern, flags) { - const has = { - surrogatePairWithoutUFlag: false, - combiningClass: false, - variationSelector: false, - emojiModifier: false, - regionalIndicatorSymbol: false, - zwj: false - }; let patternNode; try { @@ -153,20 +145,22 @@ module.exports = { return; } + const foundKinds = new Set(); + visitRegExpAST(patternNode, { onCharacterClassEnter(ccNode) { for (const chars of iterateCharacterSequence(ccNode.elements)) { for (const kind of kinds) { - has[kind] = has[kind] || hasCharacterSequence[kind](chars); + if (hasCharacterSequence[kind](chars)) { + foundKinds.add(kind); + } } } } }); - for (const kind of kinds) { - if (has[kind]) { - context.report({ node, messageId: kind }); - } + for (const kind of foundKinds) { + context.report({ node, messageId: kind }); } } From 29e3e81bb35d9d307af9e2156d6ab7f5a03adf6c Mon Sep 17 00:00:00 2001 From: Mathias Rasmussen Date: Tue, 9 Nov 2021 09:25:51 +0100 Subject: [PATCH 02/13] feat: Add fixer for missing regex unicode flag --- lib/rules/no-misleading-character-class.js | 27 ++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index fefce434883..834f0e9b8de 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -111,6 +111,8 @@ module.exports = { schema: [], + fixable: "whitespace", + messages: { surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.", combiningClass: "Unexpected combined character in character class.", @@ -127,9 +129,10 @@ module.exports = { * @param {Node} node The node to report. * @param {string} pattern The regular expression pattern to verify. * @param {string} flags The flags of the regular expression. + * @param {Function} unicodeFixer Fixer for missing "u" flag. * @returns {void} */ - function verify(node, pattern, flags) { + function verify(node, pattern, flags, unicodeFixer) { let patternNode; try { @@ -160,13 +163,17 @@ module.exports = { }); for (const kind of foundKinds) { - context.report({ node, messageId: kind }); + context.report({ + node, + messageId: kind, + fix: kind === "surrogatePairWithoutUFlag" && unicodeFixer + }); } } return { "Literal[regex]"(node) { - verify(node, node.regex.pattern, node.regex.flags); + verify(node, node.regex.pattern, node.regex.flags, fixer => fixer.insertTextAfter(node, "u")); }, "Program"() { const scope = context.getScope(); @@ -185,7 +192,19 @@ module.exports = { const flags = getStringIfConstant(flagsNode, scope); if (typeof pattern === "string") { - verify(node, pattern, flags || ""); + verify(node, pattern, flags || "", fixer => { + if (node.arguments.length === 1) { + return fixer.insertTextAfterRange(patternNode.range, ', "u"'); + } + + if (flagsNode.type === "Literal" || flagsNode.type === "TemplateLiteral") { + const range = [flagsNode.range[0], flagsNode.range[1] - 1]; + + return fixer.insertTextAfterRange(range, "u"); + } + + return null; + }); } } } From bbe3684781ef0c319c636bd36f922774017eb546 Mon Sep 17 00:00:00 2001 From: Mathias Rasmussen Date: Tue, 9 Nov 2021 09:42:33 +0100 Subject: [PATCH 03/13] test: Update tests --- .../rules/no-misleading-character-class.js | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index a02e5e13e32..eeacc3ed994 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -76,86 +76,112 @@ ruleTester.run("no-misleading-character-class", rule, { // RegExp Literals. { code: "var r = /[πŸ‘]/", + output: "var r = /[πŸ‘]/u", errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: "var r = /[\\uD83D\\uDC4D]/", + output: "var r = /[\\uD83D\\uDC4D]/u", errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: "var r = /[Á]/", + output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[Á]/u", + output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u0041\\u0301]/", + output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u0041\\u0301]/u", + output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u{41}\\u{301}]/u", + output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[❇️]/", + output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[❇️]/u", + output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u2747\\uFE0F]/", + output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u2747\\uFE0F]/u", + output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u{2747}\\u{FE0F}]/u", + output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[πŸ‘ΆπŸ»]/", + output: "var r = /[πŸ‘ΆπŸ»]/u", errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: "var r = /[πŸ‘ΆπŸ»]/u", + output: null, errors: [{ messageId: "emojiModifier" }] }, { code: "var r = /[\\uD83D\\uDC76\\uD83C\\uDFFB]/u", + output: null, errors: [{ messageId: "emojiModifier" }] }, { code: "var r = /[\\u{1F476}\\u{1F3FB}]/u", + output: null, errors: [{ messageId: "emojiModifier" }] }, { code: "var r = /[πŸ‡―πŸ‡΅]/", + output: "var r = /[πŸ‡―πŸ‡΅]/u", + errors: [{ messageId: "surrogatePairWithoutUFlag" }] + }, + { + code: "var r = /[πŸ‡―πŸ‡΅]/i", + output: "var r = /[πŸ‡―πŸ‡΅]/iu", errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: "var r = /[πŸ‡―πŸ‡΅]/u", + output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: "var r = /[\\uD83C\\uDDEF\\uD83C\\uDDF5]/u", + output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: "var r = /[\\u{1F1EF}\\u{1F1F5}]/u", + output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/", + output: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u", errors: [ { messageId: "surrogatePairWithoutUFlag" }, { messageId: "zwj" } @@ -163,100 +189,134 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u", + output: null, errors: [{ messageId: "zwj" }] }, { code: "var r = /[\\uD83D\\uDC68\\u200D\\uD83D\\uDC69\\u200D\\uD83D\\uDC66]/u", + output: null, errors: [{ messageId: "zwj" }] }, { code: "var r = /[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]/u", + output: null, errors: [{ messageId: "zwj" }] }, // RegExp constructors. { code: String.raw`var r = new RegExp("[πŸ‘]", "")`, + output: String.raw`var r = new RegExp("[πŸ‘]", "u")`, errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC4D]", "")`, + output: String.raw`var r = new RegExp("[\\uD83D\\uDC4D]", "u")`, errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: String.raw`var r = new RegExp("[Á]", "")`, + output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[Á]", "u")`, + output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u0041\\u0301]", "")`, + output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u0041\\u0301]", "u")`, + output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u{41}\\u{301}]", "u")`, + output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[❇️]", "")`, + output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[❇️]", "u")`, + output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u2747\\uFE0F]", "")`, + output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u2747\\uFE0F]", "u")`, + output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u{2747}\\u{FE0F}]", "u")`, + output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "")`, + output: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "u")`, errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "u")`, + output: null, errors: [{ messageId: "emojiModifier" }] }, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC76\\uD83C\\uDFFB]", "u")`, + output: null, errors: [{ messageId: "emojiModifier" }] }, { code: String.raw`var r = new RegExp("[\\u{1F476}\\u{1F3FB}]", "u")`, + output: null, errors: [{ messageId: "emojiModifier" }] }, { code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "")`, + output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")`, + errors: [{ messageId: "surrogatePairWithoutUFlag" }] + }, + { + code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "i")`, + output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "iu")`, + errors: [{ messageId: "surrogatePairWithoutUFlag" }] + }, + { + code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]")`, + output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")`, errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")`, + output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: String.raw`var r = new RegExp("[\\uD83C\\uDDEF\\uD83C\\uDDF5]", "u")`, + output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: String.raw`var r = new RegExp("[\\u{1F1EF}\\u{1F1F5}]", "u")`, + output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "")`, + output: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "u")`, errors: [ { messageId: "surrogatePairWithoutUFlag" }, { messageId: "zwj" } @@ -264,33 +324,40 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "u")`, + output: null, errors: [{ messageId: "zwj" }] }, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC68\\u200D\\uD83D\\uDC69\\u200D\\uD83D\\uDC66]", "u")`, + output: null, errors: [{ messageId: "zwj" }] }, { code: String.raw`var r = new RegExp("[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]", "u")`, + output: null, errors: [{ messageId: "zwj" }] }, { code: String.raw`var r = new globalThis.RegExp("[❇️]", "")`, + output: null, env: { es2020: true }, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u")`, + output: null, env: { es2020: true }, errors: [{ messageId: "emojiModifier" }] }, { code: String.raw`var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", "")`, + output: String.raw`var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", "u")`, env: { es2020: true }, errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: String.raw`var r = new globalThis.RegExp("[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]", "u")`, + output: null, env: { es2020: true }, errors: [{ messageId: "zwj" }] } From 4ccee61b49b25335dadf585ae2dde40f5a7aeb9c Mon Sep 17 00:00:00 2001 From: Mathias Rasmussen Date: Thu, 25 Nov 2021 02:31:51 +0100 Subject: [PATCH 04/13] refactor: Change fix to suggestion --- lib/rules/no-misleading-character-class.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 834f0e9b8de..0a48a23d552 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -109,6 +109,8 @@ module.exports = { url: "https://eslint.org/docs/rules/no-misleading-character-class" }, + hasSuggestions: true, + schema: [], fixable: "whitespace", @@ -118,7 +120,8 @@ module.exports = { combiningClass: "Unexpected combined character in character class.", emojiModifier: "Unexpected modified Emoji in character class.", regionalIndicatorSymbol: "Unexpected national flag in character class.", - zwj: "Unexpected joined character sequence in character class." + zwj: "Unexpected joined character sequence in character class.", + suggestUnicodeFlag: "Add unicode 'u' flag to regex." } }, create(context) { @@ -163,10 +166,19 @@ module.exports = { }); for (const kind of foundKinds) { + let suggest; + + if (kind === "surrogatePairWithoutUFlag") { + suggest = [{ + messageId: "suggestUnicodeFlag", + fix: unicodeFixer + }]; + } + context.report({ node, messageId: kind, - fix: kind === "surrogatePairWithoutUFlag" && unicodeFixer + suggest }); } } From b1326f98942689b628a0e5bf3e99a9ea6cbca1b7 Mon Sep 17 00:00:00 2001 From: Mathias Rasmussen Date: Thu, 25 Nov 2021 02:38:28 +0100 Subject: [PATCH 05/13] Revert "test: Update tests" This reverts commit 36396bad0b13c864e4ba1f4db212f03ca6622856. --- .../rules/no-misleading-character-class.js | 67 ------------------- 1 file changed, 67 deletions(-) diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index eeacc3ed994..a02e5e13e32 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -76,112 +76,86 @@ ruleTester.run("no-misleading-character-class", rule, { // RegExp Literals. { code: "var r = /[πŸ‘]/", - output: "var r = /[πŸ‘]/u", errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: "var r = /[\\uD83D\\uDC4D]/", - output: "var r = /[\\uD83D\\uDC4D]/u", errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: "var r = /[Á]/", - output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[Á]/u", - output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u0041\\u0301]/", - output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u0041\\u0301]/u", - output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u{41}\\u{301}]/u", - output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[❇️]/", - output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[❇️]/u", - output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u2747\\uFE0F]/", - output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u2747\\uFE0F]/u", - output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[\\u{2747}\\u{FE0F}]/u", - output: null, errors: [{ messageId: "combiningClass" }] }, { code: "var r = /[πŸ‘ΆπŸ»]/", - output: "var r = /[πŸ‘ΆπŸ»]/u", errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: "var r = /[πŸ‘ΆπŸ»]/u", - output: null, errors: [{ messageId: "emojiModifier" }] }, { code: "var r = /[\\uD83D\\uDC76\\uD83C\\uDFFB]/u", - output: null, errors: [{ messageId: "emojiModifier" }] }, { code: "var r = /[\\u{1F476}\\u{1F3FB}]/u", - output: null, errors: [{ messageId: "emojiModifier" }] }, { code: "var r = /[πŸ‡―πŸ‡΅]/", - output: "var r = /[πŸ‡―πŸ‡΅]/u", - errors: [{ messageId: "surrogatePairWithoutUFlag" }] - }, - { - code: "var r = /[πŸ‡―πŸ‡΅]/i", - output: "var r = /[πŸ‡―πŸ‡΅]/iu", errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: "var r = /[πŸ‡―πŸ‡΅]/u", - output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: "var r = /[\\uD83C\\uDDEF\\uD83C\\uDDF5]/u", - output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: "var r = /[\\u{1F1EF}\\u{1F1F5}]/u", - output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/", - output: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u", errors: [ { messageId: "surrogatePairWithoutUFlag" }, { messageId: "zwj" } @@ -189,134 +163,100 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u", - output: null, errors: [{ messageId: "zwj" }] }, { code: "var r = /[\\uD83D\\uDC68\\u200D\\uD83D\\uDC69\\u200D\\uD83D\\uDC66]/u", - output: null, errors: [{ messageId: "zwj" }] }, { code: "var r = /[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]/u", - output: null, errors: [{ messageId: "zwj" }] }, // RegExp constructors. { code: String.raw`var r = new RegExp("[πŸ‘]", "")`, - output: String.raw`var r = new RegExp("[πŸ‘]", "u")`, errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC4D]", "")`, - output: String.raw`var r = new RegExp("[\\uD83D\\uDC4D]", "u")`, errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: String.raw`var r = new RegExp("[Á]", "")`, - output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[Á]", "u")`, - output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u0041\\u0301]", "")`, - output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u0041\\u0301]", "u")`, - output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u{41}\\u{301}]", "u")`, - output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[❇️]", "")`, - output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[❇️]", "u")`, - output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u2747\\uFE0F]", "")`, - output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u2747\\uFE0F]", "u")`, - output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[\\u{2747}\\u{FE0F}]", "u")`, - output: null, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "")`, - output: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "u")`, errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "u")`, - output: null, errors: [{ messageId: "emojiModifier" }] }, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC76\\uD83C\\uDFFB]", "u")`, - output: null, errors: [{ messageId: "emojiModifier" }] }, { code: String.raw`var r = new RegExp("[\\u{1F476}\\u{1F3FB}]", "u")`, - output: null, errors: [{ messageId: "emojiModifier" }] }, { code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "")`, - output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")`, - errors: [{ messageId: "surrogatePairWithoutUFlag" }] - }, - { - code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "i")`, - output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "iu")`, - errors: [{ messageId: "surrogatePairWithoutUFlag" }] - }, - { - code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]")`, - output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")`, errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")`, - output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: String.raw`var r = new RegExp("[\\uD83C\\uDDEF\\uD83C\\uDDF5]", "u")`, - output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: String.raw`var r = new RegExp("[\\u{1F1EF}\\u{1F1F5}]", "u")`, - output: null, errors: [{ messageId: "regionalIndicatorSymbol" }] }, { code: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "")`, - output: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "u")`, errors: [ { messageId: "surrogatePairWithoutUFlag" }, { messageId: "zwj" } @@ -324,40 +264,33 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "u")`, - output: null, errors: [{ messageId: "zwj" }] }, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC68\\u200D\\uD83D\\uDC69\\u200D\\uD83D\\uDC66]", "u")`, - output: null, errors: [{ messageId: "zwj" }] }, { code: String.raw`var r = new RegExp("[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]", "u")`, - output: null, errors: [{ messageId: "zwj" }] }, { code: String.raw`var r = new globalThis.RegExp("[❇️]", "")`, - output: null, env: { es2020: true }, errors: [{ messageId: "combiningClass" }] }, { code: String.raw`var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u")`, - output: null, env: { es2020: true }, errors: [{ messageId: "emojiModifier" }] }, { code: String.raw`var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", "")`, - output: String.raw`var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", "u")`, env: { es2020: true }, errors: [{ messageId: "surrogatePairWithoutUFlag" }] }, { code: String.raw`var r = new globalThis.RegExp("[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]", "u")`, - output: null, env: { es2020: true }, errors: [{ messageId: "zwj" }] } From dcbf03da38a42d17458442e4ac5e9599aeae7f16 Mon Sep 17 00:00:00 2001 From: Mathias Rasmussen Date: Sun, 28 Nov 2021 22:51:33 +0100 Subject: [PATCH 06/13] Address review comments Add tests --- lib/rules/no-misleading-character-class.js | 2 - .../rules/no-misleading-character-class.js | 76 ++++++++++++++++--- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 0a48a23d552..9e765bf9a9f 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -113,8 +113,6 @@ module.exports = { schema: [], - fixable: "whitespace", - messages: { surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.", combiningClass: "Unexpected combined character in character class.", diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index a02e5e13e32..b7cf3738af5 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -76,11 +76,17 @@ ruleTester.run("no-misleading-character-class", rule, { // RegExp Literals. { code: "var r = /[πŸ‘]/", - errors: [{ messageId: "surrogatePairWithoutUFlag" }] + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[πŸ‘]/u" }] + }] }, { code: "var r = /[\\uD83D\\uDC4D]/", - errors: [{ messageId: "surrogatePairWithoutUFlag" }] + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[\\uD83D\\uDC4D]/u" }] + }] }, { code: "var r = /[Á]/", @@ -124,7 +130,10 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: "var r = /[πŸ‘ΆπŸ»]/", - errors: [{ messageId: "surrogatePairWithoutUFlag" }] + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[πŸ‘ΆπŸ»]/u" }] + }] }, { code: "var r = /[πŸ‘ΆπŸ»]/u", @@ -140,7 +149,17 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: "var r = /[πŸ‡―πŸ‡΅]/", - errors: [{ messageId: "surrogatePairWithoutUFlag" }] + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[πŸ‡―πŸ‡΅]/u" }] + }] + }, + { + code: "var r = /[πŸ‡―πŸ‡΅]/i", + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[πŸ‡―πŸ‡΅]/iu" }] + }] }, { code: "var r = /[πŸ‡―πŸ‡΅]/u", @@ -157,7 +176,10 @@ ruleTester.run("no-misleading-character-class", rule, { { code: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/", errors: [ - { messageId: "surrogatePairWithoutUFlag" }, + { + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u" }] + }, { messageId: "zwj" } ] }, @@ -177,11 +199,17 @@ ruleTester.run("no-misleading-character-class", rule, { // RegExp constructors. { code: String.raw`var r = new RegExp("[πŸ‘]", "")`, - errors: [{ messageId: "surrogatePairWithoutUFlag" }] + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‘]", "u")` }] + }] }, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC4D]", "")`, - errors: [{ messageId: "surrogatePairWithoutUFlag" }] + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[\\uD83D\\uDC4D]", "u")` }] + }] }, { code: String.raw`var r = new RegExp("[Á]", "")`, @@ -225,7 +253,10 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "")`, - errors: [{ messageId: "surrogatePairWithoutUFlag" }] + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "u")` }] + }] }, { code: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "u")`, @@ -241,7 +272,24 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "")`, - errors: [{ messageId: "surrogatePairWithoutUFlag" }] + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")` }] + }] + }, + { + code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "i")`, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "iu")` }] + }] + }, + { + code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]")`, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")` }] + }] }, { code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")`, @@ -258,7 +306,10 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "")`, errors: [ - { messageId: "surrogatePairWithoutUFlag" }, + { + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "u")` }] + }, { messageId: "zwj" } ] }, @@ -287,7 +338,10 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", "")`, env: { es2020: true }, - errors: [{ messageId: "surrogatePairWithoutUFlag" }] + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", "u")` }] + }] }, { code: String.raw`var r = new globalThis.RegExp("[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]", "u")`, From 5e5b2f89da26403a78ce9b36662f26f2c854258d Mon Sep 17 00:00:00 2001 From: Mathias Rasmussen Date: Fri, 4 Feb 2022 16:54:47 +0100 Subject: [PATCH 07/13] Update lib/rules/no-misleading-character-class.js Co-authored-by: Milos Djermanovic --- lib/rules/no-misleading-character-class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 9e765bf9a9f..9920ff1d250 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -207,7 +207,7 @@ module.exports = { return fixer.insertTextAfterRange(patternNode.range, ', "u"'); } - if (flagsNode.type === "Literal" || flagsNode.type === "TemplateLiteral") { + if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") { const range = [flagsNode.range[0], flagsNode.range[1] - 1]; return fixer.insertTextAfterRange(range, "u"); From 5c00962c0a3cdbb1062b3be5ec1ce409a6d50451 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 11 May 2022 11:32:23 +0200 Subject: [PATCH 08/13] add suggestions:null assertions --- .../rules/no-misleading-character-class.js | 215 ++++++++++++++---- 1 file changed, 172 insertions(+), 43 deletions(-) diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index b7cf3738af5..b67fc804815 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -90,43 +90,73 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: "var r = /[Á]/", - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: "var r = /[Á]/u", - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: "var r = /[\\u0041\\u0301]/", - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: "var r = /[\\u0041\\u0301]/u", - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: "var r = /[\\u{41}\\u{301}]/u", - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: "var r = /[❇️]/", - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: "var r = /[❇️]/u", - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: "var r = /[\\u2747\\uFE0F]/", - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: "var r = /[\\u2747\\uFE0F]/u", - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: "var r = /[\\u{2747}\\u{FE0F}]/u", - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: "var r = /[πŸ‘ΆπŸ»]/", @@ -137,15 +167,24 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: "var r = /[πŸ‘ΆπŸ»]/u", - errors: [{ messageId: "emojiModifier" }] + errors: [{ + messageId: "emojiModifier", + suggestions: null + }] }, { code: "var r = /[\\uD83D\\uDC76\\uD83C\\uDFFB]/u", - errors: [{ messageId: "emojiModifier" }] + errors: [{ + messageId: "emojiModifier", + suggestions: null + }] }, { code: "var r = /[\\u{1F476}\\u{1F3FB}]/u", - errors: [{ messageId: "emojiModifier" }] + errors: [{ + messageId: "emojiModifier", + suggestions: null + }] }, { code: "var r = /[πŸ‡―πŸ‡΅]/", @@ -163,15 +202,24 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: "var r = /[πŸ‡―πŸ‡΅]/u", - errors: [{ messageId: "regionalIndicatorSymbol" }] + errors: [{ + messageId: "regionalIndicatorSymbol", + suggestions: null + }] }, { code: "var r = /[\\uD83C\\uDDEF\\uD83C\\uDDF5]/u", - errors: [{ messageId: "regionalIndicatorSymbol" }] + errors: [{ + messageId: "regionalIndicatorSymbol", + suggestions: null + }] }, { code: "var r = /[\\u{1F1EF}\\u{1F1F5}]/u", - errors: [{ messageId: "regionalIndicatorSymbol" }] + errors: [{ + messageId: "regionalIndicatorSymbol", + suggestions: null + }] }, { code: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/", @@ -180,20 +228,32 @@ ruleTester.run("no-misleading-character-class", rule, { messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u" }] }, - { messageId: "zwj" } + { + messageId: "zwj", + suggestions: null + } ] }, { code: "var r = /[πŸ‘¨β€πŸ‘©β€πŸ‘¦]/u", - errors: [{ messageId: "zwj" }] + errors: [{ + messageId: "zwj", + suggestions: null + }] }, { code: "var r = /[\\uD83D\\uDC68\\u200D\\uD83D\\uDC69\\u200D\\uD83D\\uDC66]/u", - errors: [{ messageId: "zwj" }] + errors: [{ + messageId: "zwj", + suggestions: null + }] }, { code: "var r = /[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]/u", - errors: [{ messageId: "zwj" }] + errors: [{ + messageId: "zwj", + suggestions: null + }] }, // RegExp constructors. @@ -213,43 +273,73 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: String.raw`var r = new RegExp("[Á]", "")`, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[Á]", "u")`, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\u0041\\u0301]", "")`, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\u0041\\u0301]", "u")`, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\u{41}\\u{301}]", "u")`, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[❇️]", "")`, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[❇️]", "u")`, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\u2747\\uFE0F]", "")`, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\u2747\\uFE0F]", "u")`, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\u{2747}\\u{FE0F}]", "u")`, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "")`, @@ -260,15 +350,24 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: String.raw`var r = new RegExp("[πŸ‘ΆπŸ»]", "u")`, - errors: [{ messageId: "emojiModifier" }] + errors: [{ + messageId: "emojiModifier", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC76\\uD83C\\uDFFB]", "u")`, - errors: [{ messageId: "emojiModifier" }] + errors: [{ + messageId: "emojiModifier", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\u{1F476}\\u{1F3FB}]", "u")`, - errors: [{ messageId: "emojiModifier" }] + errors: [{ + messageId: "emojiModifier", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "")`, @@ -293,15 +392,24 @@ ruleTester.run("no-misleading-character-class", rule, { }, { code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")`, - errors: [{ messageId: "regionalIndicatorSymbol" }] + errors: [{ + messageId: "regionalIndicatorSymbol", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\uD83C\\uDDEF\\uD83C\\uDDF5]", "u")`, - errors: [{ messageId: "regionalIndicatorSymbol" }] + errors: [{ + messageId: "regionalIndicatorSymbol", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\u{1F1EF}\\u{1F1F5}]", "u")`, - errors: [{ messageId: "regionalIndicatorSymbol" }] + errors: [{ + messageId: "regionalIndicatorSymbol", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "")`, @@ -310,30 +418,48 @@ ruleTester.run("no-misleading-character-class", rule, { messageId: "surrogatePairWithoutUFlag", suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "u")` }] }, - { messageId: "zwj" } + { + messageId: "zwj", + suggestions: null + } ] }, { code: String.raw`var r = new RegExp("[πŸ‘¨β€πŸ‘©β€πŸ‘¦]", "u")`, - errors: [{ messageId: "zwj" }] + errors: [{ + messageId: "zwj", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC68\\u200D\\uD83D\\uDC69\\u200D\\uD83D\\uDC66]", "u")`, - errors: [{ messageId: "zwj" }] + errors: [{ + messageId: "zwj", + suggestions: null + }] }, { code: String.raw`var r = new RegExp("[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]", "u")`, - errors: [{ messageId: "zwj" }] + errors: [{ + messageId: "zwj", + suggestions: null + }] }, { code: String.raw`var r = new globalThis.RegExp("[❇️]", "")`, env: { es2020: true }, - errors: [{ messageId: "combiningClass" }] + errors: [{ + messageId: "combiningClass", + suggestions: null + }] }, { code: String.raw`var r = new globalThis.RegExp("[πŸ‘ΆπŸ»]", "u")`, env: { es2020: true }, - errors: [{ messageId: "emojiModifier" }] + errors: [{ + messageId: "emojiModifier", + suggestions: null + }] }, { code: String.raw`var r = new globalThis.RegExp("[πŸ‡―πŸ‡΅]", "")`, @@ -346,7 +472,10 @@ ruleTester.run("no-misleading-character-class", rule, { { code: String.raw`var r = new globalThis.RegExp("[\\u{1F468}\\u{200D}\\u{1F469}\\u{200D}\\u{1F466}]", "u")`, env: { es2020: true }, - errors: [{ messageId: "zwj" }] + errors: [{ + messageId: "zwj", + suggestions: null + }] } ] }); From f5fe77d2ad6be588dc4e8d90204d11be00aa1a2e Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 11 May 2022 11:39:38 +0200 Subject: [PATCH 09/13] update docs --- docs/src/rules/no-misleading-character-class.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/rules/no-misleading-character-class.md b/docs/src/rules/no-misleading-character-class.md index 005127abf48..9d190820146 100644 --- a/docs/src/rules/no-misleading-character-class.md +++ b/docs/src/rules/no-misleading-character-class.md @@ -7,6 +7,8 @@ rule_type: problem + + Disallows characters which are made with multiple code points in character class syntax. Unicode includes the characters which are made with multiple code points. From 468ce009ad00178be5bcb64758a1eb49deb4a241 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 11 May 2022 12:20:20 +0200 Subject: [PATCH 10/13] add more tests --- .../rules/no-misleading-character-class.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index b67fc804815..230d183f4d7 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -264,6 +264,27 @@ ruleTester.run("no-misleading-character-class", rule, { suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‘]", "u")` }] }] }, + { + code: "var r = new RegExp('[πŸ‘]', ``)", + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = new RegExp('[πŸ‘]', `u`)" }] + }] + }, + { + code: String.raw`var r = new RegExp("[πŸ‘]", flags)`, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: null + }] + }, + { + code: String.raw`const flags = ""; var r = new RegExp("[πŸ‘]", flags)`, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: null + }] + }, { code: String.raw`var r = new RegExp("[\\uD83D\\uDC4D]", "")`, errors: [{ @@ -383,6 +404,20 @@ ruleTester.run("no-misleading-character-class", rule, { suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "iu")` }] }] }, + { + code: "var r = new RegExp('[πŸ‡―πŸ‡΅]', `i`)", + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = new RegExp('[πŸ‡―πŸ‡΅]', `iu`)" }] + }] + }, + { + code: "var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}`)", + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = new RegExp('[πŸ‡―πŸ‡΅]', `${foo}u`)" }] + }] + }, { code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]")`, errors: [{ From be0678e555eed1db92e2ff1814ce6f74c2d5d704 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 11 May 2022 13:09:53 +0200 Subject: [PATCH 11/13] fix edge cases when inserting flags argument --- lib/rules/no-misleading-character-class.js | 11 ++++++- .../rules/no-misleading-character-class.js | 30 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 9920ff1d250..8695d073d97 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -6,6 +6,7 @@ const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); const { RegExpParser, visitRegExpAST } = require("regexpp"); const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); +const astUtils = require("./utils/ast-utils.js"); //------------------------------------------------------------------------------ // Helpers @@ -123,6 +124,7 @@ module.exports = { } }, create(context) { + const sourceCode = context.getSourceCode(); const parser = new RegExpParser(); /** @@ -204,7 +206,14 @@ module.exports = { if (typeof pattern === "string") { verify(node, pattern, flags || "", fixer => { if (node.arguments.length === 1) { - return fixer.insertTextAfterRange(patternNode.range, ', "u"'); + const penultimateToken = sourceCode.getLastToken(node, { skip: 1 }); // skip closing parenthesis + + return fixer.insertTextAfter( + penultimateToken, + astUtils.isCommaToken(penultimateToken) + ? ' "u",' + : ', "u"' + ); } if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") { diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index 230d183f4d7..66064104d58 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -425,6 +425,36 @@ ruleTester.run("no-misleading-character-class", rule, { suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")` }] }] }, + { + code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]",)`, + parserOptions: { ecmaVersion: 2017 }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u",)` }] + }] + }, + { + code: String.raw`var r = new RegExp(("[πŸ‡―πŸ‡΅]"))`, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp(("[πŸ‡―πŸ‡΅]"), "u")` }] + }] + }, + { + code: String.raw`var r = new RegExp((("[πŸ‡―πŸ‡΅]")))`, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp((("[πŸ‡―πŸ‡΅]")), "u")` }] + }] + }, + { + code: String.raw`var r = new RegExp(("[πŸ‡―πŸ‡΅]"),)`, + parserOptions: { ecmaVersion: 2017 }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp(("[πŸ‡―πŸ‡΅]"), "u",)` }] + }] + }, { code: String.raw`var r = new RegExp("[πŸ‡―πŸ‡΅]", "u")`, errors: [{ From f0e8fe2e9d61e9b76b535ab47696f95e4fb57a39 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 11 May 2022 15:00:08 +0200 Subject: [PATCH 12/13] validate pattern for regex literals --- lib/rules/no-misleading-character-class.js | 40 ++++++++++++++++++- .../rules/no-misleading-character-class.js | 39 ++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 8695d073d97..f790376bde8 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -4,7 +4,7 @@ "use strict"; const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); -const { RegExpParser, visitRegExpAST } = require("regexpp"); +const { RegExpValidator, RegExpParser, visitRegExpAST } = require("regexpp"); const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); const astUtils = require("./utils/ast-utils.js"); @@ -12,6 +12,8 @@ const astUtils = require("./utils/ast-utils.js"); // Helpers //------------------------------------------------------------------------------ +const REGEXPP_LATEST_ECMA_VERSION = 2022; + /** * Iterate character sequences of a given nodes. * @@ -183,9 +185,43 @@ 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.parserOptions; + + // ecmaVersion is unknown or it doesn't support the 'u' flag + if (typeof ecmaVersion !== "number" || ecmaVersion <= 5) { + return false; + } + + const validator = new RegExpValidator({ + ecmaVersion: Math.min(ecmaVersion + 2009, 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 => fixer.insertTextAfter(node, "u")); + verify(node, node.regex.pattern, node.regex.flags, fixer => { + if (!isValidWithUnicodeFlag(node.regex.pattern)) { + return null; + } + + return fixer.insertTextAfter(node, "u"); + }); }, "Program"() { const scope = context.getScope(); diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index 66064104d58..8b913eac4c5 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -88,6 +88,45 @@ ruleTester.run("no-misleading-character-class", rule, { suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /[\\uD83D\\uDC4D]/u" }] }] }, + { + code: "var r = /[πŸ‘]/", + parserOptions: { ecmaVersion: 3 }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: null // ecmaVersion doesn't support the 'u' flag + }] + }, + { + code: "var r = /[πŸ‘]/", + parserOptions: { ecmaVersion: 5 }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: null // ecmaVersion doesn't support the 'u' flag + }] + }, + { + code: "var r = /[πŸ‘]\\a/", + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: null // pattern would be invalid with the 'u' flag + }] + }, + { + code: "var r = /(?<=[πŸ‘])/", + parserOptions: { ecmaVersion: 9 }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /(?<=[πŸ‘])/u" }] + }] + }, + { + code: "var r = /(?<=[πŸ‘])/", + parserOptions: { ecmaVersion: 2018 }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: "var r = /(?<=[πŸ‘])/u" }] + }] + }, { code: "var r = /[Á]/", errors: [{ From f1871da43ac02ddba82062b1a25bb5e19eece928 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 11 May 2022 15:10:36 +0200 Subject: [PATCH 13/13] validate pattern for regex constructor calls --- lib/rules/no-misleading-character-class.js | 5 +++ .../rules/no-misleading-character-class.js | 39 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index f790376bde8..d3d9d382ac5 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -241,6 +241,11 @@ module.exports = { if (typeof pattern === "string") { verify(node, pattern, flags || "", fixer => { + + if (!isValidWithUnicodeFlag(pattern)) { + return null; + } + if (node.arguments.length === 1) { const penultimateToken = sourceCode.getLastToken(node, { skip: 1 }); // skip closing parenthesis diff --git a/tests/lib/rules/no-misleading-character-class.js b/tests/lib/rules/no-misleading-character-class.js index 8b913eac4c5..0aaf34e5942 100644 --- a/tests/lib/rules/no-misleading-character-class.js +++ b/tests/lib/rules/no-misleading-character-class.js @@ -331,6 +331,45 @@ ruleTester.run("no-misleading-character-class", rule, { suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("[\\uD83D\\uDC4D]", "u")` }] }] }, + { + code: String.raw`var r = new RegExp("[πŸ‘]", "")`, + parserOptions: { ecmaVersion: 3 }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: null // ecmaVersion doesn't support the 'u' flag + }] + }, + { + code: String.raw`var r = new RegExp("[πŸ‘]", "")`, + parserOptions: { ecmaVersion: 5 }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: null // ecmaVersion doesn't support the 'u' flag + }] + }, + { + code: String.raw`var r = new RegExp("[πŸ‘]\\a", "")`, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: null // pattern would be invalid with the 'u' flag + }] + }, + { + code: String.raw`var r = new RegExp("/(?<=[πŸ‘])", "")`, + parserOptions: { ecmaVersion: 9 }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("/(?<=[πŸ‘])", "u")` }] + }] + }, + { + code: String.raw`var r = new RegExp("/(?<=[πŸ‘])", "")`, + parserOptions: { ecmaVersion: 2018 }, + errors: [{ + messageId: "surrogatePairWithoutUFlag", + suggestions: [{ messageId: "suggestUnicodeFlag", output: String.raw`var r = new RegExp("/(?<=[πŸ‘])", "u")` }] + }] + }, { code: String.raw`var r = new RegExp("[Á]", "")`, errors: [{