UNPKG

12.6 kBJavaScriptView Raw
1/**
2 * @fileoverview This rule shoud require or disallow spaces before or after unary operations.
3 * @author Marcin Kumorek
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11const astUtils = require("../ast-utils");
12
13//------------------------------------------------------------------------------
14// Rule Definition
15//------------------------------------------------------------------------------
16
17module.exports = {
18 meta: {
19 docs: {
20 description: "enforce consistent spacing before or after unary operators",
21 category: "Stylistic Issues",
22 recommended: false
23 },
24
25 fixable: "whitespace",
26
27 schema: [
28 {
29 type: "object",
30 properties: {
31 words: {
32 type: "boolean"
33 },
34 nonwords: {
35 type: "boolean"
36 },
37 overrides: {
38 type: "object",
39 additionalProperties: {
40 type: "boolean"
41 }
42 }
43 },
44 additionalProperties: false
45 }
46 ]
47 },
48
49 create(context) {
50 const options = context.options && Array.isArray(context.options) && context.options[0] || { words: true, nonwords: false };
51
52 const sourceCode = context.getSourceCode();
53
54 //--------------------------------------------------------------------------
55 // Helpers
56 //--------------------------------------------------------------------------
57
58 /**
59 * Check if the node is the first "!" in a "!!" convert to Boolean expression
60 * @param {ASTnode} node AST node
61 * @returns {boolean} Whether or not the node is first "!" in "!!"
62 */
63 function isFirstBangInBangBangExpression(node) {
64 return node && node.type === "UnaryExpression" && node.argument.operator === "!" &&
65 node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!";
66 }
67
68 /**
69 * Check if the node's child argument is an "ObjectExpression"
70 * @param {ASTnode} node AST node
71 * @returns {boolean} Whether or not the argument's type is "ObjectExpression"
72 */
73 function isArgumentObjectExpression(node) {
74 return node.argument && node.argument.type && node.argument.type === "ObjectExpression";
75 }
76
77 /**
78 * Checks if an override exists for a given operator.
79 * @param {ASTnode} node AST node
80 * @param {string} operator Operator
81 * @returns {boolean} Whether or not an override has been provided for the operator
82 */
83 function overrideExistsForOperator(node, operator) {
84 return options.overrides && options.overrides.hasOwnProperty(operator);
85 }
86
87 /**
88 * Gets the value that the override was set to for this operator
89 * @param {ASTnode} node AST node
90 * @param {string} operator Operator
91 * @returns {boolean} Whether or not an override enforces a space with this operator
92 */
93 function overrideEnforcesSpaces(node, operator) {
94 return options.overrides[operator];
95 }
96
97 /**
98 * Verify Unary Word Operator has spaces after the word operator
99 * @param {ASTnode} node AST node
100 * @param {Object} firstToken first token from the AST node
101 * @param {Object} secondToken second token from the AST node
102 * @param {string} word The word to be used for reporting
103 * @returns {void}
104 */
105 function verifyWordHasSpaces(node, firstToken, secondToken, word) {
106 if (secondToken.range[0] === firstToken.range[1]) {
107 context.report({
108 node,
109 message: "Unary word operator '{{word}}' must be followed by whitespace.",
110 data: {
111 word
112 },
113 fix(fixer) {
114 return fixer.insertTextAfter(firstToken, " ");
115 }
116 });
117 }
118 }
119
120 /**
121 * Verify Unary Word Operator doesn't have spaces after the word operator
122 * @param {ASTnode} node AST node
123 * @param {Object} firstToken first token from the AST node
124 * @param {Object} secondToken second token from the AST node
125 * @param {string} word The word to be used for reporting
126 * @returns {void}
127 */
128 function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) {
129 if (isArgumentObjectExpression(node)) {
130 if (secondToken.range[0] > firstToken.range[1]) {
131 context.report({
132 node,
133 message: "Unexpected space after unary word operator '{{word}}'.",
134 data: {
135 word
136 },
137 fix(fixer) {
138 return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
139 }
140 });
141 }
142 }
143 }
144
145 /**
146 * Check Unary Word Operators for spaces after the word operator
147 * @param {ASTnode} node AST node
148 * @param {Object} firstToken first token from the AST node
149 * @param {Object} secondToken second token from the AST node
150 * @param {string} word The word to be used for reporting
151 * @returns {void}
152 */
153 function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) {
154 word = word || firstToken.value;
155
156 if (overrideExistsForOperator(node, word)) {
157 if (overrideEnforcesSpaces(node, word)) {
158 verifyWordHasSpaces(node, firstToken, secondToken, word);
159 } else {
160 verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
161 }
162 } else if (options.words) {
163 verifyWordHasSpaces(node, firstToken, secondToken, word);
164 } else {
165 verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
166 }
167 }
168
169 /**
170 * Verifies YieldExpressions satisfy spacing requirements
171 * @param {ASTnode} node AST node
172 * @returns {void}
173 */
174 function checkForSpacesAfterYield(node) {
175 const tokens = sourceCode.getFirstTokens(node, 3),
176 word = "yield";
177
178 if (!node.argument || node.delegate) {
179 return;
180 }
181
182 checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word);
183 }
184
185 /**
186 * Verifies AwaitExpressions satisfy spacing requirements
187 * @param {ASTNode} node AwaitExpression AST node
188 * @returns {void}
189 */
190 function checkForSpacesAfterAwait(node) {
191 const tokens = sourceCode.getFirstTokens(node, 3);
192
193 checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], "await");
194 }
195
196 /**
197 * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator
198 * @param {ASTnode} node AST node
199 * @param {Object} firstToken First token in the expression
200 * @param {Object} secondToken Second token in the expression
201 * @returns {void}
202 */
203 function verifyNonWordsHaveSpaces(node, firstToken, secondToken) {
204 if (node.prefix) {
205 if (isFirstBangInBangBangExpression(node)) {
206 return;
207 }
208 if (firstToken.range[1] === secondToken.range[0]) {
209 context.report({
210 node,
211 message: "Unary operator '{{operator}}' must be followed by whitespace.",
212 data: {
213 operator: firstToken.value
214 },
215 fix(fixer) {
216 return fixer.insertTextAfter(firstToken, " ");
217 }
218 });
219 }
220 } else {
221 if (firstToken.range[1] === secondToken.range[0]) {
222 context.report({
223 node,
224 message: "Space is required before unary expressions '{{token}}'.",
225 data: {
226 token: secondToken.value
227 },
228 fix(fixer) {
229 return fixer.insertTextBefore(secondToken, " ");
230 }
231 });
232 }
233 }
234 }
235
236 /**
237 * Verifies UnaryExpression, UpdateExpression and NewExpression don't have spaces before or after the operator
238 * @param {ASTnode} node AST node
239 * @param {Object} firstToken First token in the expression
240 * @param {Object} secondToken Second token in the expression
241 * @returns {void}
242 */
243 function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) {
244 if (node.prefix) {
245 if (secondToken.range[0] > firstToken.range[1]) {
246 context.report({
247 node,
248 message: "Unexpected space after unary operator '{{operator}}'.",
249 data: {
250 operator: firstToken.value
251 },
252 fix(fixer) {
253 if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) {
254 return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
255 }
256 return null;
257 }
258 });
259 }
260 } else {
261 if (secondToken.range[0] > firstToken.range[1]) {
262 context.report({
263 node,
264 message: "Unexpected space before unary operator '{{operator}}'.",
265 data: {
266 operator: secondToken.value
267 },
268 fix(fixer) {
269 return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
270 }
271 });
272 }
273 }
274 }
275
276 /**
277 * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements
278 * @param {ASTnode} node AST node
279 * @returns {void}
280 */
281 function checkForSpaces(node) {
282 const tokens = node.type === "UpdateExpression" && !node.prefix
283 ? sourceCode.getLastTokens(node, 2)
284 : sourceCode.getFirstTokens(node, 2);
285 const firstToken = tokens[0];
286 const secondToken = tokens[1];
287
288 if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") {
289 checkUnaryWordOperatorForSpaces(node, firstToken, secondToken);
290 return;
291 }
292
293 const operator = node.prefix ? tokens[0].value : tokens[1].value;
294
295 if (overrideExistsForOperator(node, operator)) {
296 if (overrideEnforcesSpaces(node, operator)) {
297 verifyNonWordsHaveSpaces(node, firstToken, secondToken);
298 } else {
299 verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
300 }
301 } else if (options.nonwords) {
302 verifyNonWordsHaveSpaces(node, firstToken, secondToken);
303 } else {
304 verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
305 }
306 }
307
308 //--------------------------------------------------------------------------
309 // Public
310 //--------------------------------------------------------------------------
311
312 return {
313 UnaryExpression: checkForSpaces,
314 UpdateExpression: checkForSpaces,
315 NewExpression: checkForSpaces,
316 YieldExpression: checkForSpacesAfterYield,
317 AwaitExpression: checkForSpacesAfterAwait
318 };
319
320 }
321};