no-implied-eval.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. /**
  2. * @fileoverview Rule to flag use of implied eval via setTimeout and setInterval
  3. * @author James Allardice
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. const { getStaticValue } = require("eslint-utils");
  11. //------------------------------------------------------------------------------
  12. // Rule Definition
  13. //------------------------------------------------------------------------------
  14. module.exports = {
  15. meta: {
  16. type: "suggestion",
  17. docs: {
  18. description: "disallow the use of `eval()`-like methods",
  19. category: "Best Practices",
  20. recommended: false,
  21. url: "https://eslint.org/docs/rules/no-implied-eval"
  22. },
  23. schema: [],
  24. messages: {
  25. impliedEval: "Implied eval. Consider passing a function instead of a string."
  26. }
  27. },
  28. create(context) {
  29. const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]);
  30. const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u;
  31. /**
  32. * Checks whether a node is evaluated as a string or not.
  33. * @param {ASTNode} node A node to check.
  34. * @returns {boolean} True if the node is evaluated as a string.
  35. */
  36. function isEvaluatedString(node) {
  37. if (
  38. (node.type === "Literal" && typeof node.value === "string") ||
  39. node.type === "TemplateLiteral"
  40. ) {
  41. return true;
  42. }
  43. if (node.type === "BinaryExpression" && node.operator === "+") {
  44. return isEvaluatedString(node.left) || isEvaluatedString(node.right);
  45. }
  46. return false;
  47. }
  48. /**
  49. * Reports if the `CallExpression` node has evaluated argument.
  50. * @param {ASTNode} node A CallExpression to check.
  51. * @returns {void}
  52. */
  53. function reportImpliedEvalCallExpression(node) {
  54. const [firstArgument] = node.arguments;
  55. if (firstArgument) {
  56. const staticValue = getStaticValue(firstArgument, context.getScope());
  57. const isStaticString = staticValue && typeof staticValue.value === "string";
  58. const isString = isStaticString || isEvaluatedString(firstArgument);
  59. if (isString) {
  60. context.report({
  61. node,
  62. messageId: "impliedEval"
  63. });
  64. }
  65. }
  66. }
  67. /**
  68. * Reports calls of `implied eval` via the global references.
  69. * @param {Variable} globalVar A global variable to check.
  70. * @returns {void}
  71. */
  72. function reportImpliedEvalViaGlobal(globalVar) {
  73. const { references, name } = globalVar;
  74. references.forEach(ref => {
  75. const identifier = ref.identifier;
  76. let node = identifier.parent;
  77. while (astUtils.isSpecificMemberAccess(node, null, name)) {
  78. node = node.parent;
  79. }
  80. if (astUtils.isSpecificMemberAccess(node, null, EVAL_LIKE_FUNC_PATTERN)) {
  81. const calleeNode = node.parent.type === "ChainExpression" ? node.parent : node;
  82. const parent = calleeNode.parent;
  83. if (parent.type === "CallExpression" && parent.callee === calleeNode) {
  84. reportImpliedEvalCallExpression(parent);
  85. }
  86. }
  87. });
  88. }
  89. //--------------------------------------------------------------------------
  90. // Public
  91. //--------------------------------------------------------------------------
  92. return {
  93. CallExpression(node) {
  94. if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) {
  95. reportImpliedEvalCallExpression(node);
  96. }
  97. },
  98. "Program:exit"() {
  99. const globalScope = context.getScope();
  100. GLOBAL_CANDIDATES
  101. .map(candidate => astUtils.getVariableByName(globalScope, candidate))
  102. .filter(globalVar => !!globalVar && globalVar.defs.length === 0)
  103. .forEach(reportImpliedEvalViaGlobal);
  104. }
  105. };
  106. }
  107. };