UNPKG

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