blob: ee19932896609934ccae25fe9a697889c860f53e [file] [log] [blame]
Yang Guo4fd355c2019-09-19 10:59:03 +02001/**
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;
Tim van der Lippeb97da6b2021-02-12 14:32:53 +000012const validator = new RegExpValidator();
Tim van der Lippe0fb47802021-11-08 16:23:10 +000013const validFlags = /[dgimsuy]/gu;
Yang Guo4fd355c2019-09-19 10:59:03 +020014const 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",
Yang Guo4fd355c2019-09-19 10:59:03 +020026 recommended: true,
27 url: "https://eslint.org/docs/rules/no-invalid-regexp"
28 },
29
30 schema: [{
31 type: "object",
32 properties: {
33 allowConstructorFlags: {
34 type: "array",
35 items: {
36 type: "string"
37 }
38 }
39 },
40 additionalProperties: false
Tim van der Lippe16aca392020-11-13 11:37:13 +000041 }],
42
43 messages: {
44 regexMessage: "{{message}}."
45 }
Yang Guo4fd355c2019-09-19 10:59:03 +020046 },
47
48 create(context) {
49
50 const options = context.options[0];
51 let allowedFlags = null;
52
53 if (options && options.allowConstructorFlags) {
54 const temp = options.allowConstructorFlags.join("").replace(validFlags, "");
55
56 if (temp) {
57 allowedFlags = new RegExp(`[${temp}]`, "giu");
58 }
59 }
60
61 /**
62 * Check if node is a string
63 * @param {ASTNode} node node to evaluate
64 * @returns {boolean} True if its a string
65 * @private
66 */
67 function isString(node) {
68 return node && node.type === "Literal" && typeof node.value === "string";
69 }
70
71 /**
Tim van der Lippeb97da6b2021-02-12 14:32:53 +000072 * Gets flags of a regular expression created by the given `RegExp()` or `new RegExp()` call
73 * Examples:
74 * new RegExp(".") // => ""
75 * new RegExp(".", "gu") // => "gu"
76 * new RegExp(".", flags) // => null
77 * @param {ASTNode} node `CallExpression` or `NewExpression` node
78 * @returns {string|null} flags if they can be determined, `null` otherwise
79 * @private
80 */
81 function getFlags(node) {
82 if (node.arguments.length < 2) {
83 return "";
84 }
85
86 if (isString(node.arguments[1])) {
87 return node.arguments[1].value;
88 }
89
90 return null;
91 }
92
93 /**
Yang Guo4fd355c2019-09-19 10:59:03 +020094 * Check syntax error in a given pattern.
95 * @param {string} pattern The RegExp pattern to validate.
96 * @param {boolean} uFlag The Unicode flag.
97 * @returns {string|null} The syntax error.
98 */
99 function validateRegExpPattern(pattern, uFlag) {
100 try {
101 validator.validatePattern(pattern, undefined1, undefined1, uFlag);
102 return null;
103 } catch (err) {
104 return err.message;
105 }
106 }
107
108 /**
109 * Check syntax error in a given flags.
110 * @param {string} flags The RegExp flags to validate.
111 * @returns {string|null} The syntax error.
112 */
113 function validateRegExpFlags(flags) {
114 try {
115 validator.validateFlags(flags);
116 return null;
Tim van der Lippe16aca392020-11-13 11:37:13 +0000117 } catch {
Yang Guo4fd355c2019-09-19 10:59:03 +0200118 return `Invalid flags supplied to RegExp constructor '${flags}'`;
119 }
120 }
121
122 return {
123 "CallExpression, NewExpression"(node) {
124 if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp" || !isString(node.arguments[0])) {
125 return;
126 }
127 const pattern = node.arguments[0].value;
Tim van der Lippeb97da6b2021-02-12 14:32:53 +0000128 let flags = getFlags(node);
Yang Guo4fd355c2019-09-19 10:59:03 +0200129
Tim van der Lippeb97da6b2021-02-12 14:32:53 +0000130 if (flags && allowedFlags) {
Yang Guo4fd355c2019-09-19 10:59:03 +0200131 flags = flags.replace(allowedFlags, "");
132 }
133
Tim van der Lippeb97da6b2021-02-12 14:32:53 +0000134 const message =
135 (
136 flags && validateRegExpFlags(flags)
137 ) ||
138 (
139
140 // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
141 flags === null
142 ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
143 : validateRegExpPattern(pattern, flags.includes("u"))
144 );
Yang Guo4fd355c2019-09-19 10:59:03 +0200145
146 if (message) {
147 context.report({
148 node,
Tim van der Lippe16aca392020-11-13 11:37:13 +0000149 messageId: "regexMessage",
Yang Guo4fd355c2019-09-19 10:59:03 +0200150 data: { message }
151 });
152 }
153 }
154 };
155 }
156};