UNPKG

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