| /** | |
| * @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); | |
| } | |
| } | |
| }; | |
| } | |
| }; |