• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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