summaryrefslogtreecommitdiff
path: root/tools/node_modules/eslint/lib/rules/no-lone-blocks.js
blob: c31439b6ae2e0dc40239518a99eb6976ac53790f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
 * @fileoverview Rule to flag blocks with no reason to exist
 * @author Brandon Mills
 */

"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "Disallow unnecessary nested blocks",
            recommended: false,
            url: "https://eslint.org/docs/rules/no-lone-blocks"
        },

        schema: [],

        messages: {
            redundantBlock: "Block is redundant.",
            redundantNestedBlock: "Nested block is redundant."
        }
    },

    create(context) {

        // A stack of lone blocks to be checked for block-level bindings
        const loneBlocks = [];
        let ruleDef;
        const sourceCode = context.getSourceCode();

        /**
         * Reports a node as invalid.
         * @param {ASTNode} node The node to be reported.
         * @returns {void}
         */
        function report(node) {
            const messageId = node.parent.type === "BlockStatement" || node.parent.type === "StaticBlock"
                ? "redundantNestedBlock"
                : "redundantBlock";

            context.report({
                node,
                messageId
            });
        }

        /**
         * Checks for any occurrence of a BlockStatement in a place where lists of statements can appear
         * @param {ASTNode} node The node to check
         * @returns {boolean} True if the node is a lone block.
         */
        function isLoneBlock(node) {
            return node.parent.type === "BlockStatement" ||
                node.parent.type === "StaticBlock" ||
                node.parent.type === "Program" ||

                // Don't report blocks in switch cases if the block is the only statement of the case.
                node.parent.type === "SwitchCase" && !(node.parent.consequent[0] === node && node.parent.consequent.length === 1);
        }

        /**
         * Checks the enclosing block of the current node for block-level bindings,
         * and "marks it" as valid if any.
         * @param {ASTNode} node The current node to check.
         * @returns {void}
         */
        function markLoneBlock(node) {
            if (loneBlocks.length === 0) {
                return;
            }

            const block = node.parent;

            if (loneBlocks[loneBlocks.length - 1] === block) {
                loneBlocks.pop();
            }
        }

        // Default rule definition: report all lone blocks
        ruleDef = {
            BlockStatement(node) {
                if (isLoneBlock(node)) {
                    report(node);
                }
            }
        };

        // ES6: report blocks without block-level bindings, or that's only child of another block
        if (context.languageOptions.ecmaVersion >= 2015) {
            ruleDef = {
                BlockStatement(node) {
                    if (isLoneBlock(node)) {
                        loneBlocks.push(node);
                    }
                },
                "BlockStatement:exit"(node) {
                    if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) {
                        loneBlocks.pop();
                        report(node);
                    } else if (
                        (
                            node.parent.type === "BlockStatement" ||
                            node.parent.type === "StaticBlock"
                        ) &&
                        node.parent.body.length === 1
                    ) {
                        report(node);
                    }
                }
            };

            ruleDef.VariableDeclaration = function(node) {
                if (node.kind === "let" || node.kind === "const") {
                    markLoneBlock(node);
                }
            };

            ruleDef.FunctionDeclaration = function(node) {
                if (sourceCode.getScope(node).isStrict) {
                    markLoneBlock(node);
                }
            };

            ruleDef.ClassDeclaration = markLoneBlock;
        }

        return ruleDef;
    }
};