]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/padded-blocks.js
de75e8d2d51b06d88a8985bb281a6d6e1547b862
[pve-eslint.git] / eslint / lib / rules / padded-blocks.js
1 /**
2 * @fileoverview A rule to ensure blank lines within blocks.
3 * @author Mathias Schreck <https://github.com/lo1tuma>
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Rule Definition
16 //------------------------------------------------------------------------------
17
18 module.exports = {
19 meta: {
20 type: "layout",
21
22 docs: {
23 description: "require or disallow padding within blocks",
24 recommended: false,
25 url: "https://eslint.org/docs/rules/padded-blocks"
26 },
27
28 fixable: "whitespace",
29
30 schema: [
31 {
32 oneOf: [
33 {
34 enum: ["always", "never"]
35 },
36 {
37 type: "object",
38 properties: {
39 blocks: {
40 enum: ["always", "never"]
41 },
42 switches: {
43 enum: ["always", "never"]
44 },
45 classes: {
46 enum: ["always", "never"]
47 }
48 },
49 additionalProperties: false,
50 minProperties: 1
51 }
52 ]
53 },
54 {
55 type: "object",
56 properties: {
57 allowSingleLineBlocks: {
58 type: "boolean"
59 }
60 },
61 additionalProperties: false
62 }
63 ],
64
65 messages: {
66 alwaysPadBlock: "Block must be padded by blank lines.",
67 neverPadBlock: "Block must not be padded by blank lines."
68 }
69 },
70
71 create(context) {
72 const options = {};
73 const typeOptions = context.options[0] || "always";
74 const exceptOptions = context.options[1] || {};
75
76 if (typeof typeOptions === "string") {
77 const shouldHavePadding = typeOptions === "always";
78
79 options.blocks = shouldHavePadding;
80 options.switches = shouldHavePadding;
81 options.classes = shouldHavePadding;
82 } else {
83 if (Object.prototype.hasOwnProperty.call(typeOptions, "blocks")) {
84 options.blocks = typeOptions.blocks === "always";
85 }
86 if (Object.prototype.hasOwnProperty.call(typeOptions, "switches")) {
87 options.switches = typeOptions.switches === "always";
88 }
89 if (Object.prototype.hasOwnProperty.call(typeOptions, "classes")) {
90 options.classes = typeOptions.classes === "always";
91 }
92 }
93
94 if (Object.prototype.hasOwnProperty.call(exceptOptions, "allowSingleLineBlocks")) {
95 options.allowSingleLineBlocks = exceptOptions.allowSingleLineBlocks === true;
96 }
97
98 const sourceCode = context.getSourceCode();
99
100 /**
101 * Gets the open brace token from a given node.
102 * @param {ASTNode} node A BlockStatement or SwitchStatement node from which to get the open brace.
103 * @returns {Token} The token of the open brace.
104 */
105 function getOpenBrace(node) {
106 if (node.type === "SwitchStatement") {
107 return sourceCode.getTokenBefore(node.cases[0]);
108 }
109
110 if (node.type === "StaticBlock") {
111 return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
112 }
113
114 // `BlockStatement` or `ClassBody`
115 return sourceCode.getFirstToken(node);
116 }
117
118 /**
119 * Checks if the given parameter is a comment node
120 * @param {ASTNode|Token} node An AST node or token
121 * @returns {boolean} True if node is a comment
122 */
123 function isComment(node) {
124 return node.type === "Line" || node.type === "Block";
125 }
126
127 /**
128 * Checks if there is padding between two tokens
129 * @param {Token} first The first token
130 * @param {Token} second The second token
131 * @returns {boolean} True if there is at least a line between the tokens
132 */
133 function isPaddingBetweenTokens(first, second) {
134 return second.loc.start.line - first.loc.end.line >= 2;
135 }
136
137
138 /**
139 * Checks if the given token has a blank line after it.
140 * @param {Token} token The token to check.
141 * @returns {boolean} Whether or not the token is followed by a blank line.
142 */
143 function getFirstBlockToken(token) {
144 let prev,
145 first = token;
146
147 do {
148 prev = first;
149 first = sourceCode.getTokenAfter(first, { includeComments: true });
150 } while (isComment(first) && first.loc.start.line === prev.loc.end.line);
151
152 return first;
153 }
154
155 /**
156 * Checks if the given token is preceded by a blank line.
157 * @param {Token} token The token to check
158 * @returns {boolean} Whether or not the token is preceded by a blank line
159 */
160 function getLastBlockToken(token) {
161 let last = token,
162 next;
163
164 do {
165 next = last;
166 last = sourceCode.getTokenBefore(last, { includeComments: true });
167 } while (isComment(last) && last.loc.end.line === next.loc.start.line);
168
169 return last;
170 }
171
172 /**
173 * Checks if a node should be padded, according to the rule config.
174 * @param {ASTNode} node The AST node to check.
175 * @throws {Error} (Unreachable)
176 * @returns {boolean} True if the node should be padded, false otherwise.
177 */
178 function requirePaddingFor(node) {
179 switch (node.type) {
180 case "BlockStatement":
181 case "StaticBlock":
182 return options.blocks;
183 case "SwitchStatement":
184 return options.switches;
185 case "ClassBody":
186 return options.classes;
187
188 /* istanbul ignore next */
189 default:
190 throw new Error("unreachable");
191 }
192 }
193
194 /**
195 * Checks the given BlockStatement node to be padded if the block is not empty.
196 * @param {ASTNode} node The AST node of a BlockStatement.
197 * @returns {void} undefined.
198 */
199 function checkPadding(node) {
200 const openBrace = getOpenBrace(node),
201 firstBlockToken = getFirstBlockToken(openBrace),
202 tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { includeComments: true }),
203 closeBrace = sourceCode.getLastToken(node),
204 lastBlockToken = getLastBlockToken(closeBrace),
205 tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { includeComments: true }),
206 blockHasTopPadding = isPaddingBetweenTokens(tokenBeforeFirst, firstBlockToken),
207 blockHasBottomPadding = isPaddingBetweenTokens(lastBlockToken, tokenAfterLast);
208
209 if (options.allowSingleLineBlocks && astUtils.isTokenOnSameLine(tokenBeforeFirst, tokenAfterLast)) {
210 return;
211 }
212
213 if (requirePaddingFor(node)) {
214
215 if (!blockHasTopPadding) {
216 context.report({
217 node,
218 loc: {
219 start: tokenBeforeFirst.loc.start,
220 end: firstBlockToken.loc.start
221 },
222 fix(fixer) {
223 return fixer.insertTextAfter(tokenBeforeFirst, "\n");
224 },
225 messageId: "alwaysPadBlock"
226 });
227 }
228 if (!blockHasBottomPadding) {
229 context.report({
230 node,
231 loc: {
232 end: tokenAfterLast.loc.start,
233 start: lastBlockToken.loc.end
234 },
235 fix(fixer) {
236 return fixer.insertTextBefore(tokenAfterLast, "\n");
237 },
238 messageId: "alwaysPadBlock"
239 });
240 }
241 } else {
242 if (blockHasTopPadding) {
243
244 context.report({
245 node,
246 loc: {
247 start: tokenBeforeFirst.loc.start,
248 end: firstBlockToken.loc.start
249 },
250 fix(fixer) {
251 return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");
252 },
253 messageId: "neverPadBlock"
254 });
255 }
256
257 if (blockHasBottomPadding) {
258
259 context.report({
260 node,
261 loc: {
262 end: tokenAfterLast.loc.start,
263 start: lastBlockToken.loc.end
264 },
265 messageId: "neverPadBlock",
266 fix(fixer) {
267 return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");
268 }
269 });
270 }
271 }
272 }
273
274 const rule = {};
275
276 if (Object.prototype.hasOwnProperty.call(options, "switches")) {
277 rule.SwitchStatement = function(node) {
278 if (node.cases.length === 0) {
279 return;
280 }
281 checkPadding(node);
282 };
283 }
284
285 if (Object.prototype.hasOwnProperty.call(options, "blocks")) {
286 rule.BlockStatement = function(node) {
287 if (node.body.length === 0) {
288 return;
289 }
290 checkPadding(node);
291 };
292 rule.StaticBlock = rule.BlockStatement;
293 }
294
295 if (Object.prototype.hasOwnProperty.call(options, "classes")) {
296 rule.ClassBody = function(node) {
297 if (node.body.length === 0) {
298 return;
299 }
300 checkPadding(node);
301 };
302 }
303
304 return rule;
305 }
306 };