1/** 2 * @fileoverview Validate strings passed to the RegExp constructor 3 * @author Michael Ficarra 4 */ 5"use strict"; 6 7//------------------------------------------------------------------------------ 8// Requirements 9//------------------------------------------------------------------------------ 10 11const RegExpValidator = require("regexpp").RegExpValidator; 12const validator = new RegExpValidator({ ecmaVersion: 2018 }); 13const validFlags = /[gimuys]/gu; 14const undefined1 = void 0; 15 16//------------------------------------------------------------------------------ 17// Rule Definition 18//------------------------------------------------------------------------------ 19 20module.exports = { 21 meta: { 22 type: "problem", 23 24 docs: { 25 description: "disallow invalid regular expression strings in `RegExp` constructors", 26 category: "Possible Errors", 27 recommended: true, 28 url: "https://eslint.org/docs/rules/no-invalid-regexp" 29 }, 30 31 schema: [{ 32 type: "object", 33 properties: { 34 allowConstructorFlags: { 35 type: "array", 36 items: { 37 type: "string" 38 } 39 } 40 }, 41 additionalProperties: false 42 }], 43 44 messages: { 45 regexMessage: "{{message}}." 46 } 47 }, 48 49 create(context) { 50 51 const options = context.options[0]; 52 let allowedFlags = null; 53 54 if (options && options.allowConstructorFlags) { 55 const temp = options.allowConstructorFlags.join("").replace(validFlags, ""); 56 57 if (temp) { 58 allowedFlags = new RegExp(`[${temp}]`, "giu"); 59 } 60 } 61 62 /** 63 * Check if node is a string 64 * @param {ASTNode} node node to evaluate 65 * @returns {boolean} True if its a string 66 * @private 67 */ 68 function isString(node) { 69 return node && node.type === "Literal" && typeof node.value === "string"; 70 } 71 72 /** 73 * Check syntax error in a given pattern. 74 * @param {string} pattern The RegExp pattern to validate. 75 * @param {boolean} uFlag The Unicode flag. 76 * @returns {string|null} The syntax error. 77 */ 78 function validateRegExpPattern(pattern, uFlag) { 79 try { 80 validator.validatePattern(pattern, undefined1, undefined1, uFlag); 81 return null; 82 } catch (err) { 83 return err.message; 84 } 85 } 86 87 /** 88 * Check syntax error in a given flags. 89 * @param {string} flags The RegExp flags to validate. 90 * @returns {string|null} The syntax error. 91 */ 92 function validateRegExpFlags(flags) { 93 try { 94 validator.validateFlags(flags); 95 return null; 96 } catch { 97 return `Invalid flags supplied to RegExp constructor '${flags}'`; 98 } 99 } 100 101 return { 102 "CallExpression, NewExpression"(node) { 103 if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp" || !isString(node.arguments[0])) { 104 return; 105 } 106 const pattern = node.arguments[0].value; 107 let flags = isString(node.arguments[1]) ? node.arguments[1].value : ""; 108 109 if (allowedFlags) { 110 flags = flags.replace(allowedFlags, ""); 111 } 112 113 // If flags are unknown, check both are errored or not. 114 const message = validateRegExpFlags(flags) || ( 115 flags 116 ? validateRegExpPattern(pattern, flags.indexOf("u") !== -1) 117 : validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false) 118 ); 119 120 if (message) { 121 context.report({ 122 node, 123 messageId: "regexMessage", 124 data: { message } 125 }); 126 } 127 } 128 }; 129 } 130}; 131