c6ed44a2798a6be0aa49bf09c09fbda3b3466545
[dotfiles/.git] / block-spacing.js
1 /**
2  * @fileoverview A rule to disallow or enforce spaces inside of single line blocks.
3  * @author Toru Nagashima
4  */
5
6 "use strict";
7
8 const util = require("./utils/ast-utils");
9
10 //------------------------------------------------------------------------------
11 // Rule Definition
12 //------------------------------------------------------------------------------
13
14 module.exports = {
15     meta: {
16         type: "layout",
17
18         docs: {
19             description: "disallow or enforce spaces inside of blocks after opening block and before closing block",
20             category: "Stylistic Issues",
21             recommended: false,
22             url: "https://eslint.org/docs/rules/block-spacing"
23         },
24
25         fixable: "whitespace",
26
27         schema: [
28             { enum: ["always", "never"] }
29         ],
30
31         messages: {
32             missing: "Requires a space {{location}} '{{token}}'.",
33             extra: "Unexpected space(s) {{location}} '{{token}}'."
34         }
35     },
36
37     create(context) {
38         const always = (context.options[0] !== "never"),
39             messageId = always ? "missing" : "extra",
40             sourceCode = context.getSourceCode();
41
42         /**
43          * Gets the open brace token from a given node.
44          * @param {ASTNode} node A BlockStatement/SwitchStatement node to get.
45          * @returns {Token} The token of the open brace.
46          */
47         function getOpenBrace(node) {
48             if (node.type === "SwitchStatement") {
49                 if (node.cases.length > 0) {
50                     return sourceCode.getTokenBefore(node.cases[0]);
51                 }
52                 return sourceCode.getLastToken(node, 1);
53             }
54             return sourceCode.getFirstToken(node);
55         }
56
57         /**
58          * Checks whether or not:
59          *   - given tokens are on same line.
60          *   - there is/isn't a space between given tokens.
61          * @param {Token} left A token to check.
62          * @param {Token} right The token which is next to `left`.
63          * @returns {boolean}
64          *    When the option is `"always"`, `true` if there are one or more spaces between given tokens.
65          *    When the option is `"never"`, `true` if there are not any spaces between given tokens.
66          *    If given tokens are not on same line, it's always `true`.
67          */
68         function isValid(left, right) {
69             return (
70                 !util.isTokenOnSameLine(left, right) ||
71                 sourceCode.isSpaceBetweenTokens(left, right) === always
72             );
73         }
74
75         /**
76          * Reports invalid spacing style inside braces.
77          * @param {ASTNode} node A BlockStatement/SwitchStatement node to get.
78          * @returns {void}
79          */
80         function checkSpacingInsideBraces(node) {
81
82             // Gets braces and the first/last token of content.
83             const openBrace = getOpenBrace(node);
84             const closeBrace = sourceCode.getLastToken(node);
85             const firstToken = sourceCode.getTokenAfter(openBrace, { includeComments: true });
86             const lastToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
87
88             // Skip if the node is invalid or empty.
89             if (openBrace.type !== "Punctuator" ||
90                 openBrace.value !== "{" ||
91                 closeBrace.type !== "Punctuator" ||
92                 closeBrace.value !== "}" ||
93                 firstToken === closeBrace
94             ) {
95                 return;
96             }
97
98             // Skip line comments for option never
99             if (!always && firstToken.type === "Line") {
100                 return;
101             }
102
103             // Check.
104             if (!isValid(openBrace, firstToken)) {
105                 context.report({
106                     node,
107                     loc: openBrace.loc.start,
108                     messageId,
109                     data: {
110                         location: "after",
111                         token: openBrace.value
112                     },
113                     fix(fixer) {
114                         if (always) {
115                             return fixer.insertTextBefore(firstToken, " ");
116                         }
117
118                         return fixer.removeRange([openBrace.range[1], firstToken.range[0]]);
119                     }
120                 });
121             }
122             if (!isValid(lastToken, closeBrace)) {
123                 context.report({
124                     node,
125                     loc: closeBrace.loc.start,
126                     messageId,
127                     data: {
128                         location: "before",
129                         token: closeBrace.value
130                     },
131                     fix(fixer) {
132                         if (always) {
133                             return fixer.insertTextAfter(lastToken, " ");
134                         }
135
136                         return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]);
137                     }
138                 });
139             }
140         }
141
142         return {
143             BlockStatement: checkSpacingInsideBraces,
144             SwitchStatement: checkSpacingInsideBraces
145         };
146     }
147 };