083dc26199f7bd4d9cd41fcc830055c4cf10ea44
[dotfiles/.git] / semi-spacing.js
1 /**
2  * @fileoverview Validates spacing before and after semicolon
3  * @author Mathias Schreck
4  */
5
6 "use strict";
7
8 const astUtils = require("./utils/ast-utils");
9
10 //------------------------------------------------------------------------------
11 // Rule Definition
12 //------------------------------------------------------------------------------
13
14 module.exports = {
15     meta: {
16         type: "layout",
17
18         docs: {
19             description: "enforce consistent spacing before and after semicolons",
20             category: "Stylistic Issues",
21             recommended: false,
22             url: "https://eslint.org/docs/rules/semi-spacing"
23         },
24
25         fixable: "whitespace",
26
27         schema: [
28             {
29                 type: "object",
30                 properties: {
31                     before: {
32                         type: "boolean",
33                         default: false
34                     },
35                     after: {
36                         type: "boolean",
37                         default: true
38                     }
39                 },
40                 additionalProperties: false
41             }
42         ]
43     },
44
45     create(context) {
46
47         const config = context.options[0],
48             sourceCode = context.getSourceCode();
49         let requireSpaceBefore = false,
50             requireSpaceAfter = true;
51
52         if (typeof config === "object") {
53             requireSpaceBefore = config.before;
54             requireSpaceAfter = config.after;
55         }
56
57         /**
58          * Checks if a given token has leading whitespace.
59          * @param {Object} token The token to check.
60          * @returns {boolean} True if the given token has leading space, false if not.
61          */
62         function hasLeadingSpace(token) {
63             const tokenBefore = sourceCode.getTokenBefore(token);
64
65             return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token);
66         }
67
68         /**
69          * Checks if a given token has trailing whitespace.
70          * @param {Object} token The token to check.
71          * @returns {boolean} True if the given token has trailing space, false if not.
72          */
73         function hasTrailingSpace(token) {
74             const tokenAfter = sourceCode.getTokenAfter(token);
75
76             return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter);
77         }
78
79         /**
80          * Checks if the given token is the last token in its line.
81          * @param {Token} token The token to check.
82          * @returns {boolean} Whether or not the token is the last in its line.
83          */
84         function isLastTokenInCurrentLine(token) {
85             const tokenAfter = sourceCode.getTokenAfter(token);
86
87             return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter));
88         }
89
90         /**
91          * Checks if the given token is the first token in its line
92          * @param {Token} token The token to check.
93          * @returns {boolean} Whether or not the token is the first in its line.
94          */
95         function isFirstTokenInCurrentLine(token) {
96             const tokenBefore = sourceCode.getTokenBefore(token);
97
98             return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore));
99         }
100
101         /**
102          * Checks if the next token of a given token is a closing parenthesis.
103          * @param {Token} token The token to check.
104          * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis.
105          */
106         function isBeforeClosingParen(token) {
107             const nextToken = sourceCode.getTokenAfter(token);
108
109             return (nextToken && astUtils.isClosingBraceToken(nextToken) || astUtils.isClosingParenToken(nextToken));
110         }
111
112         /**
113          * Reports if the given token has invalid spacing.
114          * @param {Token} token The semicolon token to check.
115          * @param {ASTNode} node The corresponding node of the token.
116          * @returns {void}
117          */
118         function checkSemicolonSpacing(token, node) {
119             if (astUtils.isSemicolonToken(token)) {
120                 const location = token.loc.start;
121
122                 if (hasLeadingSpace(token)) {
123                     if (!requireSpaceBefore) {
124                         context.report({
125                             node,
126                             loc: location,
127                             message: "Unexpected whitespace before semicolon.",
128                             fix(fixer) {
129                                 const tokenBefore = sourceCode.getTokenBefore(token);
130
131                                 return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
132                             }
133                         });
134                     }
135                 } else {
136                     if (requireSpaceBefore) {
137                         context.report({
138                             node,
139                             loc: location,
140                             message: "Missing whitespace before semicolon.",
141                             fix(fixer) {
142                                 return fixer.insertTextBefore(token, " ");
143                             }
144                         });
145                     }
146                 }
147
148                 if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) {
149                     if (hasTrailingSpace(token)) {
150                         if (!requireSpaceAfter) {
151                             context.report({
152                                 node,
153                                 loc: location,
154                                 message: "Unexpected whitespace after semicolon.",
155                                 fix(fixer) {
156                                     const tokenAfter = sourceCode.getTokenAfter(token);
157
158                                     return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
159                                 }
160                             });
161                         }
162                     } else {
163                         if (requireSpaceAfter) {
164                             context.report({
165                                 node,
166                                 loc: location,
167                                 message: "Missing whitespace after semicolon.",
168                                 fix(fixer) {
169                                     return fixer.insertTextAfter(token, " ");
170                                 }
171                             });
172                         }
173                     }
174                 }
175             }
176         }
177
178         /**
179          * Checks the spacing of the semicolon with the assumption that the last token is the semicolon.
180          * @param {ASTNode} node The node to check.
181          * @returns {void}
182          */
183         function checkNode(node) {
184             const token = sourceCode.getLastToken(node);
185
186             checkSemicolonSpacing(token, node);
187         }
188
189         return {
190             VariableDeclaration: checkNode,
191             ExpressionStatement: checkNode,
192             BreakStatement: checkNode,
193             ContinueStatement: checkNode,
194             DebuggerStatement: checkNode,
195             ReturnStatement: checkNode,
196             ThrowStatement: checkNode,
197             ImportDeclaration: checkNode,
198             ExportNamedDeclaration: checkNode,
199             ExportAllDeclaration: checkNode,
200             ExportDefaultDeclaration: checkNode,
201             ForStatement(node) {
202                 if (node.init) {
203                     checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
204                 }
205
206                 if (node.test) {
207                     checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node);
208                 }
209             }
210         };
211     }
212 };