Skip to content

Commit 55f0cb6

Browse files
Update: refactor brace-style and fix inconsistencies (fixes #7869) (#7870)
1 parent 7f8393c commit 55f0cb6

File tree

2 files changed

+165
-296
lines changed

2 files changed

+165
-296
lines changed

lib/rules/brace-style.js

Lines changed: 67 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
"use strict";
77

8+
const astUtils = require("../ast-utils");
9+
810
//------------------------------------------------------------------------------
911
// Rule Definition
1012
//------------------------------------------------------------------------------
@@ -52,238 +54,98 @@ module.exports = {
5254
//--------------------------------------------------------------------------
5355

5456
/**
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
7858
* @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
8061
*/
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;
8966

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, ""));
9469
}
9570

9671
/**
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)
15389
});
154-
};
155-
}
90+
}
15691

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+
}
16599

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+
}
167107

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+
}
169115

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)) {
177118
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)
181122
});
182123
}
183124

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)) {
204126
context.report({
205-
node: node.finalizer,
127+
node: closingCurly,
206128
message: CLOSE_MESSAGE_STROUSTRUP_ALLMAN,
207-
fix: fixer => fixer.insertTextAfter(tokens[0], "\n")
129+
fix: fixer => fixer.insertTextAfter(closingCurly, "\n")
208130
});
209131
}
210132
}
211133
}
212134

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-
268135
//--------------------------------------------------------------------------
269136
// Public API
270137
//--------------------------------------------------------------------------
271138

272139
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);
287146

147+
validateCurlyPair(openingCurly, closingCurly);
148+
}
149+
};
288150
}
289151
};

0 commit comments

Comments
 (0)