no-implied-eval.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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-community/eslint-utils");
  11. //------------------------------------------------------------------------------
  12. // Rule Definition
  13. //------------------------------------------------------------------------------
  14. /** @type {import('../types').Rule.RuleModule} */
  15. module.exports = {
  16. meta: {
  17. type: "suggestion",
  18. docs: {
  19. description: "Disallow the use of `eval()`-like methods",
  20. recommended: false,
  21. url: "https://eslint.org/docs/latest/rules/no-implied-eval",
  22. },
  23. schema: [],
  24. messages: {
  25. impliedEval:
  26. "Implied eval. Consider passing a function instead of a string.",
  27. execScript: "Implied eval. Do not use execScript().",
  28. },
  29. },
  30. create(context) {
  31. const GLOBAL_CANDIDATES = Object.freeze([
  32. "global",
  33. "window",
  34. "globalThis",
  35. ]);
  36. const EVAL_LIKE_FUNC_PATTERN =
  37. /^(?:set(?:Interval|Timeout)|execScript)$/u;
  38. const sourceCode = context.sourceCode;
  39. /**
  40. * Checks whether a node is evaluated as a string or not.
  41. * @param {ASTNode} node A node to check.
  42. * @returns {boolean} True if the node is evaluated as a string.
  43. */
  44. function isEvaluatedString(node) {
  45. if (
  46. (node.type === "Literal" && typeof node.value === "string") ||
  47. node.type === "TemplateLiteral"
  48. ) {
  49. return true;
  50. }
  51. if (node.type === "BinaryExpression" && node.operator === "+") {
  52. return (
  53. isEvaluatedString(node.left) ||
  54. isEvaluatedString(node.right)
  55. );
  56. }
  57. return false;
  58. }
  59. /**
  60. * Reports if the `CallExpression` node has evaluated argument.
  61. * @param {ASTNode} node A CallExpression to check.
  62. * @returns {void}
  63. */
  64. function reportImpliedEvalCallExpression(node) {
  65. const [firstArgument] = node.arguments;
  66. if (firstArgument) {
  67. const staticValue = getStaticValue(
  68. firstArgument,
  69. sourceCode.getScope(node),
  70. );
  71. const isStaticString =
  72. staticValue && typeof staticValue.value === "string";
  73. const isString =
  74. isStaticString || isEvaluatedString(firstArgument);
  75. if (isString) {
  76. const calleeName =
  77. node.callee.type === "Identifier"
  78. ? node.callee.name
  79. : astUtils.getStaticPropertyName(node.callee);
  80. const isExecScript = calleeName === "execScript";
  81. context.report({
  82. node,
  83. messageId: isExecScript ? "execScript" : "impliedEval",
  84. });
  85. }
  86. }
  87. }
  88. /**
  89. * Reports calls of `implied eval` via the global references.
  90. * @param {Variable} globalVar A global variable to check.
  91. * @returns {void}
  92. */
  93. function reportImpliedEvalViaGlobal(globalVar) {
  94. const { references, name } = globalVar;
  95. references.forEach(ref => {
  96. const identifier = ref.identifier;
  97. let node = identifier.parent;
  98. while (astUtils.isSpecificMemberAccess(node, null, name)) {
  99. node = node.parent;
  100. }
  101. if (
  102. astUtils.isSpecificMemberAccess(
  103. node,
  104. null,
  105. EVAL_LIKE_FUNC_PATTERN,
  106. )
  107. ) {
  108. const calleeNode =
  109. node.parent.type === "ChainExpression"
  110. ? node.parent
  111. : node;
  112. const parent = calleeNode.parent;
  113. if (
  114. parent.type === "CallExpression" &&
  115. parent.callee === calleeNode
  116. ) {
  117. reportImpliedEvalCallExpression(parent);
  118. }
  119. }
  120. });
  121. }
  122. //--------------------------------------------------------------------------
  123. // Public
  124. //--------------------------------------------------------------------------
  125. return {
  126. CallExpression(node) {
  127. if (
  128. astUtils.isSpecificId(
  129. node.callee,
  130. EVAL_LIKE_FUNC_PATTERN,
  131. ) &&
  132. sourceCode.isGlobalReference(node.callee)
  133. ) {
  134. reportImpliedEvalCallExpression(node);
  135. }
  136. },
  137. "Program:exit"(node) {
  138. const globalScope = sourceCode.getScope(node);
  139. GLOBAL_CANDIDATES.map(candidate =>
  140. astUtils.getVariableByName(globalScope, candidate),
  141. )
  142. .filter(
  143. globalVar => !!globalVar && globalVar.defs.length === 0,
  144. )
  145. .forEach(reportImpliedEvalViaGlobal);
  146. },
  147. };
  148. },
  149. };