UNPKG

3.95 kBJavaScriptView Raw
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};