| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312 | /** * @fileoverview Rule to flag use of eval() statement * @author Nicholas C. Zakas */"use strict";//------------------------------------------------------------------------------// Requirements//------------------------------------------------------------------------------const astUtils = require("../ast-utils");//------------------------------------------------------------------------------// Helpers//------------------------------------------------------------------------------const candidatesOfGlobalObject = Object.freeze([    "global",    "window"]);/** * Checks a given node is a Identifier node of the specified name. * * @param {ASTNode} node - A node to check. * @param {string} name - A name to check. * @returns {boolean} `true` if the node is a Identifier node of the name. */function isIdentifier(node, name) {    return node.type === "Identifier" && node.name === name;}/** * Checks a given node is a Literal node of the specified string value. * * @param {ASTNode} node - A node to check. * @param {string} name - A name to check. * @returns {boolean} `true` if the node is a Literal node of the name. */function isConstant(node, name) {    switch (node.type) {        case "Literal":            return node.value === name;        case "TemplateLiteral":            return (                node.expressions.length === 0 &&                node.quasis[0].value.cooked === name            );        default:            return false;    }}/** * Checks a given node is a MemberExpression node which has the specified name's * property. * * @param {ASTNode} node - A node to check. * @param {string} name - A name to check. * @returns {boolean} `true` if the node is a MemberExpression node which has *      the specified name's property */function isMember(node, name) {    return (        node.type === "MemberExpression" &&        (node.computed ? isConstant : isIdentifier)(node.property, name)    );}//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------module.exports = {    meta: {        docs: {            description: "disallow the use of `eval()`",            category: "Best Practices",            recommended: false,            url: "https://eslint.org/docs/rules/no-eval"        },        schema: [            {                type: "object",                properties: {                    allowIndirect: { type: "boolean" }                },                additionalProperties: false            }        ],        messages: {            unexpected: "eval can be harmful."        }    },    create(context) {        const allowIndirect = Boolean(            context.options[0] &&            context.options[0].allowIndirect        );        const sourceCode = context.getSourceCode();        let funcInfo = null;        /**         * Pushs a variable scope (Program or Function) information to the stack.         *         * This is used in order to check whether or not `this` binding is a         * reference to the global object.         *         * @param {ASTNode} node - A node of the scope. This is one of Program,         *      FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression.         * @returns {void}         */        function enterVarScope(node) {            const strict = context.getScope().isStrict;            funcInfo = {                upper: funcInfo,                node,                strict,                defaultThis: false,                initialized: strict            };        }        /**         * Pops a variable scope from the stack.         *         * @returns {void}         */        function exitVarScope() {            funcInfo = funcInfo.upper;        }        /**         * Reports a given node.         *         * `node` is `Identifier` or `MemberExpression`.         * The parent of `node` might be `CallExpression`.         *         * The location of the report is always `eval` `Identifier` (or possibly         * `Literal`). The type of the report is `CallExpression` if the parent is         * `CallExpression`. Otherwise, it's the given node type.         *         * @param {ASTNode} node - A node to report.         * @returns {void}         */        function report(node) {            const parent = node.parent;            const locationNode = node.type === "MemberExpression"                ? node.property                : node;            const reportNode = parent.type === "CallExpression" && parent.callee === node                ? parent                : node;            context.report({                node: reportNode,                loc: locationNode.loc.start,                messageId: "unexpected"            });        }        /**         * Reports accesses of `eval` via the global object.         *         * @param {eslint-scope.Scope} globalScope - The global scope.         * @returns {void}         */        function reportAccessingEvalViaGlobalObject(globalScope) {            for (let i = 0; i < candidatesOfGlobalObject.length; ++i) {                const name = candidatesOfGlobalObject[i];                const variable = astUtils.getVariableByName(globalScope, name);                if (!variable) {                    continue;                }                const references = variable.references;                for (let j = 0; j < references.length; ++j) {                    const identifier = references[j].identifier;                    let node = identifier.parent;                    // To detect code like `window.window.eval`.                    while (isMember(node, name)) {                        node = node.parent;                    }                    // Reports.                    if (isMember(node, "eval")) {                        report(node);                    }                }            }        }        /**         * Reports all accesses of `eval` (excludes direct calls to eval).         *         * @param {eslint-scope.Scope} globalScope - The global scope.         * @returns {void}         */        function reportAccessingEval(globalScope) {            const variable = astUtils.getVariableByName(globalScope, "eval");            if (!variable) {                return;            }            const references = variable.references;            for (let i = 0; i < references.length; ++i) {                const reference = references[i];                const id = reference.identifier;                if (id.name === "eval" && !astUtils.isCallee(id)) {                    // Is accessing to eval (excludes direct calls to eval)                    report(id);                }            }        }        if (allowIndirect) {            // Checks only direct calls to eval. It's simple!            return {                "CallExpression:exit"(node) {                    const callee = node.callee;                    if (isIdentifier(callee, "eval")) {                        report(callee);                    }                }            };        }        return {            "CallExpression:exit"(node) {                const callee = node.callee;                if (isIdentifier(callee, "eval")) {                    report(callee);                }            },            Program(node) {                const scope = context.getScope(),                    features = context.parserOptions.ecmaFeatures || {},                    strict =                        scope.isStrict ||                        node.sourceType === "module" ||                        (features.globalReturn && scope.childScopes[0].isStrict);                funcInfo = {                    upper: null,                    node,                    strict,                    defaultThis: true,                    initialized: true                };            },            "Program:exit"() {                const globalScope = context.getScope();                exitVarScope();                reportAccessingEval(globalScope);                reportAccessingEvalViaGlobalObject(globalScope);            },            FunctionDeclaration: enterVarScope,            "FunctionDeclaration:exit": exitVarScope,            FunctionExpression: enterVarScope,            "FunctionExpression:exit": exitVarScope,            ArrowFunctionExpression: enterVarScope,            "ArrowFunctionExpression:exit": exitVarScope,            ThisExpression(node) {                if (!isMember(node.parent, "eval")) {                    return;                }                /*                 * `this.eval` is found.                 * Checks whether or not the value of `this` is the global object.                 */                if (!funcInfo.initialized) {                    funcInfo.initialized = true;                    funcInfo.defaultThis = astUtils.isDefaultThisBinding(                        funcInfo.node,                        sourceCode                    );                }                if (!funcInfo.strict && funcInfo.defaultThis) {                    // `this.eval` is possible built-in `eval`.                    report(node.parent);                }            }        };    }};
 |