UNPKG

7.83 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
8const astUtils = require("../ast-utils");
9
10//------------------------------------------------------------------------------
11// Rule Definition
12//------------------------------------------------------------------------------
13
14module.exports = {
15 meta: {
16 docs: {
17 description: "enforce consistent brace style for blocks",
18 category: "Stylistic Issues",
19 recommended: false,
20 url: "https://eslint.org/docs/rules/brace-style"
21 },
22
23 schema: [
24 {
25 enum: ["1tbs", "stroustrup", "allman"]
26 },
27 {
28 type: "object",
29 properties: {
30 allowSingleLine: {
31 type: "boolean"
32 }
33 },
34 additionalProperties: false
35 }
36 ],
37
38 fixable: "whitespace",
39
40 messages: {
41 nextLineOpen: "Opening curly brace does not appear on the same line as controlling statement.",
42 sameLineOpen: "Opening curly brace appears on the same line as controlling statement.",
43 blockSameLine: "Statement inside of curly braces should be on next line.",
44 nextLineClose: "Closing curly brace does not appear on the same line as the subsequent block.",
45 singleLineClose: "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.",
46 sameLineClose: "Closing curly brace appears on the same line as the subsequent block."
47 }
48 },
49
50 create(context) {
51 const style = context.options[0] || "1tbs",
52 params = context.options[1] || {},
53 sourceCode = context.getSourceCode();
54
55 //--------------------------------------------------------------------------
56 // Helpers
57 //--------------------------------------------------------------------------
58
59 /**
60 * Fixes a place where a newline unexpectedly appears
61 * @param {Token} firstToken The token before the unexpected newline
62 * @param {Token} secondToken The token after the unexpected newline
63 * @returns {Function} A fixer function to remove the newlines between the tokens
64 */
65 function removeNewlineBetween(firstToken, secondToken) {
66 const textRange = [firstToken.range[1], secondToken.range[0]];
67 const textBetween = sourceCode.text.slice(textRange[0], textRange[1]);
68
69 // Don't do a fix if there is a comment between the tokens
70 if (textBetween.trim()) {
71 return null;
72 }
73 return fixer => fixer.replaceTextRange(textRange, " ");
74 }
75
76 /**
77 * Validates a pair of curly brackets based on the user's config
78 * @param {Token} openingCurly The opening curly bracket
79 * @param {Token} closingCurly The closing curly bracket
80 * @returns {void}
81 */
82 function validateCurlyPair(openingCurly, closingCurly) {
83 const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly);
84 const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly);
85 const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly);
86 const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly);
87
88 if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) {
89 context.report({
90 node: openingCurly,
91 messageId: "nextLineOpen",
92 fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly)
93 });
94 }
95
96 if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) {
97 context.report({
98 node: openingCurly,
99 messageId: "sameLineOpen",
100 fix: fixer => fixer.insertTextBefore(openingCurly, "\n")
101 });
102 }
103
104 if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) {
105 context.report({
106 node: openingCurly,
107 messageId: "blockSameLine",
108 fix: fixer => fixer.insertTextAfter(openingCurly, "\n")
109 });
110 }
111
112 if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) {
113 context.report({
114 node: closingCurly,
115 messageId: "singleLineClose",
116 fix: fixer => fixer.insertTextBefore(closingCurly, "\n")
117 });
118 }
119 }
120
121 /**
122 * Validates the location of a token that appears before a keyword (e.g. a newline before `else`)
123 * @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`).
124 * @returns {void}
125 */
126 function validateCurlyBeforeKeyword(curlyToken) {
127 const keywordToken = sourceCode.getTokenAfter(curlyToken);
128
129 if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
130 context.report({
131 node: curlyToken,
132 messageId: "nextLineClose",
133 fix: removeNewlineBetween(curlyToken, keywordToken)
134 });
135 }
136
137 if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
138 context.report({
139 node: curlyToken,
140 messageId: "sameLineClose",
141 fix: fixer => fixer.insertTextAfter(curlyToken, "\n")
142 });
143 }
144 }
145
146 //--------------------------------------------------------------------------
147 // Public API
148 //--------------------------------------------------------------------------
149
150 return {
151 BlockStatement(node) {
152 if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
153 validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
154 }
155 },
156 ClassBody(node) {
157 validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
158 },
159 SwitchStatement(node) {
160 const closingCurly = sourceCode.getLastToken(node);
161 const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly);
162
163 validateCurlyPair(openingCurly, closingCurly);
164 },
165 IfStatement(node) {
166 if (node.consequent.type === "BlockStatement" && node.alternate) {
167
168 // Handle the keyword after the `if` block (before `else`)
169 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent));
170 }
171 },
172 TryStatement(node) {
173
174 // Handle the keyword after the `try` block (before `catch` or `finally`)
175 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block));
176
177 if (node.handler && node.finalizer) {
178
179 // Handle the keyword after the `catch` block (before `finally`)
180 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body));
181 }
182 }
183 };
184 }
185};