]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Validate strings passed to the RegExp constructor | |
3 | * @author Michael Ficarra | |
4 | */ | |
5 | "use strict"; | |
6 | ||
7 | //------------------------------------------------------------------------------ | |
8 | // Requirements | |
9 | //------------------------------------------------------------------------------ | |
10 | ||
11 | const RegExpValidator = require("regexpp").RegExpValidator; | |
456be15e | 12 | const validator = new RegExpValidator(); |
609c276f | 13 | const validFlags = /[dgimsuy]/gu; |
eb39fafa DC |
14 | const undefined1 = void 0; |
15 | ||
16 | //------------------------------------------------------------------------------ | |
17 | // Rule Definition | |
18 | //------------------------------------------------------------------------------ | |
19 | ||
34eeec05 | 20 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
21 | module.exports = { |
22 | meta: { | |
23 | type: "problem", | |
24 | ||
25 | docs: { | |
8f9d1d4d | 26 | description: "Disallow invalid regular expression strings in `RegExp` constructors", |
eb39fafa DC |
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 | ||
5422a9cc TL |
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 | ||
eb39fafa DC |
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; | |
d3726936 | 118 | } catch { |
eb39fafa DC |
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; | |
5422a9cc | 129 | let flags = getFlags(node); |
eb39fafa | 130 | |
5422a9cc | 131 | if (flags && allowedFlags) { |
eb39fafa DC |
132 | flags = flags.replace(allowedFlags, ""); |
133 | } | |
134 | ||
5422a9cc TL |
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 | ); | |
eb39fafa DC |
146 | |
147 | if (message) { | |
148 | context.report({ | |
149 | node, | |
150 | messageId: "regexMessage", | |
151 | data: { message } | |
152 | }); | |
153 | } | |
154 | } | |
155 | }; | |
156 | } | |
157 | }; |