]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/padded-blocks.js
update to 7.1.0 sources
[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 category: "Stylistic Issues",
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 }
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 return sourceCode.getFirstToken(node);
110 }
111
112 /**
113 * Checks if the given parameter is a comment node
114 * @param {ASTNode|Token} node An AST node or token
115 * @returns {boolean} True if node is a comment
116 */
117 function isComment(node) {
118 return node.type === "Line" || node.type === "Block";
119 }
120
121 /**
122 * Checks if there is padding between two tokens
123 * @param {Token} first The first token
124 * @param {Token} second The second token
125 * @returns {boolean} True if there is at least a line between the tokens
126 */
127 function isPaddingBetweenTokens(first, second) {
128 return second.loc.start.line - first.loc.end.line >= 2;
129 }
130
131
132 /**
133 * Checks if the given token has a blank line after it.
134 * @param {Token} token The token to check.
135 * @returns {boolean} Whether or not the token is followed by a blank line.
136 */
137 function getFirstBlockToken(token) {
138 let prev,
139 first = token;
140
141 do {
142 prev = first;
143 first = sourceCode.getTokenAfter(first, { includeComments: true });
144 } while (isComment(first) && first.loc.start.line === prev.loc.end.line);
145
146 return first;
147 }
148
149 /**
150 * Checks if the given token is preceded by a blank line.
151 * @param {Token} token The token to check
152 * @returns {boolean} Whether or not the token is preceded by a blank line
153 */
154 function getLastBlockToken(token) {
155 let last = token,
156 next;
157
158 do {
159 next = last;
160 last = sourceCode.getTokenBefore(last, { includeComments: true });
161 } while (isComment(last) && last.loc.end.line === next.loc.start.line);
162
163 return last;
164 }
165
166 /**
167 * Checks if a node should be padded, according to the rule config.
168 * @param {ASTNode} node The AST node to check.
169 * @returns {boolean} True if the node should be padded, false otherwise.
170 */
171 function requirePaddingFor(node) {
172 switch (node.type) {
173 case "BlockStatement":
174 return options.blocks;
175 case "SwitchStatement":
176 return options.switches;
177 case "ClassBody":
178 return options.classes;
179
180 /* istanbul ignore next */
181 default:
182 throw new Error("unreachable");
183 }
184 }
185
186 /**
187 * Checks the given BlockStatement node to be padded if the block is not empty.
188 * @param {ASTNode} node The AST node of a BlockStatement.
189 * @returns {void} undefined.
190 */
191 function checkPadding(node) {
192 const openBrace = getOpenBrace(node),
193 firstBlockToken = getFirstBlockToken(openBrace),
194 tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { includeComments: true }),
195 closeBrace = sourceCode.getLastToken(node),
196 lastBlockToken = getLastBlockToken(closeBrace),
197 tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { includeComments: true }),
198 blockHasTopPadding = isPaddingBetweenTokens(tokenBeforeFirst, firstBlockToken),
199 blockHasBottomPadding = isPaddingBetweenTokens(lastBlockToken, tokenAfterLast);
200
201 if (options.allowSingleLineBlocks && astUtils.isTokenOnSameLine(tokenBeforeFirst, tokenAfterLast)) {
202 return;
203 }
204
205 if (requirePaddingFor(node)) {
206
207 if (!blockHasTopPadding) {
208 context.report({
209 node,
210 loc: {
211 start: tokenBeforeFirst.loc.start,
212 end: firstBlockToken.loc.start
213 },
214 fix(fixer) {
215 return fixer.insertTextAfter(tokenBeforeFirst, "\n");
216 },
217 messageId: "alwaysPadBlock"
218 });
219 }
220 if (!blockHasBottomPadding) {
221 context.report({
222 node,
223 loc: {
224 end: tokenAfterLast.loc.start,
225 start: lastBlockToken.loc.end
226 },
227 fix(fixer) {
228 return fixer.insertTextBefore(tokenAfterLast, "\n");
229 },
230 messageId: "alwaysPadBlock"
231 });
232 }
233 } else {
234 if (blockHasTopPadding) {
235
236 context.report({
237 node,
238 loc: {
239 start: tokenBeforeFirst.loc.start,
240 end: firstBlockToken.loc.start
241 },
242 fix(fixer) {
243 return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");
244 },
245 messageId: "neverPadBlock"
246 });
247 }
248
249 if (blockHasBottomPadding) {
250
251 context.report({
252 node,
253 loc: {
254 end: tokenAfterLast.loc.start,
255 start: lastBlockToken.loc.end
256 },
257 messageId: "neverPadBlock",
258 fix(fixer) {
259 return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");
260 }
261 });
262 }
263 }
264 }
265
266 const rule = {};
267
268 if (Object.prototype.hasOwnProperty.call(options, "switches")) {
269 rule.SwitchStatement = function(node) {
270 if (node.cases.length === 0) {
271 return;
272 }
273 checkPadding(node);
274 };
275 }
276
277 if (Object.prototype.hasOwnProperty.call(options, "blocks")) {
278 rule.BlockStatement = function(node) {
279 if (node.body.length === 0) {
280 return;
281 }
282 checkPadding(node);
283 };
284 }
285
286 if (Object.prototype.hasOwnProperty.call(options, "classes")) {
287 rule.ClassBody = function(node) {
288 if (node.body.length === 0) {
289 return;
290 }
291 checkPadding(node);
292 };
293 }
294
295 return rule;
296 }
297 };