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 | docs: {
|
15 | description: "disallow unnecessary nested blocks",
|
16 | category: "Best Practices",
|
17 | recommended: false,
|
18 | url: "https://eslint.org/docs/rules/no-lone-blocks"
|
19 | },
|
20 |
|
21 | schema: []
|
22 | },
|
23 |
|
24 | create(context) {
|
25 |
|
26 | // A stack of lone blocks to be checked for block-level bindings
|
27 | const loneBlocks = [];
|
28 | let ruleDef;
|
29 |
|
30 | /**
|
31 | * Reports a node as invalid.
|
32 | * @param {ASTNode} node - The node to be reported.
|
33 | * @returns {void}
|
34 | */
|
35 | function report(node) {
|
36 | const message = node.parent.type === "BlockStatement" ? "Nested block is redundant." : "Block is redundant.";
|
37 |
|
38 | context.report({ node, message });
|
39 | }
|
40 |
|
41 | /**
|
42 | * Checks for any ocurrence of a BlockStatement in a place where lists of statements can appear
|
43 | * @param {ASTNode} node The node to check
|
44 | * @returns {boolean} True if the node is a lone block.
|
45 | */
|
46 | function isLoneBlock(node) {
|
47 | return node.parent.type === "BlockStatement" ||
|
48 | node.parent.type === "Program" ||
|
49 |
|
50 | // Don't report blocks in switch cases if the block is the only statement of the case.
|
51 | node.parent.type === "SwitchCase" && !(node.parent.consequent[0] === node && node.parent.consequent.length === 1);
|
52 | }
|
53 |
|
54 | /**
|
55 | * Checks the enclosing block of the current node for block-level bindings,
|
56 | * and "marks it" as valid if any.
|
57 | * @returns {void}
|
58 | */
|
59 | function markLoneBlock() {
|
60 | if (loneBlocks.length === 0) {
|
61 | return;
|
62 | }
|
63 |
|
64 | const block = context.getAncestors().pop();
|
65 |
|
66 | if (loneBlocks[loneBlocks.length - 1] === block) {
|
67 | loneBlocks.pop();
|
68 | }
|
69 | }
|
70 |
|
71 | // Default rule definition: report all lone blocks
|
72 | ruleDef = {
|
73 | BlockStatement(node) {
|
74 | if (isLoneBlock(node)) {
|
75 | report(node);
|
76 | }
|
77 | }
|
78 | };
|
79 |
|
80 | // ES6: report blocks without block-level bindings
|
81 | if (context.parserOptions.ecmaVersion >= 6) {
|
82 | ruleDef = {
|
83 | BlockStatement(node) {
|
84 | if (isLoneBlock(node)) {
|
85 | loneBlocks.push(node);
|
86 | }
|
87 | },
|
88 | "BlockStatement:exit"(node) {
|
89 | if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) {
|
90 | loneBlocks.pop();
|
91 | report(node);
|
92 | }
|
93 | }
|
94 | };
|
95 |
|
96 | ruleDef.VariableDeclaration = function(node) {
|
97 | if (node.kind === "let" || node.kind === "const") {
|
98 | markLoneBlock();
|
99 | }
|
100 | };
|
101 |
|
102 | ruleDef.FunctionDeclaration = function() {
|
103 | if (context.getScope().isStrict) {
|
104 | markLoneBlock();
|
105 | }
|
106 | };
|
107 |
|
108 | ruleDef.ClassDeclaration = markLoneBlock;
|
109 | }
|
110 |
|
111 | return ruleDef;
|
112 | }
|
113 | };
|