JAVASCRIPT   52

no-eval.js

Guest on 21st August 2021 09:31:00 AM

  1. /**
  2.  * @fileoverview Rule to flag use of eval() statement
  3.  * @author Nicholas C. Zakas
  4.  */
  5.  
  6. "use strict";
  7.  
  8. //------------------------------------------------------------------------------
  9. // Requirements
  10. //------------------------------------------------------------------------------
  11.  
  12. const astUtils = require("../ast-utils");
  13.  
  14. //------------------------------------------------------------------------------
  15. // Helpers
  16. //------------------------------------------------------------------------------
  17.  
  18. const candidatesOfGlobalObject = Object.freeze([
  19.     "global",
  20.     "window"
  21. ]);
  22.  
  23. /**
  24.  * Checks a given node is a Identifier node of the specified name.
  25.  *
  26.  * @param {ASTNode} node - A node to check.
  27.  * @param {string} name - A name to check.
  28.  * @returns {boolean} `true` if the node is a Identifier node of the name.
  29.  */
  30. function isIdentifier(node, name) {
  31.     return node.type === "Identifier" && node.name === name;
  32. }
  33.  
  34. /**
  35.  * Checks a given node is a Literal node of the specified string value.
  36.  *
  37.  * @param {ASTNode} node - A node to check.
  38.  * @param {string} name - A name to check.
  39.  * @returns {boolean} `true` if the node is a Literal node of the name.
  40.  */
  41. function isConstant(node, name) {
  42.     switch (node.type) {
  43.         case "Literal":
  44.             return node.value === name;
  45.  
  46.         case "TemplateLiteral":
  47.             return (
  48.                 node.expressions.length === 0 &&
  49.                 node.quasis[0].value.cooked === name
  50.             );
  51.  
  52.         default:
  53.             return false;
  54.     }
  55. }
  56.  
  57. /**
  58.  * Checks a given node is a MemberExpression node which has the specified name's
  59.  * property.
  60.  *
  61.  * @param {ASTNode} node - A node to check.
  62.  * @param {string} name - A name to check.
  63.  * @returns {boolean} `true` if the node is a MemberExpression node which has
  64.  *      the specified name's property
  65.  */
  66. function isMember(node, name) {
  67.     return (
  68.         node.type === "MemberExpression" &&
  69.         (node.computed ? isConstant : isIdentifier)(node.property, name)
  70.     );
  71. }
  72.  
  73. //------------------------------------------------------------------------------
  74. // Rule Definition
  75. //------------------------------------------------------------------------------
  76.  
  77. module.exports = {
  78.     meta: {
  79.         docs: {
  80.             description: "disallow the use of `eval()`",
  81.             category: "Best Practices",
  82.             recommended: false
  83.         },
  84.  
  85.         schema: [
  86.             {
  87.                 type: "object",
  88.                 properties: {
  89.                     allowIndirect: { type: "boolean" }
  90.                 },
  91.                 additionalProperties: false
  92.             }
  93.         ]
  94.     },
  95.  
  96.     create(context) {
  97.         const allowIndirect = Boolean(
  98.             context.options[0] &&
  99.             context.options[0].allowIndirect
  100.         );
  101.         const sourceCode = context.getSourceCode();
  102.         let funcInfo = null;
  103.  
  104.         /**
  105.          * Pushs a variable scope (Program or Function) information to the stack.
  106.          *
  107.          * This is used in order to check whether or not `this` binding is a
  108.          * reference to the global object.
  109.          *
  110.          * @param {ASTNode} node - A node of the scope. This is one of Program,
  111.          *      FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression.
  112.          * @returns {void}
  113.          */
  114.         function enterVarScope(node) {
  115.             const strict = context.getScope().isStrict;
  116.  
  117.             funcInfo = {
  118.                 upper: funcInfo,
  119.                 node,
  120.                 strict,
  121.                 defaultThis: false,
  122.                 initialized: strict
  123.             };
  124.         }
  125.  
  126.         /**
  127.          * Pops a variable scope from the stack.
  128.          *
  129.          * @returns {void}
  130.          */
  131.         function exitVarScope() {
  132.             funcInfo = funcInfo.upper;
  133.         }
  134.  
  135.         /**
  136.          * Reports a given node.
  137.          *
  138.          * `node` is `Identifier` or `MemberExpression`.
  139.          * The parent of `node` might be `CallExpression`.
  140.          *
  141.          * The location of the report is always `eval` `Identifier` (or possibly
  142.          * `Literal`). The type of the report is `CallExpression` if the parent is
  143.          * `CallExpression`. Otherwise, it's the given node type.
  144.          *
  145.          * @param {ASTNode} node - A node to report.
  146.          * @returns {void}
  147.          */
  148.         function report(node) {
  149.             let locationNode = node;
  150.             const parent = node.parent;
  151.  
  152.             if (node.type === "MemberExpression") {
  153.                 locationNode = node.property;
  154.             }
  155.             if (parent.type === "CallExpression" && parent.callee === node) {
  156.                 node = parent;
  157.             }
  158.  
  159.             context.report({
  160.                 node,
  161.                 loc: locationNode.loc.start,
  162.                 message: "eval can be harmful."
  163.             });
  164.         }
  165.  
  166.         /**
  167.          * Reports accesses of `eval` via the global object.
  168.          *
  169.          * @param {escope.Scope} globalScope - The global scope.
  170.          * @returns {void}
  171.          */
  172.         function reportAccessingEvalViaGlobalObject(globalScope) {
  173.             for (let i = 0; i < candidatesOfGlobalObject.length; ++i) {
  174.                 const name = candidatesOfGlobalObject[i];
  175.                 const variable = astUtils.getVariableByName(globalScope, name);
  176.  
  177.                 if (!variable) {
  178.                     continue;
  179.                 }
  180.  
  181.                 const references = variable.references;
  182.  
  183.                 for (let j = 0; j < references.length; ++j) {
  184.                     const identifier = references[j].identifier;
  185.                     let node = identifier.parent;
  186.  
  187.                     // To detect code like `window.window.eval`.
  188.                     while (isMember(node, name)) {
  189.                         node = node.parent;
  190.                     }
  191.  
  192.                     // Reports.
  193.                     if (isMember(node, "eval")) {
  194.                         report(node);
  195.                     }
  196.                 }
  197.             }
  198.         }
  199.  
  200.         /**
  201.          * Reports all accesses of `eval` (excludes direct calls to eval).
  202.          *
  203.          * @param {escope.Scope} globalScope - The global scope.
  204.          * @returns {void}
  205.          */
  206.         function reportAccessingEval(globalScope) {
  207.             const variable = astUtils.getVariableByName(globalScope, "eval");
  208.  
  209.             if (!variable) {
  210.                 return;
  211.             }
  212.  
  213.             const references = variable.references;
  214.  
  215.             for (let i = 0; i < references.length; ++i) {
  216.                 const reference = references[i];
  217.                 const id = reference.identifier;
  218.  
  219.                 if (id.name === "eval" && !astUtils.isCallee(id)) {
  220.  
  221.                     // Is accessing to eval (excludes direct calls to eval)
  222.                     report(id);
  223.                 }
  224.             }
  225.         }
  226.  
  227.         if (allowIndirect) {
  228.  
  229.             // Checks only direct calls to eval. It's simple!
  230.             return {
  231.                 "CallExpression:exit"(node) {
  232.                     const callee = node.callee;
  233.  
  234.                     if (isIdentifier(callee, "eval")) {
  235.                         report(callee);
  236.                     }
  237.                 }
  238.             };
  239.         }
  240.  
  241.         return {
  242.             "CallExpression:exit"(node) {
  243.                 const callee = node.callee;
  244.  
  245.                 if (isIdentifier(callee, "eval")) {
  246.                     report(callee);
  247.                 }
  248.             },
  249.  
  250.             Program(node) {
  251.                 const scope = context.getScope(),
  252.                     features = context.parserOptions.ecmaFeatures || {},
  253.                     strict =
  254.                         scope.isStrict ||
  255.                         node.sourceType === "module" ||
  256.                         (features.globalReturn && scope.childScopes[0].isStrict);
  257.  
  258.                 funcInfo = {
  259.                     upper: null,
  260.                     node,
  261.                     strict,
  262.                     defaultThis: true,
  263.                     initialized: true
  264.                 };
  265.             },
  266.  
  267.             "Program:exit"() {
  268.                 const globalScope = context.getScope();
  269.  
  270.                 exitVarScope();
  271.                 reportAccessingEval(globalScope);
  272.                 reportAccessingEvalViaGlobalObject(globalScope);
  273.             },
  274.  
  275.             FunctionDeclaration: enterVarScope,
  276.             "FunctionDeclaration:exit": exitVarScope,
  277.             FunctionExpression: enterVarScope,
  278.             "FunctionExpression:exit": exitVarScope,
  279.             ArrowFunctionExpression: enterVarScope,
  280.             "ArrowFunctionExpression:exit": exitVarScope,
  281.  
  282.             ThisExpression(node) {
  283.                 if (!isMember(node.parent, "eval")) {
  284.                     return;
  285.                 }
  286.  
  287.                 /*
  288.                  * `this.eval` is found.
  289.                  * Checks whether or not the value of `this` is the global object.
  290.                  */
  291.                 if (!funcInfo.initialized) {
  292.                     funcInfo.initialized = true;
  293.                     funcInfo.defaultThis = astUtils.isDefaultThisBinding(
  294.                         funcInfo.node,
  295.                         sourceCode
  296.                     );
  297.                 }
  298.  
  299.                 if (!funcInfo.strict && funcInfo.defaultThis) {
  300.  
  301.                     // `this.eval` is possible built-in `eval`.
  302.                     report(node.parent);
  303.                 }
  304.             }
  305.         };
  306.  
  307.     }
  308. };

Raw Paste


Login or Register to edit or fork this paste. It's free.