]>
Commit | Line | Data |
---|---|---|
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 | ||
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 | ||
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 | */ | |
32 | function isMember(node, name) { | |
6f036462 | 33 | return astUtils.isSpecificMemberAccess(node, null, name); |
eb39fafa DC |
34 | } |
35 | ||
36 | //------------------------------------------------------------------------------ | |
37 | // Rule Definition | |
38 | //------------------------------------------------------------------------------ | |
39 | ||
40 | module.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 | }; |