1/** 2 * @fileoverview Rule to flag use of implied eval via setTimeout and setInterval 3 * @author James Allardice 4 */ 5 6"use strict"; 7 8//------------------------------------------------------------------------------ 9// Requirements 10//------------------------------------------------------------------------------ 11 12const astUtils = require("./utils/ast-utils"); 13const { getStaticValue } = require("eslint-utils"); 14 15//------------------------------------------------------------------------------ 16// Rule Definition 17//------------------------------------------------------------------------------ 18 19module.exports = { 20 meta: { 21 type: "suggestion", 22 23 docs: { 24 description: "disallow the use of `eval()`-like methods", 25 category: "Best Practices", 26 recommended: false, 27 url: "https://eslint.org/docs/rules/no-implied-eval" 28 }, 29 30 schema: [], 31 32 messages: { 33 impliedEval: "Implied eval. Consider passing a function instead of a string." 34 } 35 }, 36 37 create(context) { 38 const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]); 39 const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u; 40 41 /** 42 * Checks whether a node is evaluated as a string or not. 43 * @param {ASTNode} node A node to check. 44 * @returns {boolean} True if the node is evaluated as a string. 45 */ 46 function isEvaluatedString(node) { 47 if ( 48 (node.type === "Literal" && typeof node.value === "string") || 49 node.type === "TemplateLiteral" 50 ) { 51 return true; 52 } 53 if (node.type === "BinaryExpression" && node.operator === "+") { 54 return isEvaluatedString(node.left) || isEvaluatedString(node.right); 55 } 56 return false; 57 } 58 59 /** 60 * Reports if the `CallExpression` node has evaluated argument. 61 * @param {ASTNode} node A CallExpression to check. 62 * @returns {void} 63 */ 64 function reportImpliedEvalCallExpression(node) { 65 const [firstArgument] = node.arguments; 66 67 if (firstArgument) { 68 69 const staticValue = getStaticValue(firstArgument, context.getScope()); 70 const isStaticString = staticValue && typeof staticValue.value === "string"; 71 const isString = isStaticString || isEvaluatedString(firstArgument); 72 73 if (isString) { 74 context.report({ 75 node, 76 messageId: "impliedEval" 77 }); 78 } 79 } 80 81 } 82 83 /** 84 * Reports calls of `implied eval` via the global references. 85 * @param {Variable} globalVar A global variable to check. 86 * @returns {void} 87 */ 88 function reportImpliedEvalViaGlobal(globalVar) { 89 const { references, name } = globalVar; 90 91 references.forEach(ref => { 92 const identifier = ref.identifier; 93 let node = identifier.parent; 94 95 while (astUtils.isSpecificMemberAccess(node, null, name)) { 96 node = node.parent; 97 } 98 99 if (astUtils.isSpecificMemberAccess(node, null, EVAL_LIKE_FUNC_PATTERN)) { 100 const calleeNode = node.parent.type === "ChainExpression" ? node.parent : node; 101 const parent = calleeNode.parent; 102 103 if (parent.type === "CallExpression" && parent.callee === calleeNode) { 104 reportImpliedEvalCallExpression(parent); 105 } 106 } 107 }); 108 } 109 110 //-------------------------------------------------------------------------- 111 // Public 112 //-------------------------------------------------------------------------- 113 114 return { 115 CallExpression(node) { 116 if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) { 117 reportImpliedEvalCallExpression(node); 118 } 119 }, 120 "Program:exit"() { 121 const globalScope = context.getScope(); 122 123 GLOBAL_CANDIDATES 124 .map(candidate => astUtils.getVariableByName(globalScope, candidate)) 125 .filter(globalVar => !!globalVar && globalVar.defs.length === 0) 126 .forEach(reportImpliedEvalViaGlobal); 127 } 128 }; 129 130 } 131}; 132