UNPKG

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