UNPKG

9.1 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag block statements that do not use the one true brace style
3 * @author Ian Christian Myers
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13 meta: {
14 docs: {
15 description: "enforce consistent brace style for blocks",
16 category: "Stylistic Issues",
17 recommended: false
18 },
19
20 schema: [
21 {
22 enum: ["1tbs", "stroustrup", "allman"]
23 },
24 {
25 type: "object",
26 properties: {
27 allowSingleLine: {
28 type: "boolean"
29 }
30 },
31 additionalProperties: false
32 }
33 ]
34 },
35
36 create(context) {
37 const style = context.options[0] || "1tbs",
38 params = context.options[1] || {},
39 sourceCode = context.getSourceCode();
40
41 const OPEN_MESSAGE = "Opening curly brace does not appear on the same line as controlling statement.",
42 OPEN_MESSAGE_ALLMAN = "Opening curly brace appears on the same line as controlling statement.",
43 BODY_MESSAGE = "Statement inside of curly braces should be on next line.",
44 CLOSE_MESSAGE = "Closing curly brace does not appear on the same line as the subsequent block.",
45 CLOSE_MESSAGE_SINGLE = "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.",
46 CLOSE_MESSAGE_STROUSTRUP_ALLMAN = "Closing curly brace appears on the same line as the subsequent block.";
47
48 //--------------------------------------------------------------------------
49 // Helpers
50 //--------------------------------------------------------------------------
51
52 /**
53 * Determines if a given node is a block statement.
54 * @param {ASTNode} node The node to check.
55 * @returns {boolean} True if the node is a block statement, false if not.
56 * @private
57 */
58 function isBlock(node) {
59 return node && node.type === "BlockStatement";
60 }
61
62 /**
63 * Check if the token is an punctuator with a value of curly brace
64 * @param {Object} token - Token to check
65 * @returns {boolean} true if its a curly punctuator
66 * @private
67 */
68 function isCurlyPunctuator(token) {
69 return token.value === "{" || token.value === "}";
70 }
71
72 /**
73 * Binds a list of properties to a function that verifies that the opening
74 * curly brace is on the same line as its controlling statement of a given
75 * node.
76 * @param {...string} The properties to check on the node.
77 * @returns {Function} A function that will perform the check on a node
78 * @private
79 */
80 function checkBlock() {
81 const blockProperties = arguments;
82
83 return function(node) {
84 Array.prototype.forEach.call(blockProperties, function(blockProp) {
85 const block = node[blockProp];
86
87 if (!isBlock(block)) {
88 return;
89 }
90
91 const previousToken = sourceCode.getTokenBefore(block);
92 const curlyToken = sourceCode.getFirstToken(block);
93 const curlyTokenEnd = sourceCode.getLastToken(block);
94 const allOnSameLine = previousToken.loc.start.line === curlyTokenEnd.loc.start.line;
95
96 if (allOnSameLine && params.allowSingleLine) {
97 return;
98 }
99
100 if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) {
101 context.report(node, OPEN_MESSAGE);
102 } else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) {
103 context.report(node, OPEN_MESSAGE_ALLMAN);
104 }
105
106 if (!block.body.length) {
107 return;
108 }
109
110 if (curlyToken.loc.start.line === block.body[0].loc.start.line) {
111 context.report(block.body[0], BODY_MESSAGE);
112 }
113
114 if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.start.line) {
115 context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE);
116 }
117 });
118 };
119 }
120
121 /**
122 * Enforces the configured brace style on IfStatements
123 * @param {ASTNode} node An IfStatement node.
124 * @returns {void}
125 * @private
126 */
127 function checkIfStatement(node) {
128 checkBlock("consequent", "alternate")(node);
129
130 if (node.alternate) {
131
132 const tokens = sourceCode.getTokensBefore(node.alternate, 2);
133
134 if (style === "1tbs") {
135 if (tokens[0].loc.start.line !== tokens[1].loc.start.line &&
136 node.consequent.type === "BlockStatement" &&
137 isCurlyPunctuator(tokens[0])) {
138 context.report(node.alternate, CLOSE_MESSAGE);
139 }
140 } else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
141 context.report(node.alternate, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
142 }
143
144 }
145 }
146
147 /**
148 * Enforces the configured brace style on TryStatements
149 * @param {ASTNode} node A TryStatement node.
150 * @returns {void}
151 * @private
152 */
153 function checkTryStatement(node) {
154 checkBlock("block", "finalizer")(node);
155
156 if (isBlock(node.finalizer)) {
157 const tokens = sourceCode.getTokensBefore(node.finalizer, 2);
158
159 if (style === "1tbs") {
160 if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
161 context.report(node.finalizer, CLOSE_MESSAGE);
162 }
163 } else if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
164 context.report(node.finalizer, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
165 }
166 }
167 }
168
169 /**
170 * Enforces the configured brace style on CatchClauses
171 * @param {ASTNode} node A CatchClause node.
172 * @returns {void}
173 * @private
174 */
175 function checkCatchClause(node) {
176 const previousToken = sourceCode.getTokenBefore(node),
177 firstToken = sourceCode.getFirstToken(node);
178
179 checkBlock("body")(node);
180
181 if (isBlock(node.body)) {
182 if (style === "1tbs") {
183 if (previousToken.loc.start.line !== firstToken.loc.start.line) {
184 context.report(node, CLOSE_MESSAGE);
185 }
186 } else {
187 if (previousToken.loc.start.line === firstToken.loc.start.line) {
188 context.report(node, CLOSE_MESSAGE_STROUSTRUP_ALLMAN);
189 }
190 }
191 }
192 }
193
194 /**
195 * Enforces the configured brace style on SwitchStatements
196 * @param {ASTNode} node A SwitchStatement node.
197 * @returns {void}
198 * @private
199 */
200 function checkSwitchStatement(node) {
201 let tokens;
202
203 if (node.cases && node.cases.length) {
204 tokens = sourceCode.getTokensBefore(node.cases[0], 2);
205 } else {
206 tokens = sourceCode.getLastTokens(node, 3);
207 }
208
209 if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) {
210 context.report(node, OPEN_MESSAGE);
211 } else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) {
212 context.report(node, OPEN_MESSAGE_ALLMAN);
213 }
214 }
215
216 //--------------------------------------------------------------------------
217 // Public API
218 //--------------------------------------------------------------------------
219
220 return {
221 FunctionDeclaration: checkBlock("body"),
222 FunctionExpression: checkBlock("body"),
223 ArrowFunctionExpression: checkBlock("body"),
224 IfStatement: checkIfStatement,
225 TryStatement: checkTryStatement,
226 CatchClause: checkCatchClause,
227 DoWhileStatement: checkBlock("body"),
228 WhileStatement: checkBlock("body"),
229 WithStatement: checkBlock("body"),
230 ForStatement: checkBlock("body"),
231 ForInStatement: checkBlock("body"),
232 ForOfStatement: checkBlock("body"),
233 SwitchStatement: checkSwitchStatement
234 };
235
236 }
237};