UNPKG

5.03 kBJavaScriptView Raw
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();
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 * Gets flags of a regular expression created by the given `RegExp()` or `new RegExp()` call
74 * Examples:
75 * new RegExp(".") // => ""
76 * new RegExp(".", "gu") // => "gu"
77 * new RegExp(".", flags) // => null
78 * @param {ASTNode} node `CallExpression` or `NewExpression` node
79 * @returns {string|null} flags if they can be determined, `null` otherwise
80 * @private
81 */
82 function getFlags(node) {
83 if (node.arguments.length < 2) {
84 return "";
85 }
86
87 if (isString(node.arguments[1])) {
88 return node.arguments[1].value;
89 }
90
91 return null;
92 }
93
94 /**
95 * Check syntax error in a given pattern.
96 * @param {string} pattern The RegExp pattern to validate.
97 * @param {boolean} uFlag The Unicode flag.
98 * @returns {string|null} The syntax error.
99 */
100 function validateRegExpPattern(pattern, uFlag) {
101 try {
102 validator.validatePattern(pattern, undefined1, undefined1, uFlag);
103 return null;
104 } catch (err) {
105 return err.message;
106 }
107 }
108
109 /**
110 * Check syntax error in a given flags.
111 * @param {string} flags The RegExp flags to validate.
112 * @returns {string|null} The syntax error.
113 */
114 function validateRegExpFlags(flags) {
115 try {
116 validator.validateFlags(flags);
117 return null;
118 } catch {
119 return `Invalid flags supplied to RegExp constructor '${flags}'`;
120 }
121 }
122
123 return {
124 "CallExpression, NewExpression"(node) {
125 if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp" || !isString(node.arguments[0])) {
126 return;
127 }
128 const pattern = node.arguments[0].value;
129 let flags = getFlags(node);
130
131 if (flags && allowedFlags) {
132 flags = flags.replace(allowedFlags, "");
133 }
134
135 const message =
136 (
137 flags && validateRegExpFlags(flags)
138 ) ||
139 (
140
141 // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
142 flags === null
143 ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
144 : validateRegExpPattern(pattern, flags.includes("u"))
145 );
146
147 if (message) {
148 context.report({
149 node,
150 messageId: "regexMessage",
151 data: { message }
152 });
153 }
154 }
155 };
156 }
157};