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 if (overrideExistsForOperator(word)) {
145 if (overrideEnforcesSpaces(word)) {
146 verifyWordHasSpaces(node, firstToken, secondToken, word);
147 } else {
148 verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
149 }
150 } else if (options.words) {
151 verifyWordHasSpaces(node, firstToken, secondToken, word);
152 } else {
153 verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
154 }
155 }
156
157 /**
158 * Verifies YieldExpressions satisfy spacing requirements
159 * @param {ASTnode} node AST node
160 * @returns {void}
161 */
162 function checkForSpacesAfterYield(node) {
163 const tokens = sourceCode.getFirstTokens(node, 3),
164 word = "yield";
165
166 if (!node.argument || node.delegate) {
167 return;
168 }
169
170 checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word);
171 }
172
173 /**
174 * Verifies AwaitExpressions satisfy spacing requirements
175 * @param {ASTNode} node AwaitExpression AST node
176 * @returns {void}
177 */
178 function checkForSpacesAfterAwait(node) {
179 const tokens = sourceCode.getFirstTokens(node, 3);
180
181 checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], "await");
182 }
183
184 /**
185 * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator
186 * @param {ASTnode} node AST node
187 * @param {Object} firstToken First token in the expression
188 * @param {Object} secondToken Second token in the expression
189 * @returns {void}
190 */
191 function verifyNonWordsHaveSpaces(node, firstToken, secondToken) {
192 if (node.prefix) {
193 if (isFirstBangInBangBangExpression(node)) {
194 return;
195 }
196 if (firstToken.range[1] === secondToken.range[0]) {
197 context.report({
198 node,
199 message: "Unary operator '{{operator}}' must be followed by whitespace.",
200 data: {
201 operator: firstToken.value
202 },
203 fix(fixer) {
204 return fixer.insertTextAfter(firstToken, " ");
205 }
206 });
207 }
208 } else {
209 if (firstToken.range[1] === secondToken.range[0]) {
210 context.report({
211 node,
212 message: "Space is required before unary expressions '{{token}}'.",
213 data: {
214 token: secondToken.value
215 },
216 fix(fixer) {
217 return fixer.insertTextBefore(secondToken, " ");
218 }
219 });
220 }
221 }
222 }
223
224 /**
225 * Verifies UnaryExpression, UpdateExpression and NewExpression don't have spaces before or after the operator
226 * @param {ASTnode} node AST node
227 * @param {Object} firstToken First token in the expression
228 * @param {Object} secondToken Second token in the expression
229 * @returns {void}
230 */
231 function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) {
232 if (node.prefix) {
233 if (secondToken.range[0] > firstToken.range[1]) {
234 context.report({
235 node,
236 message: "Unexpected space after unary operator '{{operator}}'.",
237 data: {
238 operator: firstToken.value
239 },
240 fix(fixer) {
241 if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) {
242 return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
243 }
244 return null;
245 }
246 });
247 }
248 } else {
249 if (secondToken.range[0] > firstToken.range[1]) {
250 context.report({
251 node,
252 message: "Unexpected space before unary operator '{{operator}}'.",
253 data: {
254 operator: secondToken.value
255 },
256 fix(fixer) {
257 return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
258 }
259 });
260 }
261 }
262 }
263
264 /**
265 * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements
266 * @param {ASTnode} node AST node
267 * @returns {void}
268 */
269 function checkForSpaces(node) {
270 const tokens = node.type === "UpdateExpression" && !node.prefix
271 ? sourceCode.getLastTokens(node, 2)
272 : sourceCode.getFirstTokens(node, 2);
273 const firstToken = tokens[0];
274 const secondToken = tokens[1];
275
276 if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") {
277 checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, firstToken.value);
278 return;
279 }
280
281 const operator = node.prefix ? tokens[0].value : tokens[1].value;
282
283 if (overrideExistsForOperator(operator)) {
284 if (overrideEnforcesSpaces(operator)) {
285 verifyNonWordsHaveSpaces(node, firstToken, secondToken);
286 } else {
287 verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
288 }
289 } else if (options.nonwords) {
290 verifyNonWordsHaveSpaces(node, firstToken, secondToken);
291 } else {
292 verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
293 }
294 }
295
296 //--------------------------------------------------------------------------
297 // Public
298 //--------------------------------------------------------------------------
299
300 return {
301 UnaryExpression: checkForSpaces,
302 UpdateExpression: checkForSpaces,
303 NewExpression: checkForSpaces,
304 YieldExpression: checkForSpacesAfterYield,
305 AwaitExpression: checkForSpacesAfterAwait
306 };
307
308 }
309};