]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-eval.js
first commit
[pve-eslint.git] / eslint / lib / rules / no-eval.js
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
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 const candidatesOfGlobalObject = Object.freeze([
19 "global",
20 "window",
21 "globalThis"
22 ]);
23
24 /**
25 * Checks a given node is a Identifier node of the specified name.
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 */
30 function 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 * @param {ASTNode} node A node to check.
37 * @param {string} name A name to check.
38 * @returns {boolean} `true` if the node is a Literal node of the name.
39 */
40 function isConstant(node, name) {
41 switch (node.type) {
42 case "Literal":
43 return node.value === name;
44
45 case "TemplateLiteral":
46 return (
47 node.expressions.length === 0 &&
48 node.quasis[0].value.cooked === name
49 );
50
51 default:
52 return false;
53 }
54 }
55
56 /**
57 * Checks a given node is a MemberExpression node which has the specified name's
58 * property.
59 * @param {ASTNode} node A node to check.
60 * @param {string} name A name to check.
61 * @returns {boolean} `true` if the node is a MemberExpression node which has
62 * the specified name's property
63 */
64 function isMember(node, name) {
65 return (
66 node.type === "MemberExpression" &&
67 (node.computed ? isConstant : isIdentifier)(node.property, name)
68 );
69 }
70
71 //------------------------------------------------------------------------------
72 // Rule Definition
73 //------------------------------------------------------------------------------
74
75 module.exports = {
76 meta: {
77 type: "suggestion",
78
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", default: false }
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 * @param {ASTNode} node A node of the scope. This is one of Program,
115 * FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression.
116 * @returns {void}
117 */
118 function enterVarScope(node) {
119 const strict = context.getScope().isStrict;
120
121 funcInfo = {
122 upper: funcInfo,
123 node,
124 strict,
125 defaultThis: false,
126 initialized: strict
127 };
128 }
129
130 /**
131 * Pops a variable scope from the stack.
132 * @returns {void}
133 */
134 function exitVarScope() {
135 funcInfo = funcInfo.upper;
136 }
137
138 /**
139 * Reports a given node.
140 *
141 * `node` is `Identifier` or `MemberExpression`.
142 * The parent of `node` might be `CallExpression`.
143 *
144 * The location of the report is always `eval` `Identifier` (or possibly
145 * `Literal`). The type of the report is `CallExpression` if the parent is
146 * `CallExpression`. Otherwise, it's the given node type.
147 * @param {ASTNode} node A node to report.
148 * @returns {void}
149 */
150 function report(node) {
151 const parent = node.parent;
152 const locationNode = node.type === "MemberExpression"
153 ? node.property
154 : node;
155
156 const reportNode = parent.type === "CallExpression" && parent.callee === node
157 ? parent
158 : node;
159
160 context.report({
161 node: reportNode,
162 loc: locationNode.loc,
163 messageId: "unexpected"
164 });
165 }
166
167 /**
168 * Reports accesses of `eval` via the global object.
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 * @param {eslint-scope.Scope} globalScope The global scope.
203 * @returns {void}
204 */
205 function reportAccessingEval(globalScope) {
206 const variable = astUtils.getVariableByName(globalScope, "eval");
207
208 if (!variable) {
209 return;
210 }
211
212 const references = variable.references;
213
214 for (let i = 0; i < references.length; ++i) {
215 const reference = references[i];
216 const id = reference.identifier;
217
218 if (id.name === "eval" && !astUtils.isCallee(id)) {
219
220 // Is accessing to eval (excludes direct calls to eval)
221 report(id);
222 }
223 }
224 }
225
226 if (allowIndirect) {
227
228 // Checks only direct calls to eval. It's simple!
229 return {
230 "CallExpression:exit"(node) {
231 const callee = node.callee;
232
233 if (isIdentifier(callee, "eval")) {
234 report(callee);
235 }
236 }
237 };
238 }
239
240 return {
241 "CallExpression:exit"(node) {
242 const callee = node.callee;
243
244 if (isIdentifier(callee, "eval")) {
245 report(callee);
246 }
247 },
248
249 Program(node) {
250 const scope = context.getScope(),
251 features = context.parserOptions.ecmaFeatures || {},
252 strict =
253 scope.isStrict ||
254 node.sourceType === "module" ||
255 (features.globalReturn && scope.childScopes[0].isStrict);
256
257 funcInfo = {
258 upper: null,
259 node,
260 strict,
261 defaultThis: true,
262 initialized: true
263 };
264 },
265
266 "Program:exit"() {
267 const globalScope = context.getScope();
268
269 exitVarScope();
270 reportAccessingEval(globalScope);
271 reportAccessingEvalViaGlobalObject(globalScope);
272 },
273
274 FunctionDeclaration: enterVarScope,
275 "FunctionDeclaration:exit": exitVarScope,
276 FunctionExpression: enterVarScope,
277 "FunctionExpression:exit": exitVarScope,
278 ArrowFunctionExpression: enterVarScope,
279 "ArrowFunctionExpression:exit": exitVarScope,
280
281 ThisExpression(node) {
282 if (!isMember(node.parent, "eval")) {
283 return;
284 }
285
286 /*
287 * `this.eval` is found.
288 * Checks whether or not the value of `this` is the global object.
289 */
290 if (!funcInfo.initialized) {
291 funcInfo.initialized = true;
292 funcInfo.defaultThis = astUtils.isDefaultThisBinding(
293 funcInfo.node,
294 sourceCode
295 );
296 }
297
298 if (!funcInfo.strict && funcInfo.defaultThis) {
299
300 // `this.eval` is possible built-in `eval`.
301 report(node.parent);
302 }
303 }
304 };
305
306 }
307 };