2 * @fileoverview Rule to flag block statements that do not use the one true brace style
3 * @author Ian Christian Myers
8 const astUtils = require("./utils/ast-utils");
10 //------------------------------------------------------------------------------
12 //------------------------------------------------------------------------------
19 description: "enforce consistent brace style for blocks",
20 category: "Stylistic Issues",
22 url: "https://eslint.org/docs/rules/brace-style"
27 enum: ["1tbs", "stroustrup", "allman"]
37 additionalProperties: false
41 fixable: "whitespace",
44 nextLineOpen: "Opening curly brace does not appear on the same line as controlling statement.",
45 sameLineOpen: "Opening curly brace appears on the same line as controlling statement.",
46 blockSameLine: "Statement inside of curly braces should be on next line.",
47 nextLineClose: "Closing curly brace does not appear on the same line as the subsequent block.",
48 singleLineClose: "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.",
49 sameLineClose: "Closing curly brace appears on the same line as the subsequent block."
54 const style = context.options[0] || "1tbs",
55 params = context.options[1] || {},
56 sourceCode = context.getSourceCode();
58 //--------------------------------------------------------------------------
60 //--------------------------------------------------------------------------
63 * Fixes a place where a newline unexpectedly appears
64 * @param {Token} firstToken The token before the unexpected newline
65 * @param {Token} secondToken The token after the unexpected newline
66 * @returns {Function} A fixer function to remove the newlines between the tokens
68 function removeNewlineBetween(firstToken, secondToken) {
69 const textRange = [firstToken.range[1], secondToken.range[0]];
70 const textBetween = sourceCode.text.slice(textRange[0], textRange[1]);
72 // Don't do a fix if there is a comment between the tokens
73 if (textBetween.trim()) {
76 return fixer => fixer.replaceTextRange(textRange, " ");
80 * Validates a pair of curly brackets based on the user's config
81 * @param {Token} openingCurly The opening curly bracket
82 * @param {Token} closingCurly The closing curly bracket
85 function validateCurlyPair(openingCurly, closingCurly) {
86 const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly);
87 const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly);
88 const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly);
89 const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly);
91 if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) {
94 messageId: "nextLineOpen",
95 fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly)
99 if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) {
102 messageId: "sameLineOpen",
103 fix: fixer => fixer.insertTextBefore(openingCurly, "\n")
107 if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) {
110 messageId: "blockSameLine",
111 fix: fixer => fixer.insertTextAfter(openingCurly, "\n")
115 if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) {
118 messageId: "singleLineClose",
119 fix: fixer => fixer.insertTextBefore(closingCurly, "\n")
125 * Validates the location of a token that appears before a keyword (e.g. a newline before `else`)
126 * @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`).
129 function validateCurlyBeforeKeyword(curlyToken) {
130 const keywordToken = sourceCode.getTokenAfter(curlyToken);
132 if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
135 messageId: "nextLineClose",
136 fix: removeNewlineBetween(curlyToken, keywordToken)
140 if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
143 messageId: "sameLineClose",
144 fix: fixer => fixer.insertTextAfter(curlyToken, "\n")
149 //--------------------------------------------------------------------------
151 //--------------------------------------------------------------------------
154 BlockStatement(node) {
155 if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
156 validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
160 validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
162 SwitchStatement(node) {
163 const closingCurly = sourceCode.getLastToken(node);
164 const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly);
166 validateCurlyPair(openingCurly, closingCurly);
169 if (node.consequent.type === "BlockStatement" && node.alternate) {
171 // Handle the keyword after the `if` block (before `else`)
172 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent));
177 // Handle the keyword after the `try` block (before `catch` or `finally`)
178 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block));
180 if (node.handler && node.finalizer) {
182 // Handle the keyword after the `catch` block (before `finally`)
183 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body));