blob: fbfeb5636d754a2b1138219f9a0b087751a606f5 [file] [log] [blame]
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +01001/**
2 * @fileoverview Rule to disallow use of the `RegExp` constructor in favor of regular expression literals
3 * @author Milos Djermanovic
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils");
14
15//------------------------------------------------------------------------------
16// Helpers
17//------------------------------------------------------------------------------
18
19/**
20 * Determines whether the given node is a string literal.
21 * @param {ASTNode} node Node to check.
22 * @returns {boolean} True if the node is a string literal.
23 */
24function isStringLiteral(node) {
25 return node.type === "Literal" && typeof node.value === "string";
26}
27
28/**
Tim van der Lippe16aca392020-11-13 11:37:13 +000029 * Determines whether the given node is a regex literal.
30 * @param {ASTNode} node Node to check.
31 * @returns {boolean} True if the node is a regex literal.
32 */
33function isRegexLiteral(node) {
34 return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex");
35}
36
37/**
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010038 * Determines whether the given node is a template literal without expressions.
39 * @param {ASTNode} node Node to check.
40 * @returns {boolean} True if the node is a template literal without expressions.
41 */
42function isStaticTemplateLiteral(node) {
43 return node.type === "TemplateLiteral" && node.expressions.length === 0;
44}
45
46
47//------------------------------------------------------------------------------
48// Rule Definition
49//------------------------------------------------------------------------------
50
51module.exports = {
52 meta: {
53 type: "suggestion",
54
55 docs: {
56 description: "disallow use of the `RegExp` constructor in favor of regular expression literals",
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010057 recommended: false,
58 url: "https://eslint.org/docs/rules/prefer-regex-literals"
59 },
60
Tim van der Lippe16aca392020-11-13 11:37:13 +000061 schema: [
62 {
63 type: "object",
64 properties: {
65 disallowRedundantWrapping: {
66 type: "boolean",
67 default: false
68 }
69 },
70 additionalProperties: false
71 }
72 ],
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010073
74 messages: {
Tim van der Lippe16aca392020-11-13 11:37:13 +000075 unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.",
76 unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.",
77 unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor."
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010078 }
79 },
80
81 create(context) {
Tim van der Lippe16aca392020-11-13 11:37:13 +000082 const [{ disallowRedundantWrapping = false } = {}] = context.options;
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010083
84 /**
85 * Determines whether the given identifier node is a reference to a global variable.
86 * @param {ASTNode} node `Identifier` node to check.
87 * @returns {boolean} True if the identifier is a reference to a global variable.
88 */
89 function isGlobalReference(node) {
90 const scope = context.getScope();
91 const variable = findVariable(scope, node);
92
93 return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
94 }
95
96 /**
97 * Determines whether the given node is a String.raw`` tagged template expression
98 * with a static template literal.
99 * @param {ASTNode} node Node to check.
100 * @returns {boolean} True if the node is String.raw`` with a static template.
101 */
102 function isStringRawTaggedStaticTemplateLiteral(node) {
103 return node.type === "TaggedTemplateExpression" &&
Tim van der Lippe16aca392020-11-13 11:37:13 +0000104 astUtils.isSpecificMemberAccess(node.tag, "String", "raw") &&
105 isGlobalReference(astUtils.skipChainExpression(node.tag).object) &&
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100106 isStaticTemplateLiteral(node.quasi);
107 }
108
109 /**
110 * Determines whether the given node is considered to be a static string by the logic of this rule.
111 * @param {ASTNode} node Node to check.
112 * @returns {boolean} True if the node is a static string.
113 */
114 function isStaticString(node) {
115 return isStringLiteral(node) ||
116 isStaticTemplateLiteral(node) ||
117 isStringRawTaggedStaticTemplateLiteral(node);
118 }
119
Tim van der Lippe16aca392020-11-13 11:37:13 +0000120 /**
121 * Determines whether the relevant arguments of the given are all static string literals.
122 * @param {ASTNode} node Node to check.
123 * @returns {boolean} True if all arguments are static strings.
124 */
125 function hasOnlyStaticStringArguments(node) {
126 const args = node.arguments;
127
128 if ((args.length === 1 || args.length === 2) && args.every(isStaticString)) {
129 return true;
130 }
131
132 return false;
133 }
134
135 /**
136 * Determines whether the arguments of the given node indicate that a regex literal is unnecessarily wrapped.
137 * @param {ASTNode} node Node to check.
138 * @returns {boolean} True if the node already contains a regex literal argument.
139 */
140 function isUnnecessarilyWrappedRegexLiteral(node) {
141 const args = node.arguments;
142
143 if (args.length === 1 && isRegexLiteral(args[0])) {
144 return true;
145 }
146
147 if (args.length === 2 && isRegexLiteral(args[0]) && isStaticString(args[1])) {
148 return true;
149 }
150
151 return false;
152 }
153
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100154 return {
155 Program() {
156 const scope = context.getScope();
157 const tracker = new ReferenceTracker(scope);
158 const traceMap = {
159 RegExp: {
160 [CALL]: true,
161 [CONSTRUCT]: true
162 }
163 };
164
165 for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
Tim van der Lippe16aca392020-11-13 11:37:13 +0000166 if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) {
167 if (node.arguments.length === 2) {
168 context.report({ node, messageId: "unexpectedRedundantRegExpWithFlags" });
169 } else {
170 context.report({ node, messageId: "unexpectedRedundantRegExp" });
171 }
172 } else if (hasOnlyStaticStringArguments(node)) {
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100173 context.report({ node, messageId: "unexpectedRegExp" });
174 }
175 }
176 }
177 };
178 }
179};