no-lone-blocks.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /**
  2. * @fileoverview Rule to flag blocks with no reason to exist
  3. * @author Brandon Mills
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. /** @type {import('../types').Rule.RuleModule} */
  10. module.exports = {
  11. meta: {
  12. type: "suggestion",
  13. docs: {
  14. description: "Disallow unnecessary nested blocks",
  15. recommended: false,
  16. url: "https://eslint.org/docs/latest/rules/no-lone-blocks",
  17. },
  18. schema: [],
  19. messages: {
  20. redundantBlock: "Block is redundant.",
  21. redundantNestedBlock: "Nested block is redundant.",
  22. },
  23. },
  24. create(context) {
  25. // A stack of lone blocks to be checked for block-level bindings
  26. const loneBlocks = [];
  27. let ruleDef;
  28. const sourceCode = context.sourceCode;
  29. /**
  30. * Reports a node as invalid.
  31. * @param {ASTNode} node The node to be reported.
  32. * @returns {void}
  33. */
  34. function report(node) {
  35. const messageId =
  36. node.parent.type === "BlockStatement" ||
  37. node.parent.type === "StaticBlock"
  38. ? "redundantNestedBlock"
  39. : "redundantBlock";
  40. context.report({
  41. node,
  42. messageId,
  43. });
  44. }
  45. /**
  46. * Checks for any occurrence of a BlockStatement in a place where lists of statements can appear
  47. * @param {ASTNode} node The node to check
  48. * @returns {boolean} True if the node is a lone block.
  49. */
  50. function isLoneBlock(node) {
  51. return (
  52. node.parent.type === "BlockStatement" ||
  53. node.parent.type === "StaticBlock" ||
  54. node.parent.type === "Program" ||
  55. // Don't report blocks in switch cases if the block is the only statement of the case.
  56. (node.parent.type === "SwitchCase" &&
  57. !(
  58. node.parent.consequent[0] === node &&
  59. node.parent.consequent.length === 1
  60. ))
  61. );
  62. }
  63. /**
  64. * Checks the enclosing block of the current node for block-level bindings,
  65. * and "marks it" as valid if any.
  66. * @param {ASTNode} node The current node to check.
  67. * @returns {void}
  68. */
  69. function markLoneBlock(node) {
  70. if (loneBlocks.length === 0) {
  71. return;
  72. }
  73. const block = node.parent;
  74. if (loneBlocks.at(-1) === block) {
  75. loneBlocks.pop();
  76. }
  77. }
  78. // Default rule definition: report all lone blocks
  79. ruleDef = {
  80. BlockStatement(node) {
  81. if (isLoneBlock(node)) {
  82. report(node);
  83. }
  84. },
  85. };
  86. // ES6: report blocks without block-level bindings, or that's only child of another block
  87. if (context.languageOptions.ecmaVersion >= 2015) {
  88. ruleDef = {
  89. BlockStatement(node) {
  90. if (isLoneBlock(node)) {
  91. loneBlocks.push(node);
  92. }
  93. },
  94. "BlockStatement:exit"(node) {
  95. if (loneBlocks.length > 0 && loneBlocks.at(-1) === node) {
  96. loneBlocks.pop();
  97. report(node);
  98. } else if (
  99. (node.parent.type === "BlockStatement" ||
  100. node.parent.type === "StaticBlock") &&
  101. node.parent.body.length === 1
  102. ) {
  103. report(node);
  104. }
  105. },
  106. };
  107. ruleDef.VariableDeclaration = function (node) {
  108. if (node.kind !== "var") {
  109. markLoneBlock(node);
  110. }
  111. };
  112. ruleDef.FunctionDeclaration = function (node) {
  113. if (sourceCode.getScope(node).isStrict) {
  114. markLoneBlock(node);
  115. }
  116. };
  117. ruleDef.ClassDeclaration = markLoneBlock;
  118. }
  119. return ruleDef;
  120. },
  121. };