no-constant-condition.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. /**
  2. * @fileoverview Rule to flag use constant conditions
  3. * @author Christian Schulz <http://rndm.de>
  4. */
  5. "use strict";
  6. const { isConstant } = require("./utils/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Helpers
  9. //------------------------------------------------------------------------------
  10. //------------------------------------------------------------------------------
  11. // Rule Definition
  12. //------------------------------------------------------------------------------
  13. /** @type {import('../shared/types').Rule} */
  14. module.exports = {
  15. meta: {
  16. type: "problem",
  17. docs: {
  18. description: "Disallow constant expressions in conditions",
  19. recommended: true,
  20. url: "https://eslint.org/docs/latest/rules/no-constant-condition"
  21. },
  22. schema: [
  23. {
  24. type: "object",
  25. properties: {
  26. checkLoops: {
  27. enum: ["all", "allExceptWhileTrue", "none", true, false]
  28. }
  29. },
  30. additionalProperties: false
  31. }
  32. ],
  33. messages: {
  34. unexpected: "Unexpected constant condition."
  35. }
  36. },
  37. create(context) {
  38. const options = context.options[0] || {};
  39. let checkLoops = options.checkLoops ?? "allExceptWhileTrue";
  40. const loopSetStack = [];
  41. const sourceCode = context.sourceCode;
  42. if (options.checkLoops === true) {
  43. checkLoops = "all";
  44. } else if (options.checkLoops === false) {
  45. checkLoops = "none";
  46. }
  47. let loopsInCurrentScope = new Set();
  48. //--------------------------------------------------------------------------
  49. // Helpers
  50. //--------------------------------------------------------------------------
  51. /**
  52. * Tracks when the given node contains a constant condition.
  53. * @param {ASTNode} node The AST node to check.
  54. * @returns {void}
  55. * @private
  56. */
  57. function trackConstantConditionLoop(node) {
  58. if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) {
  59. loopsInCurrentScope.add(node);
  60. }
  61. }
  62. /**
  63. * Reports when the set contains the given constant condition node
  64. * @param {ASTNode} node The AST node to check.
  65. * @returns {void}
  66. * @private
  67. */
  68. function checkConstantConditionLoopInSet(node) {
  69. if (loopsInCurrentScope.has(node)) {
  70. loopsInCurrentScope.delete(node);
  71. context.report({ node: node.test, messageId: "unexpected" });
  72. }
  73. }
  74. /**
  75. * Reports when the given node contains a constant condition.
  76. * @param {ASTNode} node The AST node to check.
  77. * @returns {void}
  78. * @private
  79. */
  80. function reportIfConstant(node) {
  81. if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) {
  82. context.report({ node: node.test, messageId: "unexpected" });
  83. }
  84. }
  85. /**
  86. * Stores current set of constant loops in loopSetStack temporarily
  87. * and uses a new set to track constant loops
  88. * @returns {void}
  89. * @private
  90. */
  91. function enterFunction() {
  92. loopSetStack.push(loopsInCurrentScope);
  93. loopsInCurrentScope = new Set();
  94. }
  95. /**
  96. * Reports when the set still contains stored constant conditions
  97. * @returns {void}
  98. * @private
  99. */
  100. function exitFunction() {
  101. loopsInCurrentScope = loopSetStack.pop();
  102. }
  103. /**
  104. * Checks node when checkLoops option is enabled
  105. * @param {ASTNode} node The AST node to check.
  106. * @returns {void}
  107. * @private
  108. */
  109. function checkLoop(node) {
  110. if (checkLoops === "all" || checkLoops === "allExceptWhileTrue") {
  111. trackConstantConditionLoop(node);
  112. }
  113. }
  114. //--------------------------------------------------------------------------
  115. // Public
  116. //--------------------------------------------------------------------------
  117. return {
  118. ConditionalExpression: reportIfConstant,
  119. IfStatement: reportIfConstant,
  120. WhileStatement(node) {
  121. if (node.test.type === "Literal" && node.test.value === true && checkLoops === "allExceptWhileTrue") {
  122. return;
  123. }
  124. checkLoop(node);
  125. },
  126. "WhileStatement:exit": checkConstantConditionLoopInSet,
  127. DoWhileStatement: checkLoop,
  128. "DoWhileStatement:exit": checkConstantConditionLoopInSet,
  129. ForStatement: checkLoop,
  130. "ForStatement > .test": node => checkLoop(node.parent),
  131. "ForStatement:exit": checkConstantConditionLoopInSet,
  132. FunctionDeclaration: enterFunction,
  133. "FunctionDeclaration:exit": exitFunction,
  134. FunctionExpression: enterFunction,
  135. "FunctionExpression:exit": exitFunction,
  136. YieldExpression: () => loopsInCurrentScope.clear()
  137. };
  138. }
  139. };