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 8const astUtils = require("./utils/ast-utils"); 9 10//------------------------------------------------------------------------------ 11// Rule Definition 12//------------------------------------------------------------------------------ 13 14module.exports = { 15 meta: { 16 type: "layout", 17 18 docs: { 19 description: "enforce consistent brace style for blocks", 20 category: "Stylistic Issues", 21 recommended: false, 22 url: "https://eslint.org/docs/rules/brace-style" 23 }, 24 25 schema: [ 26 { 27 enum: ["1tbs", "stroustrup", "allman"] 28 }, 29 { 30 type: "object", 31 properties: { 32 allowSingleLine: { 33 type: "boolean", 34 default: false 35 } 36 }, 37 additionalProperties: false 38 } 39 ], 40 41 fixable: "whitespace", 42 43 messages: { 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." 50 } 51 }, 52 53 create(context) { 54 const style = context.options[0] || "1tbs", 55 params = context.options[1] || {}, 56 sourceCode = context.getSourceCode(); 57 58 //-------------------------------------------------------------------------- 59 // Helpers 60 //-------------------------------------------------------------------------- 61 62 /** 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 67 */ 68 function removeNewlineBetween(firstToken, secondToken) { 69 const textRange = [firstToken.range[1], secondToken.range[0]]; 70 const textBetween = sourceCode.text.slice(textRange[0], textRange[1]); 71 72 // Don't do a fix if there is a comment between the tokens 73 if (textBetween.trim()) { 74 return null; 75 } 76 return fixer => fixer.replaceTextRange(textRange, " "); 77 } 78 79 /** 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 83 * @returns {void} 84 */ 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); 90 91 if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) { 92 context.report({ 93 node: openingCurly, 94 messageId: "nextLineOpen", 95 fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly) 96 }); 97 } 98 99 if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) { 100 context.report({ 101 node: openingCurly, 102 messageId: "sameLineOpen", 103 fix: fixer => fixer.insertTextBefore(openingCurly, "\n") 104 }); 105 } 106 107 if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) { 108 context.report({ 109 node: openingCurly, 110 messageId: "blockSameLine", 111 fix: fixer => fixer.insertTextAfter(openingCurly, "\n") 112 }); 113 } 114 115 if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) { 116 context.report({ 117 node: closingCurly, 118 messageId: "singleLineClose", 119 fix: fixer => fixer.insertTextBefore(closingCurly, "\n") 120 }); 121 } 122 } 123 124 /** 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`). 127 * @returns {void} 128 */ 129 function validateCurlyBeforeKeyword(curlyToken) { 130 const keywordToken = sourceCode.getTokenAfter(curlyToken); 131 132 if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) { 133 context.report({ 134 node: curlyToken, 135 messageId: "nextLineClose", 136 fix: removeNewlineBetween(curlyToken, keywordToken) 137 }); 138 } 139 140 if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) { 141 context.report({ 142 node: curlyToken, 143 messageId: "sameLineClose", 144 fix: fixer => fixer.insertTextAfter(curlyToken, "\n") 145 }); 146 } 147 } 148 149 //-------------------------------------------------------------------------- 150 // Public API 151 //-------------------------------------------------------------------------- 152 153 return { 154 BlockStatement(node) { 155 if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) { 156 validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node)); 157 } 158 }, 159 ClassBody(node) { 160 validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node)); 161 }, 162 SwitchStatement(node) { 163 const closingCurly = sourceCode.getLastToken(node); 164 const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly); 165 166 validateCurlyPair(openingCurly, closingCurly); 167 }, 168 IfStatement(node) { 169 if (node.consequent.type === "BlockStatement" && node.alternate) { 170 171 // Handle the keyword after the `if` block (before `else`) 172 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent)); 173 } 174 }, 175 TryStatement(node) { 176 177 // Handle the keyword after the `try` block (before `catch` or `finally`) 178 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block)); 179 180 if (node.handler && node.finalizer) { 181 182 // Handle the keyword after the `catch` block (before `finally`) 183 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body)); 184 } 185 } 186 }; 187 } 188}; 189