UNPKG

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