UNPKG

7.97 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 (
109 nextToken &&
110 nextToken.type === "Punctuator" &&
111 (nextToken.value === "}" || nextToken.value === ")")
112 );
113 }
114
115 /**
116 * Checks if the given token is a semicolon.
117 * @param {Token} token The token to check.
118 * @returns {boolean} Whether or not the given token is a semicolon.
119 */
120 function isSemicolon(token) {
121 return token.type === "Punctuator" && token.value === ";";
122 }
123
124 /**
125 * Reports if the given token has invalid spacing.
126 * @param {Token} token The semicolon token to check.
127 * @param {ASTNode} node The corresponding node of the token.
128 * @returns {void}
129 */
130 function checkSemicolonSpacing(token, node) {
131 if (isSemicolon(token)) {
132 const location = token.loc.start;
133
134 if (hasLeadingSpace(token)) {
135 if (!requireSpaceBefore) {
136 context.report({
137 node,
138 loc: location,
139 message: "Unexpected whitespace before semicolon.",
140 fix(fixer) {
141 const tokenBefore = sourceCode.getTokenBefore(token);
142
143 return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
144 }
145 });
146 }
147 } else {
148 if (requireSpaceBefore) {
149 context.report({
150 node,
151 loc: location,
152 message: "Missing whitespace before semicolon.",
153 fix(fixer) {
154 return fixer.insertTextBefore(token, " ");
155 }
156 });
157 }
158 }
159
160 if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) {
161 if (hasTrailingSpace(token)) {
162 if (!requireSpaceAfter) {
163 context.report({
164 node,
165 loc: location,
166 message: "Unexpected whitespace after semicolon.",
167 fix(fixer) {
168 const tokenAfter = sourceCode.getTokenAfter(token);
169
170 return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
171 }
172 });
173 }
174 } else {
175 if (requireSpaceAfter) {
176 context.report({
177 node,
178 loc: location,
179 message: "Missing whitespace after semicolon.",
180 fix(fixer) {
181 return fixer.insertTextAfter(token, " ");
182 }
183 });
184 }
185 }
186 }
187 }
188 }
189
190 /**
191 * Checks the spacing of the semicolon with the assumption that the last token is the semicolon.
192 * @param {ASTNode} node The node to check.
193 * @returns {void}
194 */
195 function checkNode(node) {
196 const token = sourceCode.getLastToken(node);
197
198 checkSemicolonSpacing(token, node);
199 }
200
201 return {
202 VariableDeclaration: checkNode,
203 ExpressionStatement: checkNode,
204 BreakStatement: checkNode,
205 ContinueStatement: checkNode,
206 DebuggerStatement: checkNode,
207 ReturnStatement: checkNode,
208 ThrowStatement: checkNode,
209 ForStatement(node) {
210 if (node.init) {
211 checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
212 }
213
214 if (node.test) {
215 checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node);
216 }
217 }
218 };
219 }
220};