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