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