no-constant-condition.js 4.4 KB

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