| /** | |
|  * @fileoverview Validate strings passed to the RegExp constructor | |
|  * @author Michael Ficarra | |
|  */ | |
| "use strict"; | |
| 
 | |
| //------------------------------------------------------------------------------ | |
| // Requirements | |
| //------------------------------------------------------------------------------ | |
|  | |
| const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; | |
| const validator = new RegExpValidator(); | |
| const validFlags = /[dgimsuvy]/gu; | |
| const undefined1 = void 0; | |
| 
 | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
|  | |
| /** @type {import('../shared/types').Rule} */ | |
| module.exports = { | |
|     meta: { | |
|         type: "problem", | |
| 
 | |
|         docs: { | |
|             description: "Disallow invalid regular expression strings in `RegExp` constructors", | |
|             recommended: true, | |
|             url: "https://eslint.org/docs/latest/rules/no-invalid-regexp" | |
|         }, | |
| 
 | |
|         schema: [{ | |
|             type: "object", | |
|             properties: { | |
|                 allowConstructorFlags: { | |
|                     type: "array", | |
|                     items: { | |
|                         type: "string" | |
|                     } | |
|                 } | |
|             }, | |
|             additionalProperties: false | |
|         }], | |
| 
 | |
|         messages: { | |
|             regexMessage: "{{message}}." | |
|         } | |
|     }, | |
| 
 | |
|     create(context) { | |
| 
 | |
|         const options = context.options[0]; | |
|         let allowedFlags = null; | |
| 
 | |
|         if (options && options.allowConstructorFlags) { | |
|             const temp = options.allowConstructorFlags.join("").replace(validFlags, ""); | |
| 
 | |
|             if (temp) { | |
|                 allowedFlags = new RegExp(`[${temp}]`, "giu"); | |
|             } | |
|         } | |
| 
 | |
|         /** | |
|          * Reports error with the provided message. | |
|          * @param {ASTNode} node The node holding the invalid RegExp | |
|          * @param {string} message The message to report. | |
|          * @returns {void} | |
|          */ | |
|         function report(node, message) { | |
|             context.report({ | |
|                 node, | |
|                 messageId: "regexMessage", | |
|                 data: { message } | |
|             }); | |
|         } | |
| 
 | |
|         /** | |
|          * Check if node is a string | |
|          * @param {ASTNode} node node to evaluate | |
|          * @returns {boolean} True if its a string | |
|          * @private | |
|          */ | |
|         function isString(node) { | |
|             return node && node.type === "Literal" && typeof node.value === "string"; | |
|         } | |
| 
 | |
|         /** | |
|          * Gets flags of a regular expression created by the given `RegExp()` or `new RegExp()` call | |
|          * Examples: | |
|          *     new RegExp(".")         // => "" | |
|          *     new RegExp(".", "gu")   // => "gu" | |
|          *     new RegExp(".", flags)  // => null | |
|          * @param {ASTNode} node `CallExpression` or `NewExpression` node | |
|          * @returns {string|null} flags if they can be determined, `null` otherwise | |
|          * @private | |
|          */ | |
|         function getFlags(node) { | |
|             if (node.arguments.length < 2) { | |
|                 return ""; | |
|             } | |
| 
 | |
|             if (isString(node.arguments[1])) { | |
|                 return node.arguments[1].value; | |
|             } | |
| 
 | |
|             return null; | |
|         } | |
| 
 | |
|         /** | |
|          * Check syntax error in a given pattern. | |
|          * @param {string} pattern The RegExp pattern to validate. | |
|          * @param {Object} flags The RegExp flags to validate. | |
|          * @param {boolean} [flags.unicode] The Unicode flag. | |
|          * @param {boolean} [flags.unicodeSets] The UnicodeSets flag. | |
|          * @returns {string|null} The syntax error. | |
|          */ | |
|         function validateRegExpPattern(pattern, flags) { | |
|             try { | |
|                 validator.validatePattern(pattern, undefined1, undefined1, flags); | |
|                 return null; | |
|             } catch (err) { | |
|                 return err.message; | |
|             } | |
|         } | |
| 
 | |
|         /** | |
|          * Check syntax error in a given flags. | |
|          * @param {string|null} flags The RegExp flags to validate. | |
|          * @returns {string|null} The syntax error. | |
|          */ | |
|         function validateRegExpFlags(flags) { | |
|             if (!flags) { | |
|                 return null; | |
|             } | |
|             try { | |
|                 validator.validateFlags(flags); | |
|             } catch { | |
|                 return `Invalid flags supplied to RegExp constructor '${flags}'`; | |
|             } | |
| 
 | |
|             /* | |
|              * `regexpp` checks the combination of `u` and `v` flags when parsing `Pattern` according to `ecma262`, | |
|              * but this rule may check only the flag when the pattern is unidentifiable, so check it here. | |
|              * https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern | |
|              */ | |
|             if (flags.includes("u") && flags.includes("v")) { | |
|                 return "Regex 'u' and 'v' flags cannot be used together"; | |
|             } | |
|             return null; | |
|         } | |
| 
 | |
|         return { | |
|             "CallExpression, NewExpression"(node) { | |
|                 if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp") { | |
|                     return; | |
|                 } | |
| 
 | |
|                 let flags = getFlags(node); | |
| 
 | |
|                 if (flags && allowedFlags) { | |
|                     flags = flags.replace(allowedFlags, ""); | |
|                 } | |
| 
 | |
|                 let message = validateRegExpFlags(flags); | |
| 
 | |
|                 if (message) { | |
|                     report(node, message); | |
|                     return; | |
|                 } | |
| 
 | |
|                 if (!isString(node.arguments[0])) { | |
|                     return; | |
|                 } | |
| 
 | |
|                 const pattern = node.arguments[0].value; | |
| 
 | |
|                 message = ( | |
| 
 | |
|                     // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag | |
|                     flags === null | |
|                         ? ( | |
|                             validateRegExpPattern(pattern, { unicode: true, unicodeSets: false }) && | |
|                             validateRegExpPattern(pattern, { unicode: false, unicodeSets: true }) && | |
|                             validateRegExpPattern(pattern, { unicode: false, unicodeSets: false }) | |
|                         ) | |
|                         : validateRegExpPattern(pattern, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }) | |
|                 ); | |
| 
 | |
|                 if (message) { | |
|                     report(node, message); | |
|                 } | |
|             } | |
|         }; | |
|     } | |
| };
 |