UNPKG

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