]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/brace-style.js
f4adb9490ebdf69a2837a6ce8ac94cb90ff13c14
[pve-eslint.git] / eslint / lib / rules / brace-style.js
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
8 const astUtils = require("./utils/ast-utils");
9
10 //------------------------------------------------------------------------------
11 // Rule Definition
12 //------------------------------------------------------------------------------
13
14 /** @type {import('../shared/types').Rule} */
15 module.exports = {
16 meta: {
17 type: "layout",
18
19 docs: {
20 description: "enforce consistent brace style for blocks",
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 StaticBlock(node) {
160 validateCurlyPair(
161 sourceCode.getFirstToken(node, { skip: 1 }), // skip the `static` token
162 sourceCode.getLastToken(node)
163 );
164 },
165 ClassBody(node) {
166 validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
167 },
168 SwitchStatement(node) {
169 const closingCurly = sourceCode.getLastToken(node);
170 const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly);
171
172 validateCurlyPair(openingCurly, closingCurly);
173 },
174 IfStatement(node) {
175 if (node.consequent.type === "BlockStatement" && node.alternate) {
176
177 // Handle the keyword after the `if` block (before `else`)
178 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent));
179 }
180 },
181 TryStatement(node) {
182
183 // Handle the keyword after the `try` block (before `catch` or `finally`)
184 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block));
185
186 if (node.handler && node.finalizer) {
187
188 // Handle the keyword after the `catch` block (before `finally`)
189 validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body));
190 }
191 }
192 };
193 }
194 };