1 | /**
|
2 | * @fileoverview Rule to flag blocks with no reason to exist
|
3 | * @author Brandon Mills
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Rule Definition
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | module.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 | };
|