UNPKG

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