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 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) { 33 return astUtils.isSpecificMemberAccess(node, null, name); 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 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")) { 204 report(callee); 205 } 206 } 207 }; 208 } 209 210 return { 211 "CallExpression:exit"(node) { 212 const callee = node.callee; 213 214 if (astUtils.isSpecificId(callee, "eval")) { 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}; 278