UNPKG

9.36 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("../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 docs: {
80 description: "disallow the use of `eval()`",
81 category: "Best Practices",
82 recommended: false
83 },
84
85 schema: [
86 {
87 type: "object",
88 properties: {
89 allowIndirect: { type: "boolean" }
90 },
91 additionalProperties: false
92 }
93 ]
94 },
95
96 create(context) {
97 const allowIndirect = Boolean(
98 context.options[0] &&
99 context.options[0].allowIndirect
100 );
101 const sourceCode = context.getSourceCode();
102 let funcInfo = null;
103
104 /**
105 * Pushs a variable scope (Program or Function) information to the stack.
106 *
107 * This is used in order to check whether or not `this` binding is a
108 * reference to the global object.
109 *
110 * @param {ASTNode} node - A node of the scope. This is one of Program,
111 * FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression.
112 * @returns {void}
113 */
114 function enterVarScope(node) {
115 const strict = context.getScope().isStrict;
116
117 funcInfo = {
118 upper: funcInfo,
119 node,
120 strict,
121 defaultThis: false,
122 initialized: strict
123 };
124 }
125
126 /**
127 * Pops a variable scope from the stack.
128 *
129 * @returns {void}
130 */
131 function exitVarScope() {
132 funcInfo = funcInfo.upper;
133 }
134
135 /**
136 * Reports a given node.
137 *
138 * `node` is `Identifier` or `MemberExpression`.
139 * The parent of `node` might be `CallExpression`.
140 *
141 * The location of the report is always `eval` `Identifier` (or possibly
142 * `Literal`). The type of the report is `CallExpression` if the parent is
143 * `CallExpression`. Otherwise, it's the given node type.
144 *
145 * @param {ASTNode} node - A node to report.
146 * @returns {void}
147 */
148 function report(node) {
149 let locationNode = node;
150 const parent = node.parent;
151
152 if (node.type === "MemberExpression") {
153 locationNode = node.property;
154 }
155 if (parent.type === "CallExpression" && parent.callee === node) {
156 node = parent;
157 }
158
159 context.report({
160 node,
161 loc: locationNode.loc.start,
162 message: "eval can be harmful."
163 });
164 }
165
166 /**
167 * Reports accesses of `eval` via the global object.
168 *
169 * @param {eslint-scope.Scope} globalScope - The global scope.
170 * @returns {void}
171 */
172 function reportAccessingEvalViaGlobalObject(globalScope) {
173 for (let i = 0; i < candidatesOfGlobalObject.length; ++i) {
174 const name = candidatesOfGlobalObject[i];
175 const variable = astUtils.getVariableByName(globalScope, name);
176
177 if (!variable) {
178 continue;
179 }
180
181 const references = variable.references;
182
183 for (let j = 0; j < references.length; ++j) {
184 const identifier = references[j].identifier;
185 let node = identifier.parent;
186
187 // To detect code like `window.window.eval`.
188 while (isMember(node, name)) {
189 node = node.parent;
190 }
191
192 // Reports.
193 if (isMember(node, "eval")) {
194 report(node);
195 }
196 }
197 }
198 }
199
200 /**
201 * Reports all accesses of `eval` (excludes direct calls to eval).
202 *
203 * @param {eslint-scope.Scope} globalScope - The global scope.
204 * @returns {void}
205 */
206 function reportAccessingEval(globalScope) {
207 const variable = astUtils.getVariableByName(globalScope, "eval");
208
209 if (!variable) {
210 return;
211 }
212
213 const references = variable.references;
214
215 for (let i = 0; i < references.length; ++i) {
216 const reference = references[i];
217 const id = reference.identifier;
218
219 if (id.name === "eval" && !astUtils.isCallee(id)) {
220
221 // Is accessing to eval (excludes direct calls to eval)
222 report(id);
223 }
224 }
225 }
226
227 if (allowIndirect) {
228
229 // Checks only direct calls to eval. It's simple!
230 return {
231 "CallExpression:exit"(node) {
232 const callee = node.callee;
233
234 if (isIdentifier(callee, "eval")) {
235 report(callee);
236 }
237 }
238 };
239 }
240
241 return {
242 "CallExpression:exit"(node) {
243 const callee = node.callee;
244
245 if (isIdentifier(callee, "eval")) {
246 report(callee);
247 }
248 },
249
250 Program(node) {
251 const scope = context.getScope(),
252 features = context.parserOptions.ecmaFeatures || {},
253 strict =
254 scope.isStrict ||
255 node.sourceType === "module" ||
256 (features.globalReturn && scope.childScopes[0].isStrict);
257
258 funcInfo = {
259 upper: null,
260 node,
261 strict,
262 defaultThis: true,
263 initialized: true
264 };
265 },
266
267 "Program:exit"() {
268 const globalScope = context.getScope();
269
270 exitVarScope();
271 reportAccessingEval(globalScope);
272 reportAccessingEvalViaGlobalObject(globalScope);
273 },
274
275 FunctionDeclaration: enterVarScope,
276 "FunctionDeclaration:exit": exitVarScope,
277 FunctionExpression: enterVarScope,
278 "FunctionExpression:exit": exitVarScope,
279 ArrowFunctionExpression: enterVarScope,
280 "ArrowFunctionExpression:exit": exitVarScope,
281
282 ThisExpression(node) {
283 if (!isMember(node.parent, "eval")) {
284 return;
285 }
286
287 /*
288 * `this.eval` is found.
289 * Checks whether or not the value of `this` is the global object.
290 */
291 if (!funcInfo.initialized) {
292 funcInfo.initialized = true;
293 funcInfo.defaultThis = astUtils.isDefaultThisBinding(
294 funcInfo.node,
295 sourceCode
296 );
297 }
298
299 if (!funcInfo.strict && funcInfo.defaultThis) {
300
301 // `this.eval` is possible built-in `eval`.
302 report(node.parent);
303 }
304 }
305 };
306
307 }
308};