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