|
5 | 5 |
|
6 | 6 | "use strict";
|
7 | 7 |
|
| 8 | +const astUtils = require("../ast-utils"); |
| 9 | + |
8 | 10 | //------------------------------------------------------------------------------
|
9 | 11 | // Rule Definition
|
10 | 12 | //------------------------------------------------------------------------------
|
@@ -52,238 +54,98 @@ module.exports = {
|
52 | 54 | //--------------------------------------------------------------------------
|
53 | 55 |
|
54 | 56 | /**
|
55 |
| - * Determines if a given node is a block statement. |
56 |
| - * @param {ASTNode} node The node to check. |
57 |
| - * @returns {boolean} True if the node is a block statement, false if not. |
58 |
| - * @private |
59 |
| - */ |
60 |
| - function isBlock(node) { |
61 |
| - return node && node.type === "BlockStatement"; |
62 |
| - } |
63 |
| - |
64 |
| - /** |
65 |
| - * Check if the token is an punctuator with a value of curly brace |
66 |
| - * @param {Object} token - Token to check |
67 |
| - * @returns {boolean} true if its a curly punctuator |
68 |
| - * @private |
69 |
| - */ |
70 |
| - function isCurlyPunctuator(token) { |
71 |
| - return token.value === "{" || token.value === "}"; |
72 |
| - } |
73 |
| - |
74 |
| - /** |
75 |
| - * Reports a place where a newline unexpectedly appears |
76 |
| - * @param {ASTNode} node The node to report |
77 |
| - * @param {string} message The message to report |
| 57 | + * Fixes a place where a newline unexpectedly appears |
78 | 58 | * @param {Token} firstToken The token before the unexpected newline
|
79 |
| - * @returns {void} |
| 59 | + * @param {Token} secondToken The token after the unexpected newline |
| 60 | + * @returns {Function} A fixer function to remove the newlines between the tokens |
80 | 61 | */
|
81 |
| - function reportExtraNewline(node, message, firstToken) { |
82 |
| - context.report({ |
83 |
| - node, |
84 |
| - message, |
85 |
| - fix(fixer) { |
86 |
| - const secondToken = sourceCode.getTokenAfter(firstToken); |
87 |
| - const textBetween = sourceCode.getText().slice(firstToken.range[1], secondToken.range[0]); |
88 |
| - const NEWLINE_REGEX = /\r\n|\r|\n|\u2028|\u2029/g; |
| 62 | + function removeNewlineBetween(firstToken, secondToken) { |
| 63 | + const textRange = [firstToken.range[1], secondToken.range[0]]; |
| 64 | + const textBetween = sourceCode.text.slice(textRange[0], textRange[1]); |
| 65 | + const NEWLINE_REGEX = /\r\n|\r|\n|\u2028|\u2029/g; |
89 | 66 |
|
90 |
| - // Don't do a fix if there is a comment between the tokens. |
91 |
| - return textBetween.trim() ? null : fixer.replaceTextRange([firstToken.range[1], secondToken.range[0]], textBetween.replace(NEWLINE_REGEX, "")); |
92 |
| - } |
93 |
| - }); |
| 67 | + // Don't do a fix if there is a comment between the tokens |
| 68 | + return fixer => fixer.replaceTextRange(textRange, textBetween.trim() ? null : textBetween.replace(NEWLINE_REGEX, "")); |
94 | 69 | }
|
95 | 70 |
|
96 | 71 | /**
|
97 |
| - * Binds a list of properties to a function that verifies that the opening |
98 |
| - * curly brace is on the same line as its controlling statement of a given |
99 |
| - * node. |
100 |
| - * @param {...string} The properties to check on the node. |
101 |
| - * @returns {Function} A function that will perform the check on a node |
102 |
| - * @private |
103 |
| - */ |
104 |
| - function checkBlock() { |
105 |
| - const blockProperties = arguments; |
106 |
| - |
107 |
| - return function(node) { |
108 |
| - Array.prototype.forEach.call(blockProperties, blockProp => { |
109 |
| - const block = node[blockProp]; |
110 |
| - |
111 |
| - if (!isBlock(block)) { |
112 |
| - return; |
113 |
| - } |
114 |
| - |
115 |
| - const previousToken = sourceCode.getTokenBefore(block); |
116 |
| - const curlyToken = sourceCode.getFirstToken(block); |
117 |
| - const curlyTokenEnd = sourceCode.getLastToken(block); |
118 |
| - const allOnSameLine = previousToken.loc.start.line === curlyTokenEnd.loc.start.line; |
119 |
| - |
120 |
| - if (allOnSameLine && params.allowSingleLine) { |
121 |
| - return; |
122 |
| - } |
123 |
| - |
124 |
| - if (style !== "allman" && previousToken.loc.start.line !== curlyToken.loc.start.line) { |
125 |
| - reportExtraNewline(node, OPEN_MESSAGE, previousToken); |
126 |
| - } else if (style === "allman" && previousToken.loc.start.line === curlyToken.loc.start.line) { |
127 |
| - context.report({ |
128 |
| - node, |
129 |
| - message: OPEN_MESSAGE_ALLMAN, |
130 |
| - fix: fixer => fixer.insertTextBefore(curlyToken, "\n") |
131 |
| - }); |
132 |
| - } |
133 |
| - |
134 |
| - if (!block.body.length) { |
135 |
| - return; |
136 |
| - } |
137 |
| - |
138 |
| - if (curlyToken.loc.start.line === block.body[0].loc.start.line) { |
139 |
| - context.report({ |
140 |
| - node: block.body[0], |
141 |
| - message: BODY_MESSAGE, |
142 |
| - fix: fixer => fixer.insertTextAfter(curlyToken, "\n") |
143 |
| - }); |
144 |
| - } |
145 |
| - |
146 |
| - if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.end.line) { |
147 |
| - context.report({ |
148 |
| - node: block.body[block.body.length - 1], |
149 |
| - message: CLOSE_MESSAGE_SINGLE, |
150 |
| - fix: fixer => fixer.insertTextBefore(curlyTokenEnd, "\n") |
151 |
| - }); |
152 |
| - } |
| 72 | + * Validates a pair of curly brackets based on the user's config |
| 73 | + * @param {Token} openingCurly The opening curly bracket |
| 74 | + * @param {Token} closingCurly The closing curly bracket |
| 75 | + * @returns {void} |
| 76 | + */ |
| 77 | + function validateCurlyPair(openingCurly, closingCurly) { |
| 78 | + const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly); |
| 79 | + const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly); |
| 80 | + const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly); |
| 81 | + const tokenAfterClosingCurly = sourceCode.getTokenAfter(closingCurly); |
| 82 | + const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly); |
| 83 | + |
| 84 | + if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) { |
| 85 | + context.report({ |
| 86 | + node: openingCurly, |
| 87 | + message: OPEN_MESSAGE, |
| 88 | + fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly) |
153 | 89 | });
|
154 |
| - }; |
155 |
| - } |
| 90 | + } |
156 | 91 |
|
157 |
| - /** |
158 |
| - * Enforces the configured brace style on IfStatements |
159 |
| - * @param {ASTNode} node An IfStatement node. |
160 |
| - * @returns {void} |
161 |
| - * @private |
162 |
| - */ |
163 |
| - function checkIfStatement(node) { |
164 |
| - checkBlock("consequent", "alternate")(node); |
| 92 | + if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) { |
| 93 | + context.report({ |
| 94 | + node: openingCurly, |
| 95 | + message: OPEN_MESSAGE_ALLMAN, |
| 96 | + fix: fixer => fixer.insertTextBefore(openingCurly, "\n") |
| 97 | + }); |
| 98 | + } |
165 | 99 |
|
166 |
| - if (node.alternate) { |
| 100 | + if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) { |
| 101 | + context.report({ |
| 102 | + node: openingCurly, |
| 103 | + message: BODY_MESSAGE, |
| 104 | + fix: fixer => fixer.insertTextAfter(openingCurly, "\n") |
| 105 | + }); |
| 106 | + } |
167 | 107 |
|
168 |
| - const tokens = sourceCode.getTokensBefore(node.alternate, 2); |
| 108 | + if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) { |
| 109 | + context.report({ |
| 110 | + node: closingCurly, |
| 111 | + message: CLOSE_MESSAGE_SINGLE, |
| 112 | + fix: fixer => fixer.insertTextBefore(closingCurly, "\n") |
| 113 | + }); |
| 114 | + } |
169 | 115 |
|
170 |
| - if (style === "1tbs") { |
171 |
| - if (tokens[0].loc.start.line !== tokens[1].loc.start.line && |
172 |
| - node.consequent.type === "BlockStatement" && |
173 |
| - isCurlyPunctuator(tokens[0])) { |
174 |
| - reportExtraNewline(node.alternate, CLOSE_MESSAGE, tokens[0]); |
175 |
| - } |
176 |
| - } else if (tokens[0].loc.start.line === tokens[1].loc.start.line) { |
| 116 | + if (tokenAfterClosingCurly && tokenAfterClosingCurly.type === "Keyword" && new Set(["else", "catch", "finally"]).has(tokenAfterClosingCurly.value)) { |
| 117 | + if (style === "1tbs" && !astUtils.isTokenOnSameLine(closingCurly, tokenAfterClosingCurly)) { |
177 | 118 | context.report({
|
178 |
| - node: node.alternate, |
179 |
| - message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN, |
180 |
| - fix: fixer => fixer.insertTextAfter(tokens[0], "\n") |
| 119 | + node: closingCurly, |
| 120 | + message: CLOSE_MESSAGE, |
| 121 | + fix: removeNewlineBetween(closingCurly, tokenAfterClosingCurly) |
181 | 122 | });
|
182 | 123 | }
|
183 | 124 |
|
184 |
| - } |
185 |
| - } |
186 |
| - |
187 |
| - /** |
188 |
| - * Enforces the configured brace style on TryStatements |
189 |
| - * @param {ASTNode} node A TryStatement node. |
190 |
| - * @returns {void} |
191 |
| - * @private |
192 |
| - */ |
193 |
| - function checkTryStatement(node) { |
194 |
| - checkBlock("block", "finalizer")(node); |
195 |
| - |
196 |
| - if (isBlock(node.finalizer)) { |
197 |
| - const tokens = sourceCode.getTokensBefore(node.finalizer, 2); |
198 |
| - |
199 |
| - if (style === "1tbs") { |
200 |
| - if (tokens[0].loc.start.line !== tokens[1].loc.start.line) { |
201 |
| - reportExtraNewline(node.finalizer, CLOSE_MESSAGE, tokens[0]); |
202 |
| - } |
203 |
| - } else if (tokens[0].loc.start.line === tokens[1].loc.start.line) { |
| 125 | + if (style !== "1tbs" && astUtils.isTokenOnSameLine(closingCurly, tokenAfterClosingCurly)) { |
204 | 126 | context.report({
|
205 |
| - node: node.finalizer, |
| 127 | + node: closingCurly, |
206 | 128 | message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
|
207 |
| - fix: fixer => fixer.insertTextAfter(tokens[0], "\n") |
| 129 | + fix: fixer => fixer.insertTextAfter(closingCurly, "\n") |
208 | 130 | });
|
209 | 131 | }
|
210 | 132 | }
|
211 | 133 | }
|
212 | 134 |
|
213 |
| - /** |
214 |
| - * Enforces the configured brace style on CatchClauses |
215 |
| - * @param {ASTNode} node A CatchClause node. |
216 |
| - * @returns {void} |
217 |
| - * @private |
218 |
| - */ |
219 |
| - function checkCatchClause(node) { |
220 |
| - const previousToken = sourceCode.getTokenBefore(node), |
221 |
| - firstToken = sourceCode.getFirstToken(node); |
222 |
| - |
223 |
| - checkBlock("body")(node); |
224 |
| - |
225 |
| - if (isBlock(node.body)) { |
226 |
| - if (style === "1tbs") { |
227 |
| - if (previousToken.loc.start.line !== firstToken.loc.start.line) { |
228 |
| - reportExtraNewline(node, CLOSE_MESSAGE, previousToken); |
229 |
| - } |
230 |
| - } else { |
231 |
| - if (previousToken.loc.start.line === firstToken.loc.start.line) { |
232 |
| - context.report({ |
233 |
| - node, |
234 |
| - message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN, |
235 |
| - fix: fixer => fixer.insertTextAfter(previousToken, "\n") |
236 |
| - }); |
237 |
| - } |
238 |
| - } |
239 |
| - } |
240 |
| - } |
241 |
| - |
242 |
| - /** |
243 |
| - * Enforces the configured brace style on SwitchStatements |
244 |
| - * @param {ASTNode} node A SwitchStatement node. |
245 |
| - * @returns {void} |
246 |
| - * @private |
247 |
| - */ |
248 |
| - function checkSwitchStatement(node) { |
249 |
| - let tokens; |
250 |
| - |
251 |
| - if (node.cases && node.cases.length) { |
252 |
| - tokens = sourceCode.getTokensBefore(node.cases[0], 2); |
253 |
| - } else { |
254 |
| - tokens = sourceCode.getLastTokens(node, 3); |
255 |
| - } |
256 |
| - |
257 |
| - if (style !== "allman" && tokens[0].loc.start.line !== tokens[1].loc.start.line) { |
258 |
| - reportExtraNewline(node, OPEN_MESSAGE, tokens[0]); |
259 |
| - } else if (style === "allman" && tokens[0].loc.start.line === tokens[1].loc.start.line) { |
260 |
| - context.report({ |
261 |
| - node, |
262 |
| - message: OPEN_MESSAGE_ALLMAN, |
263 |
| - fix: fixer => fixer.insertTextBefore(tokens[1], "\n") |
264 |
| - }); |
265 |
| - } |
266 |
| - } |
267 |
| - |
268 | 135 | //--------------------------------------------------------------------------
|
269 | 136 | // Public API
|
270 | 137 | //--------------------------------------------------------------------------
|
271 | 138 |
|
272 | 139 | return {
|
273 |
| - FunctionDeclaration: checkBlock("body"), |
274 |
| - FunctionExpression: checkBlock("body"), |
275 |
| - ArrowFunctionExpression: checkBlock("body"), |
276 |
| - IfStatement: checkIfStatement, |
277 |
| - TryStatement: checkTryStatement, |
278 |
| - CatchClause: checkCatchClause, |
279 |
| - DoWhileStatement: checkBlock("body"), |
280 |
| - WhileStatement: checkBlock("body"), |
281 |
| - WithStatement: checkBlock("body"), |
282 |
| - ForStatement: checkBlock("body"), |
283 |
| - ForInStatement: checkBlock("body"), |
284 |
| - ForOfStatement: checkBlock("body"), |
285 |
| - SwitchStatement: checkSwitchStatement |
286 |
| - }; |
| 140 | + BlockStatement(node) { |
| 141 | + validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node)); |
| 142 | + }, |
| 143 | + SwitchStatement(node) { |
| 144 | + const closingCurly = sourceCode.getLastToken(node); |
| 145 | + const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly); |
287 | 146 |
|
| 147 | + validateCurlyPair(openingCurly, closingCurly); |
| 148 | + } |
| 149 | + }; |
288 | 150 | }
|
289 | 151 | };
|
0 commit comments