• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview A rule to ensure whitespace before blocks.
3 * @author Mathias Schreck <https://github.com/lo1tuma>
4 */
5
6"use strict";
7
8const astUtils = require("./utils/ast-utils");
9
10//------------------------------------------------------------------------------
11// Rule Definition
12//------------------------------------------------------------------------------
13
14module.exports = {
15    meta: {
16        type: "layout",
17
18        docs: {
19            description: "enforce consistent spacing before blocks",
20            category: "Stylistic Issues",
21            recommended: false,
22            url: "https://eslint.org/docs/rules/space-before-blocks"
23        },
24
25        fixable: "whitespace",
26
27        schema: [
28            {
29                oneOf: [
30                    {
31                        enum: ["always", "never"]
32                    },
33                    {
34                        type: "object",
35                        properties: {
36                            keywords: {
37                                enum: ["always", "never", "off"]
38                            },
39                            functions: {
40                                enum: ["always", "never", "off"]
41                            },
42                            classes: {
43                                enum: ["always", "never", "off"]
44                            }
45                        },
46                        additionalProperties: false
47                    }
48                ]
49            }
50        ],
51
52        messages: {
53            unexpectedSpace: "Unexpected space before opening brace.",
54            missingSpace: "Missing space before opening brace."
55        }
56    },
57
58    create(context) {
59        const config = context.options[0],
60            sourceCode = context.getSourceCode();
61        let alwaysFunctions = true,
62            alwaysKeywords = true,
63            alwaysClasses = true,
64            neverFunctions = false,
65            neverKeywords = false,
66            neverClasses = false;
67
68        if (typeof config === "object") {
69            alwaysFunctions = config.functions === "always";
70            alwaysKeywords = config.keywords === "always";
71            alwaysClasses = config.classes === "always";
72            neverFunctions = config.functions === "never";
73            neverKeywords = config.keywords === "never";
74            neverClasses = config.classes === "never";
75        } else if (config === "never") {
76            alwaysFunctions = false;
77            alwaysKeywords = false;
78            alwaysClasses = false;
79            neverFunctions = true;
80            neverKeywords = true;
81            neverClasses = true;
82        }
83
84        /**
85         * Checks whether or not a given token is an arrow operator (=>) or a keyword
86         * in order to avoid to conflict with `arrow-spacing` and `keyword-spacing`.
87         * @param {Token} token A token to check.
88         * @returns {boolean} `true` if the token is an arrow operator.
89         */
90        function isConflicted(token) {
91            return (token.type === "Punctuator" && token.value === "=>") || token.type === "Keyword";
92        }
93
94        /**
95         * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line.
96         * @param {ASTNode|Token} node The AST node of a BlockStatement.
97         * @returns {void} undefined.
98         */
99        function checkPrecedingSpace(node) {
100            const precedingToken = sourceCode.getTokenBefore(node);
101
102            if (precedingToken && !isConflicted(precedingToken) && astUtils.isTokenOnSameLine(precedingToken, node)) {
103                const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node);
104                const parent = context.getAncestors().pop();
105                let requireSpace;
106                let requireNoSpace;
107
108                if (parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") {
109                    requireSpace = alwaysFunctions;
110                    requireNoSpace = neverFunctions;
111                } else if (node.type === "ClassBody") {
112                    requireSpace = alwaysClasses;
113                    requireNoSpace = neverClasses;
114                } else {
115                    requireSpace = alwaysKeywords;
116                    requireNoSpace = neverKeywords;
117                }
118
119                if (requireSpace && !hasSpace) {
120                    context.report({
121                        node,
122                        messageId: "missingSpace",
123                        fix(fixer) {
124                            return fixer.insertTextBefore(node, " ");
125                        }
126                    });
127                } else if (requireNoSpace && hasSpace) {
128                    context.report({
129                        node,
130                        messageId: "unexpectedSpace",
131                        fix(fixer) {
132                            return fixer.removeRange([precedingToken.range[1], node.range[0]]);
133                        }
134                    });
135                }
136            }
137        }
138
139        /**
140         * Checks if the CaseBlock of an given SwitchStatement node has a preceding space.
141         * @param {ASTNode} node The node of a SwitchStatement.
142         * @returns {void} undefined.
143         */
144        function checkSpaceBeforeCaseBlock(node) {
145            const cases = node.cases;
146            let openingBrace;
147
148            if (cases.length > 0) {
149                openingBrace = sourceCode.getTokenBefore(cases[0]);
150            } else {
151                openingBrace = sourceCode.getLastToken(node, 1);
152            }
153
154            checkPrecedingSpace(openingBrace);
155        }
156
157        return {
158            BlockStatement: checkPrecedingSpace,
159            ClassBody: checkPrecedingSpace,
160            SwitchStatement: checkSpaceBeforeCaseBlock
161        };
162
163    }
164};
165