| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 | /** * @fileoverview Rule to flag use of implied eval via setTimeout and setInterval * @author James Allardice */"use strict";//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------module.exports = {    meta: {        docs: {            description: "disallow the use of `eval()`-like methods",            category: "Best Practices",            recommended: false,            url: "https://eslint.org/docs/rules/no-implied-eval"        },        schema: []    },    create(context) {        const CALLEE_RE = /^(setTimeout|setInterval|execScript)$/;        /*         * Figures out if we should inspect a given binary expression. Is a stack         * of stacks, where the first element in each substack is a CallExpression.         */        const impliedEvalAncestorsStack = [];        //--------------------------------------------------------------------------        // Helpers        //--------------------------------------------------------------------------        /**         * Get the last element of an array, without modifying arr, like pop(), but non-destructive.         * @param {array} arr What to inspect         * @returns {*} The last element of arr         * @private         */        function last(arr) {            return arr ? arr[arr.length - 1] : null;        }        /**         * Checks if the given MemberExpression node is a potentially implied eval identifier on window.         * @param {ASTNode} node The MemberExpression node to check.         * @returns {boolean} Whether or not the given node is potentially an implied eval.         * @private         */        function isImpliedEvalMemberExpression(node) {            const object = node.object,                property = node.property,                hasImpliedEvalName = CALLEE_RE.test(property.name) || CALLEE_RE.test(property.value);            return object.name === "window" && hasImpliedEvalName;        }        /**         * Determines if a node represents a call to a potentially implied eval.         *         * This checks the callee name and that there's an argument, but not the type of the argument.         *         * @param {ASTNode} node The CallExpression to check.         * @returns {boolean} True if the node matches, false if not.         * @private         */        function isImpliedEvalCallExpression(node) {            const isMemberExpression = (node.callee.type === "MemberExpression"),                isIdentifier = (node.callee.type === "Identifier"),                isImpliedEvalCallee =                    (isIdentifier && CALLEE_RE.test(node.callee.name)) ||                    (isMemberExpression && isImpliedEvalMemberExpression(node.callee));            return isImpliedEvalCallee && node.arguments.length;        }        /**         * Checks that the parent is a direct descendent of an potential implied eval CallExpression, and if the parent is a CallExpression, that we're the first argument.         * @param {ASTNode} node The node to inspect the parent of.         * @returns {boolean} Was the parent a direct descendent, and is the child therefore potentially part of a dangerous argument?         * @private         */        function hasImpliedEvalParent(node) {            // make sure our parent is marked            return node.parent === last(last(impliedEvalAncestorsStack)) &&                // if our parent is a CallExpression, make sure we're the first argument                (node.parent.type !== "CallExpression" || node === node.parent.arguments[0]);        }        /**         * Checks if our parent is marked as part of an implied eval argument. If         * so, collapses the top of impliedEvalAncestorsStack and reports on the         * original CallExpression.         * @param {ASTNode} node The CallExpression to check.         * @returns {boolean} True if the node matches, false if not.         * @private         */        function checkString(node) {            if (hasImpliedEvalParent(node)) {                // remove the entire substack, to avoid duplicate reports                const substack = impliedEvalAncestorsStack.pop();                context.report({ node: substack[0], message: "Implied eval. Consider passing a function instead of a string." });            }        }        //--------------------------------------------------------------------------        // Public        //--------------------------------------------------------------------------        return {            CallExpression(node) {                if (isImpliedEvalCallExpression(node)) {                    // call expressions create a new substack                    impliedEvalAncestorsStack.push([node]);                }            },            "CallExpression:exit"(node) {                if (node === last(last(impliedEvalAncestorsStack))) {                    /*                     * Destroys the entire sub-stack, rather than just using                     * last(impliedEvalAncestorsStack).pop(), as a CallExpression is                     * always the bottom of a impliedEvalAncestorsStack substack.                     */                    impliedEvalAncestorsStack.pop();                }            },            BinaryExpression(node) {                if (node.operator === "+" && hasImpliedEvalParent(node)) {                    last(impliedEvalAncestorsStack).push(node);                }            },            "BinaryExpression:exit"(node) {                if (node === last(last(impliedEvalAncestorsStack))) {                    last(impliedEvalAncestorsStack).pop();                }            },            Literal(node) {                if (typeof node.value === "string") {                    checkString(node);                }            },            TemplateLiteral(node) {                checkString(node);            }        };    }};
 |