UNPKG

9.54 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag use of eval() statement
3 * @author Nicholas C. Zakas
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("../util/ast-utils");
13
14//------------------------------------------------------------------------------
15// Helpers
16//------------------------------------------------------------------------------
17
18const candidatesOfGlobalObject = Object.freeze([
19 "global",
20 "window"
21]);
22
23/**
24 * Checks a given node is a Identifier node of the specified name.
25 *
26 * @param {ASTNode} node - A node to check.
27 * @param {string} name - A name to check.
28 * @returns {boolean} `true` if the node is a Identifier node of the name.
29 */
30function isIdentifier(node, name) {
31 return node.type === "Identifier" && node.name === name;
32}
33
34/**
35 * Checks a given node is a Literal node of the specified string value.
36 *
37 * @param {ASTNode} node - A node to check.
38 * @param {string} name - A name to check.
39 * @returns {boolean} `true` if the node is a Literal node of the name.
40 */
41function isConstant(node, name) {
42 switch (node.type) {
43 case "Literal":
44 return node.value === name;
45
46 case "TemplateLiteral":
47 return (
48 node.expressions.length === 0 &&
49 node.quasis[0].value.cooked === name
50 );
51
52 default:
53 return false;
54 }
55}
56
57/**
58 * Checks a given node is a MemberExpression node which has the specified name's
59 * property.
60 *
61 * @param {ASTNode} node - A node to check.
62 * @param {string} name - A name to check.
63 * @returns {boolean} `true` if the node is a MemberExpression node which has
64 * the specified name's property
65 */
66function isMember(node, name) {
67 return (
68 node.type === "MemberExpression" &&
69 (node.computed ? isConstant : isIdentifier)(node.property, name)
70 );
71}
72
73//------------------------------------------------------------------------------
74// Rule Definition
75//------------------------------------------------------------------------------
76
77module.exports = {
78 meta: {
79 type: "suggestion",
80
81 docs: {
82 description: "disallow the use of `eval()`",
83 category: "Best Practices",
84 recommended: false,
85 url: "https://eslint.org/docs/rules/no-eval"
86 },
87
88 schema: [
89 {
90 type: "object",
91 properties: {
92 allowIndirect: { type: "boolean", default: false }
93 },
94 additionalProperties: false
95 }
96 ],
97
98 messages: {
99 unexpected: "eval can be harmful."
100 }
101 },
102
103 create(context) {
104 const allowIndirect = Boolean(
105 context.options[0] &&
106 context.options[0].allowIndirect
107 );
108 const sourceCode = context.getSourceCode();
109 let funcInfo = null;
110
111 /**
112 * Pushs a variable scope (Program or Function) information to the stack.
113 *
114 * This is used in order to check whether or not `this` binding is a
115 * reference to the global object.
116 *
117 * @param {ASTNode} node - A node of the scope. This is one of Program,
118 * FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression.
119 * @returns {void}
120 */
121 function enterVarScope(node) {
122 const strict = context.getScope().isStrict;
123
124 funcInfo = {
125 upper: funcInfo,
126 node,
127 strict,
128 defaultThis: false,
129 initialized: strict
130 };
131 }
132
133 /**
134 * Pops a variable scope from the stack.
135 *
136 * @returns {void}
137 */
138 function exitVarScope() {
139 funcInfo = funcInfo.upper;
140 }
141
142 /**
143 * Reports a given node.
144 *
145 * `node` is `Identifier` or `MemberExpression`.
146 * The parent of `node` might be `CallExpression`.
147 *
148 * The location of the report is always `eval` `Identifier` (or possibly
149 * `Literal`). The type of the report is `CallExpression` if the parent is
150 * `CallExpression`. Otherwise, it's the given node type.
151 *
152 * @param {ASTNode} node - A node to report.
153 * @returns {void}
154 */
155 function report(node) {
156 const parent = node.parent;
157 const locationNode = node.type === "MemberExpression"
158 ? node.property
159 : node;
160
161 const reportNode = parent.type === "CallExpression" && parent.callee === node
162 ? parent
163 : node;
164
165 context.report({
166 node: reportNode,
167 loc: locationNode.loc.start,
168 messageId: "unexpected"
169 });
170 }
171
172 /**
173 * Reports accesses of `eval` via the global object.
174 *
175 * @param {eslint-scope.Scope} globalScope - The global scope.
176 * @returns {void}
177 */
178 function reportAccessingEvalViaGlobalObject(globalScope) {
179 for (let i = 0; i < candidatesOfGlobalObject.length; ++i) {
180 const name = candidatesOfGlobalObject[i];
181 const variable = astUtils.getVariableByName(globalScope, name);
182
183 if (!variable) {
184 continue;
185 }
186
187 const references = variable.references;
188
189 for (let j = 0; j < references.length; ++j) {
190 const identifier = references[j].identifier;
191 let node = identifier.parent;
192
193 // To detect code like `window.window.eval`.
194 while (isMember(node, name)) {
195 node = node.parent;
196 }
197
198 // Reports.
199 if (isMember(node, "eval")) {
200 report(node);
201 }
202 }
203 }
204 }
205
206 /**
207 * Reports all accesses of `eval` (excludes direct calls to eval).
208 *
209 * @param {eslint-scope.Scope} globalScope - The global scope.
210 * @returns {void}
211 */
212 function reportAccessingEval(globalScope) {
213 const variable = astUtils.getVariableByName(globalScope, "eval");
214
215 if (!variable) {
216 return;
217 }
218
219 const references = variable.references;
220
221 for (let i = 0; i < references.length; ++i) {
222 const reference = references[i];
223 const id = reference.identifier;
224
225 if (id.name === "eval" && !astUtils.isCallee(id)) {
226
227 // Is accessing to eval (excludes direct calls to eval)
228 report(id);
229 }
230 }
231 }
232
233 if (allowIndirect) {
234
235 // Checks only direct calls to eval. It's simple!
236 return {
237 "CallExpression:exit"(node) {
238 const callee = node.callee;
239
240 if (isIdentifier(callee, "eval")) {
241 report(callee);
242 }
243 }
244 };
245 }
246
247 return {
248 "CallExpression:exit"(node) {
249 const callee = node.callee;
250
251 if (isIdentifier(callee, "eval")) {
252 report(callee);
253 }
254 },
255
256 Program(node) {
257 const scope = context.getScope(),
258 features = context.parserOptions.ecmaFeatures || {},
259 strict =
260 scope.isStrict ||
261 node.sourceType === "module" ||
262 (features.globalReturn && scope.childScopes[0].isStrict);
263
264 funcInfo = {
265 upper: null,
266 node,
267 strict,
268 defaultThis: true,
269 initialized: true
270 };
271 },
272
273 "Program:exit"() {
274 const globalScope = context.getScope();
275
276 exitVarScope();
277 reportAccessingEval(globalScope);
278 reportAccessingEvalViaGlobalObject(globalScope);
279 },
280
281 FunctionDeclaration: enterVarScope,
282 "FunctionDeclaration:exit": exitVarScope,
283 FunctionExpression: enterVarScope,
284 "FunctionExpression:exit": exitVarScope,
285 ArrowFunctionExpression: enterVarScope,
286 "ArrowFunctionExpression:exit": exitVarScope,
287
288 ThisExpression(node) {
289 if (!isMember(node.parent, "eval")) {
290 return;
291 }
292
293 /*
294 * `this.eval` is found.
295 * Checks whether or not the value of `this` is the global object.
296 */
297 if (!funcInfo.initialized) {
298 funcInfo.initialized = true;
299 funcInfo.defaultThis = astUtils.isDefaultThisBinding(
300 funcInfo.node,
301 sourceCode
302 );
303 }
304
305 if (!funcInfo.strict && funcInfo.defaultThis) {
306
307 // `this.eval` is possible built-in `eval`.
308 report(node.parent);
309 }
310 }
311 };
312
313 }
314};