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