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