From bac91cbf8a838d2e5ec3207c6ed2a5b62341c97c Mon Sep 17 00:00:00 2001 From: Yash-Singh1 Date: Wed, 15 Sep 2021 19:08:53 -0700 Subject: [PATCH] New: Autofix support to prefer-regex-literals Fixes #15029 --- lib/rules/prefer-regex-literals.js | 224 +++++- tests/lib/rules/prefer-regex-literals.js | 854 ++++++++++++++++++++++- 2 files changed, 1034 insertions(+), 44 deletions(-) diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index fbfeb5636d7..eca2eaa3e39 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -10,21 +10,13 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils"); +const { CALL, CONSTRUCT, ReferenceTracker, findVariable, getStaticValue } = require("eslint-utils"); +const { validateRegExpLiteral } = require("regexpp"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -/** - * Determines whether the given node is a string literal. - * @param {ASTNode} node Node to check. - * @returns {boolean} True if the node is a string literal. - */ -function isStringLiteral(node) { - return node.type === "Literal" && typeof node.value === "string"; -} - /** * Determines whether the given node is a regex literal. * @param {ASTNode} node Node to check. @@ -43,6 +35,75 @@ function isStaticTemplateLiteral(node) { return node.type === "TemplateLiteral" && node.expressions.length === 0; } +const validPrecedingTokens = [ + "(", + ";", + "[", + ",", + "=", + "+", + "*", + "-", + "?", + "~", + "%", + "**", + "!", + "typeof", + "instanceof", + "&&", + "||", + "??", + "await", + "yield", + "return", + "...", + "delete", + "void", + "in", + "<", + ">", + "<=", + ">=", + "==", + "===", + "!=", + "!==", + "<<", + ">>", + ">>>", + "&", + "|", + "^", + ":", + "{", + "=>", + "*=", + "<<=", + ">>=", + ">>>=", + "^=", + "|=", + "&=", + "??=", + "||=", + "&&=", + "**=", + "+=", + "-=", + "/=", + "%=", + "/", + "do", + "break", + "continue", + "debugger", + "case", + "throw", + "of", + ")" +]; + //------------------------------------------------------------------------------ // Rule Definition @@ -58,6 +119,8 @@ module.exports = { url: "https://eslint.org/docs/rules/prefer-regex-literals" }, + fixable: "code", + schema: [ { type: "object", @@ -80,6 +143,8 @@ module.exports = { create(context) { const [{ disallowRedundantWrapping = false } = {}] = context.options; + const sourceCode = context.getSourceCode(); + const text = sourceCode.getText(); /** * Determines whether the given identifier node is a reference to a global variable. @@ -107,53 +172,81 @@ module.exports = { } /** - * Determines whether the given node is considered to be a static string by the logic of this rule. - * @param {ASTNode} node Node to check. - * @returns {boolean} True if the node is a static string. + * Gets the value of a string + * @param {ASTNode} node The node to get the string of. + * @param {Scope} [scope] The scope + * @returns {string} The value of the node. */ - function isStaticString(node) { - return isStringLiteral(node) || - isStaticTemplateLiteral(node) || - isStringRawTaggedStaticTemplateLiteral(node); - } + function getStringValue(node, scope) { + const result = getStaticValue(node, scope); - /** - * Determines whether the relevant arguments of the given are all static string literals. - * @param {ASTNode} node Node to check. - * @returns {boolean} True if all arguments are static strings. - */ - function hasOnlyStaticStringArguments(node) { - const args = node.arguments; - - if ((args.length === 1 || args.length === 2) && args.every(isStaticString)) { - return true; + if (result && typeof result.value === "string") { + return result.value; } - - return false; + return null; } /** * Determines whether the arguments of the given node indicate that a regex literal is unnecessarily wrapped. * @param {ASTNode} node Node to check. + * @param {Scope} scope The scope passed to getStringValue * @returns {boolean} True if the node already contains a regex literal argument. */ - function isUnnecessarilyWrappedRegexLiteral(node) { + function isUnnecessarilyWrappedRegexLiteral(node, scope) { const args = node.arguments; if (args.length === 1 && isRegexLiteral(args[0])) { return true; } - if (args.length === 2 && isRegexLiteral(args[0]) && isStaticString(args[1])) { + if (args.length === 2 && isRegexLiteral(args[0]) && getStringValue(args[1], scope)) { return true; } return false; } + /* eslint-disable jsdoc/valid-types -- eslint-plugin-jsdoc's type parser doesn't support square brackets */ + /** + * Returns a ecmaVersion compatible for regexpp. + * @param {import("../linter/linter").ParserOptions["ecmaVersion"]} ecmaVersion The ecmaVersion to convert. + * @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp. + */ + function getRegexppEcmaVersion(ecmaVersion) { + /* eslint-enable jsdoc/valid-types -- JSDoc is over, enable jsdoc/valid-types again */ + if (ecmaVersion === 3 || ecmaVersion === 5) { + return 5; + } + return ecmaVersion + 2009; + } + + /** + * Ensures that String is the only variable present in all child scopes + * @param {Scope} scope The scope to go within and remove variables from + * @param {boolean} [children] Whether to iterate over children or not and if false iterate through parents + * @returns {Scope} The newer scope with only String present + */ + function noStringScope(scope, children = true) { + scope.variables.filter(variable => variable.name !== "String").forEach(definedVariable => scope.set.delete(definedVariable.name)); + if (children) { + for (const childScopeIndex in scope.childScopes) { + if (!isNaN(+childScopeIndex)) { + scope.childScopes[childScopeIndex] = noStringScope(scope.childScopes[childScopeIndex]); + } + } + if (scope.childScopes.length === 0 && scope.upper) { + scope.upper = noStringScope(scope.upper, false); + } + } else if (scope.upper) { + scope.upper = noStringScope(scope.upper, false); + } + return scope; + } + return { Program() { - const scope = context.getScope(); + let scope = context.getScope(); + const tracker = new ReferenceTracker(scope); const traceMap = { RegExp: { @@ -163,14 +256,69 @@ module.exports = { }; for (const { node } of tracker.iterateGlobalReferences(traceMap)) { - if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) { + scope = noStringScope(scope); + + if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node, scope)) { if (node.arguments.length === 2) { - context.report({ node, messageId: "unexpectedRedundantRegExpWithFlags" }); + context.report({ + node, + messageId: "unexpectedRedundantRegExpWithFlags", + // eslint-disable-next-line no-loop-func -- scope value won't change + fix(fixer) { + return fixer.replaceTextRange(node.range, node.arguments[0].raw + getStringValue(node.arguments[1], scope)); + } + }); } else { - context.report({ node, messageId: "unexpectedRedundantRegExp" }); + context.report({ + node, + messageId: "unexpectedRedundantRegExp", + fix(fixer) { + return fixer.replaceTextRange(node.range, node.arguments[0].raw); + } + }); } - } else if (hasOnlyStaticStringArguments(node)) { - context.report({ node, messageId: "unexpectedRegExp" }); + } else if ( + (getStringValue(node.arguments[0], scope) !== null) && + (!node.arguments[1] || getStringValue(node.arguments[1], scope) !== null) && + (node.arguments.length === 1 || node.arguments.length === 2) + ) { + let regexContent = getStringValue(node.arguments[0], scope); + + if (regexContent && !isStringRawTaggedStaticTemplateLiteral(node.arguments[0])) { + regexContent = regexContent.replace(/\r/gu, "\\r").replace(/\n/gu, "\\n").replace(/\t/gu, "\\t").replace(/\f/gu, "\\f").replace(/\v/gu, "\\v"); + } + + const newRegExpValue = `/${regexContent || "(?:)"}/${getStringValue(node.arguments[1], scope) || ""}`; + + let noFix = false; + + try { + validateRegExpLiteral( + newRegExpValue, + { ecmaVersion: getRegexppEcmaVersion(context.parserOptions.ecmaVersion) } + ); + } catch { + noFix = true; + } + + const tokenBefore = sourceCode.getTokenBefore(node); + + if (tokenBefore && !validPrecedingTokens.includes(tokenBefore.value)) { + noFix = true; + } + + context.report({ + node, + messageId: "unexpectedRegExp", + ...(noFix ? {} : { + fix(fixer) { + return fixer.replaceTextRange( + node.range, + (text[node.range[0] - 1] === "/" ? " " : "") + newRegExpValue + (["in", "instanceof"].includes((sourceCode.getTokenAfter(node) || {}).value) && text[node.range[1]] === "i" ? " " : "") + ); + } + }) + }); } } } diff --git a/tests/lib/rules/prefer-regex-literals.js b/tests/lib/rules/prefer-regex-literals.js index ccd88ae74fd..9d11af3407f 100644 --- a/tests/lib/rules/prefer-regex-literals.js +++ b/tests/lib/rules/prefer-regex-literals.js @@ -33,17 +33,12 @@ ruleTester.run("prefer-regex-literals", rule, { "RegExp(`a` + suffix);", "new RegExp(String.raw`a` + suffix);", "RegExp('a', flags)", + "const flags = 'gu';RegExp('a', flags)", "RegExp('a', 'g' + flags)", "new RegExp(String.raw`a`, flags);", "RegExp(`${prefix}abc`)", "new RegExp(`a${b}c`);", - "new RegExp(`a${''}c`);", "new RegExp(String.raw`a${b}c`);", - "new RegExp(String.raw`a${''}c`);", - "new RegExp('a' + 'b')", - "RegExp(1)", - "new RegExp(/a/, 'u');", - "new RegExp(/a/);", { code: "new RegExp(/a/, flags);", options: [{ disallowRedundantWrapping: true }] @@ -134,116 +129,164 @@ ruleTester.run("prefer-regex-literals", rule, { invalid: [ { code: "new RegExp('abc');", + output: "/abc/;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "RegExp('abc');", + output: "/abc/;", errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "new RegExp('abc', 'g');", + output: "/abc/g;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "RegExp('abc', 'g');", + output: "/abc/g;", errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "new RegExp(`abc`);", + output: "/abc/;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "RegExp(`abc`);", + output: "/abc/;", errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "new RegExp(`abc`, `g`);", + output: "/abc/g;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "RegExp(`abc`, `g`);", + output: "/abc/g;", errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "new RegExp(String.raw`abc`);", + output: "/abc/;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "RegExp(String.raw`abc`);", + output: "/abc/;", errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "new RegExp(String.raw`abc`, String.raw`g`);", + output: "/abc/g;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "RegExp(String.raw`abc`, String.raw`g`);", + output: "/abc/g;", errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "new RegExp(String['raw']`a`);", + output: "/a/;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "new RegExp('');", + output: "/(?:)/;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "RegExp('', '');", + output: "/(?:)/;", errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "new RegExp(String.raw``);", + output: "/(?:)/;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "new RegExp('a', `g`);", + output: "/a/g;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "RegExp(`a`, 'g');", + output: "/a/g;", errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "RegExp(String.raw`a`, 'g');", + output: "/a/g;", errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "new RegExp(String.raw`\\d`, `g`);", + output: "/\\d/g;", + errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] + }, + { + code: "new RegExp(String.raw`\\\\d`, `g`);", + output: "/\\\\d/g;", + errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] + }, + { + code: "new RegExp(String['raw']`\\\\d`, `g`);", + output: "/\\\\d/g;", + errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] + }, + { + code: "new RegExp(String[\"raw\"]`\\\\d`, `g`);", + output: "/\\\\d/g;", errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "RegExp('a', String.raw`g`);", + output: "/a/g;", errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "new globalThis.RegExp('a');", + output: "/a/;", env: { es2020: true }, errors: [{ messageId: "unexpectedRegExp", type: "NewExpression" }] }, { code: "globalThis.RegExp('a');", + output: "/a/;", env: { es2020: true }, errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }] }, { code: "new RegExp(/a/);", + output: "/a/;", options: [{ disallowRedundantWrapping: true }], errors: [{ messageId: "unexpectedRedundantRegExp", type: "NewExpression", line: 1, column: 1 }] }, { code: "new RegExp(/a/, 'u');", + output: "/a/u;", options: [{ disallowRedundantWrapping: true }], errors: [{ messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, column: 1 }] }, { code: "new RegExp(/a/, `u`);", + output: "/a/u;", + options: [{ disallowRedundantWrapping: true }], + errors: [{ messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, column: 1 }] + }, + { + code: "new RegExp(/a/, String.raw`u`);", + output: "/a/u;", options: [{ disallowRedundantWrapping: true }], errors: [{ messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, column: 1 }] }, { code: "new RegExp('a');", + output: "/a/;", options: [{ disallowRedundantWrapping: true }], errors: [{ messageId: "unexpectedRegExp", type: "NewExpression", line: 1, column: 1 }] }, @@ -251,6 +294,805 @@ ruleTester.run("prefer-regex-literals", rule, { // Optional chaining { code: "new RegExp((String?.raw)`a`);", + output: "/a/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + + { + code: "new RegExp('+');", + output: null, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('*');", + output: null, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('+');", + output: null, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('*');", + output: null, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('+', 'g');", + output: null, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('*', 'g');", + output: null, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('+', 'g');", + output: null, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('*', 'g');", + output: null, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('abc', 'u');", + output: null, + parserOptions: { ecmaVersion: 3 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('abc', 'd');", + output: null, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('abc', 'd');", + output: "/abc/d;", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\\\\\\\', '');", + output: "/\\\\/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\n', '');", + output: "/\\n/;", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\n\\n', '');", + output: "/\\n\\n/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\t', '');", + output: "/\\t/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\t\\t', '');", + output: "/\\t\\t/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\r\\n', '');", + output: "/\\r\\n/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\u1234', 'g')", + output: "/ሴ/g", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\u{1234}', 'g')", + output: "/ሴ/g", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\u{11111}', 'g')", + output: "/𑄑/g", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\v', '');", + output: "/\\v/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\v\\v', '');", + output: "/\\v\\v/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\f', '');", + output: "/\\f/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\f\\f', '');", + output: "/\\f\\f/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\\\b', '');", + output: "/\\b/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\\\b\\\\b', '');", + output: "/\\b\\b/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('\\\\B\\\\b', '');", + output: "/\\B\\b/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\\\w', '');", + output: "/\\w/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new globalThis.RegExp('\\\\W', '');", + output: "/\\W/;", + globals: { + globalThis: "readonly" + }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\\\s', '');", + output: "/\\s/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('\\\\S', '')", + output: "/\\S/", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "globalThis.RegExp('\\\\d', '');", + output: "/\\d/;", + globals: { + globalThis: "readonly" + }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "globalThis.RegExp('\\\\D', '')", + output: "/\\D/", + globals: { + globalThis: "readonly" + }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "globalThis.RegExp('\\\\\\\\\\\\D', '')", + output: "/\\\\\\D/", + globals: { + globalThis: "readonly" + }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('\\\\D\\\\D', '')", + output: "/\\D\\D/", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new globalThis.RegExp('\\\\0\\\\0', '');", + output: "/\\0\\0/;", + globals: { + globalThis: "writable" + }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('\\\\0\\\\0', '');", + output: "/\\0\\0/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\\\0\\\\0\\\\0', '')", + output: "/\\0\\0\\0/", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp('\\\\78\\\\126\\\\5934', '')", + output: "/\\78\\126\\5934/", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp(String.raw`\\78\\126` + '\\\\5934', '' + `g` + '')", + output: "/\\78\\126\\5934/g", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new window['RegExp']('\\\\x56\\\\x78\\\\x45', '');", + output: "/\\x56\\x78\\x45/;", + env: { + browser: true + }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp(`a${''}c`);", + output: "/ac/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp(String.raw`a${''}c`);", + output: "/ac/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('a' + 'b')", + output: "/ab/", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('\\\\[' + \"b\\\\]\")", + output: "/\\[b\\]/", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('\\\\cA' + '')", + output: "/\\cA/", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('\\\\p{Emoji_Presentation}\\\\P{Script_Extensions=Latin}' + '', `ug`)", + output: "/\\p{Emoji_Presentation}\\P{Script_Extensions=Latin}/ug", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "a in(RegExp('abc'))", + output: "a in(/abc/)", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp('(\\\\p{Emoji_Presentation})\\\\1' + '', `ug`)", + output: "/(\\p{Emoji_Presentation})\\1/ug", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: `x = y + RegExp("foo").test(x) ? bar() : baz()`, + output: null, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "func(new RegExp(String.raw`a${''}c\\d`, 'u'),new RegExp(String.raw`a${''}c\\d`, 'u'))", + output: "func(/ac\\d/u,/ac\\d/u)", + errors: [{ messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }] + }, + { + code: `x = y; + RegExp("foo").test(x) ? bar() : baz()`, + output: `x = y; + /foo/.test(x) ? bar() : baz()`, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "typeof RegExp(\"foo\")", + output: "typeof /foo/", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "RegExp(\"foo\") instanceof RegExp(String.raw`blahblah`, 'g') ? typeof new RegExp('(\\\\p{Emoji_Presentation})\\\\1' + '', `ug`) : false", + output: "/foo/ instanceof /blahblah/g ? typeof /(\\p{Emoji_Presentation})\\1/ug : false", + errors: [{ messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }] + }, + { + code: "[ new RegExp(`someregular`)]", + output: "[ /someregular/]", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: `const totallyValidatesEmails = new RegExp("\\\\S+@(\\\\S+\\\\.)+\\\\S+") + if (typeof totallyValidatesEmails === 'object') { + runSomethingThatExists(Regexp('stuff')) + }`, + output: `const totallyValidatesEmails = /\\S+@(\\S+\\.)+\\S+/ + if (typeof totallyValidatesEmails === 'object') { + runSomethingThatExists(Regexp('stuff')) + }`, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "!new RegExp('^Hey, ', 'u') && new RegExp('jk$') && ~new RegExp('^Sup, ') || new RegExp('hi') + new RegExp('person') === -new RegExp('hi again') ? 5 * new RegExp('abc') : 'notregbutstring'", + output: "!/^Hey, /u && /jk$/ && ~/^Sup, / || /hi/ + /person/ === -/hi again/ ? 5 * /abc/ : 'notregbutstring'", + errors: [{ messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }] + }, + { + code: `#!/usr/bin/sh + RegExp("foo")`, + output: `#!/usr/bin/sh + /foo/`, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "async function abc(){await new RegExp(\"foo\")}", + output: "async function abc(){await /foo/}", + parserOptions: { ecmaVersion: 8, sourceType: "module" }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "function* abc(){yield new RegExp(\"foo\")}", + output: "function* abc(){yield /foo/}", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "function* abc(){yield* new RegExp(\"foo\")}", + output: "function* abc(){yield* /foo/}", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "console.log({ ...new RegExp('a') })", + output: "console.log({ .../a/ })", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "delete RegExp('a');", + output: "delete /a/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "void RegExp('a');", + output: "void /a/;", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp(\"\\\\S+@(\\\\S+\\\\.)+\\\\S+\")**RegExp('a')", + output: "/\\S+@(\\S+\\.)+\\S+/**/a/", + errors: [{ messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }] + }, + { + code: "new RegExp(\"\\\\S+@(\\\\S+\\\\.)+\\\\S+\")%RegExp('a')", + output: "/\\S+@(\\S+\\.)+\\S+/%/a/", + errors: [{ messageId: "unexpectedRegExp" }, { messageId: "unexpectedRegExp" }] + }, + { + code: "a in RegExp('abc')", + output: "a in /abc/", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ == new RegExp('cba'); + `, + output: ` + /abc/ == /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ === new RegExp('cba'); + `, + output: ` + /abc/ === /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ != new RegExp('cba'); + `, + output: ` + /abc/ != /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ !== new RegExp('cba'); + `, + output: ` + /abc/ !== /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ > new RegExp('cba'); + `, + output: ` + /abc/ > /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ < new RegExp('cba'); + `, + output: ` + /abc/ < /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ >= new RegExp('cba'); + `, + output: ` + /abc/ >= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ <= new RegExp('cba'); + `, + output: ` + /abc/ <= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ << new RegExp('cba'); + `, + output: ` + /abc/ << /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ >> new RegExp('cba'); + `, + output: ` + /abc/ >> /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ >>> new RegExp('cba'); + `, + output: ` + /abc/ >>> /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ ^ new RegExp('cba'); + `, + output: ` + /abc/ ^ /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ & new RegExp('cba'); + `, + output: ` + /abc/ & /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + /abc/ | new RegExp('cba'); + `, + output: ` + /abc/ | /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + null ?? new RegExp('blah') + `, + output: ` + null ?? /blah/ + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc *= new RegExp('blah') + `, + output: ` + abc *= /blah/ + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + console.log({a: new RegExp('sup')}) + `, + output: ` + console.log({a: /sup/}) + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + console.log(() => {new RegExp('sup')}) + `, + output: ` + console.log(() => {/sup/}) + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + function abc() {new RegExp('sup')} + `, + output: ` + function abc() {/sup/} + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + function abc() {return new RegExp('sup')} + `, + output: ` + function abc() {return /sup/} + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc <<= new RegExp('cba'); + `, + output: ` + abc <<= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc >>= new RegExp('cba'); + `, + output: ` + abc >>= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc >>>= new RegExp('cba'); + `, + output: ` + abc >>>= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc ^= new RegExp('cba'); + `, + output: ` + abc ^= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc &= new RegExp('cba'); + `, + output: ` + abc &= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc |= new RegExp('cba'); + `, + output: ` + abc |= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc ??= new RegExp('cba'); + `, + output: ` + abc ??= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc &&= new RegExp('cba'); + `, + output: ` + abc &&= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc ||= new RegExp('cba'); + `, + output: ` + abc ||= /cba/; + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc **= new RegExp('blah') + `, + output: ` + abc **= /blah/ + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc /= new RegExp('blah') + `, + output: ` + abc /= /blah/ + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc += new RegExp('blah') + `, + output: ` + abc += /blah/ + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc -= new RegExp('blah') + `, + output: ` + abc -= /blah/ + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + abc %= new RegExp('blah') + `, + output: ` + abc %= /blah/ + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + () => new RegExp('blah') + `, + output: ` + () => /blah/ + `, + parserOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "a/RegExp(\"foo\")in b", + output: "a/ /foo/ in b", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "a/RegExp(\"foo\")instanceof b", + output: "a/ /foo/ instanceof b", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "do RegExp(\"foo\")\nwhile (true);", + output: "do /foo/\nwhile (true);", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "for(let i;i<5;i++) { break\nnew RegExp('search')}", + output: "for(let i;i<5;i++) { break\n/search/}", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "for(let i;i<5;i++) { continue\nnew RegExp('search')}", + output: "for(let i;i<5;i++) { continue\n/search/}", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: ` + switch (value) { + case "possibility": + console.log('possibility matched') + case RegExp('myReg').toString(): + console.log('matches a regexp\\' toString value') + break; + } + `, + output: ` + switch (value) { + case "possibility": + console.log('possibility matched') + case /myReg/.toString(): + console.log('matches a regexp\\' toString value') + break; + } + `, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "throw new RegExp('abcdefg') // fail with a regular expression", + output: "throw /abcdefg/ // fail with a regular expression", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "for (value of new RegExp('something being searched')) { console.log(value) }", + output: "for (value of /something being searched/) { console.log(value) }", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "(async function(){for await (value of new RegExp('something being searched')) { console.log(value) }})()", + output: "(async function(){for await (value of /something being searched/) { console.log(value) }})()", + parserOptions: { ecmaVersion: 2018 }, + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "for (value in new RegExp('something being searched')) { console.log(value) }", + output: "for (value in /something being searched/) { console.log(value) }", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "if (condition1 && condition2) new RegExp('avalue').test(str);", + output: "if (condition1 && condition2) /avalue/.test(str);", + errors: [{ messageId: "unexpectedRegExp" }] + }, + { + code: "debugger\nnew RegExp('myReg')", + output: "debugger\n/myReg/", errors: [{ messageId: "unexpectedRegExp" }] } ]