You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							306 lines
						
					
					
						
							9.2 KiB
						
					
					
				
			
		
		
	
	
							306 lines
						
					
					
						
							9.2 KiB
						
					
					
				/** | 
						|
 * @fileoverview Rule to flag use of eval() statement | 
						|
 * @author Nicholas C. Zakas | 
						|
 */ | 
						|
 | 
						|
"use strict"; | 
						|
 | 
						|
//------------------------------------------------------------------------------ | 
						|
// Requirements | 
						|
//------------------------------------------------------------------------------ | 
						|
 | 
						|
const astUtils = require("./utils/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: { | 
						|
        type: "suggestion", | 
						|
 | 
						|
        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", default: false } | 
						|
                }, | 
						|
                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); | 
						|
                } | 
						|
            } | 
						|
        }; | 
						|
 | 
						|
    } | 
						|
};
 | 
						|
 |