]>
Commit | Line | Data |
---|---|---|
eb39fafa | 1 | /** |
456be15e | 2 | * @fileoverview This rule should require or disallow spaces before or after unary operations. |
eb39fafa DC |
3 | * @author Marcin Kumorek |
4 | */ | |
5 | "use strict"; | |
6 | ||
7 | //------------------------------------------------------------------------------ | |
8 | // Requirements | |
9 | //------------------------------------------------------------------------------ | |
10 | ||
11 | const astUtils = require("./utils/ast-utils"); | |
12 | ||
13 | //------------------------------------------------------------------------------ | |
14 | // Rule Definition | |
15 | //------------------------------------------------------------------------------ | |
16 | ||
17 | module.exports = { | |
18 | meta: { | |
19 | type: "layout", | |
20 | ||
21 | docs: { | |
22 | description: "enforce consistent spacing before or after unary operators", | |
23 | category: "Stylistic Issues", | |
24 | recommended: false, | |
25 | url: "https://eslint.org/docs/rules/space-unary-ops" | |
26 | }, | |
27 | ||
28 | fixable: "whitespace", | |
29 | ||
30 | schema: [ | |
31 | { | |
32 | type: "object", | |
33 | properties: { | |
34 | words: { | |
35 | type: "boolean", | |
36 | default: true | |
37 | }, | |
38 | nonwords: { | |
39 | type: "boolean", | |
40 | default: false | |
41 | }, | |
42 | overrides: { | |
43 | type: "object", | |
44 | additionalProperties: { | |
45 | type: "boolean" | |
46 | } | |
47 | } | |
48 | }, | |
49 | additionalProperties: false | |
50 | } | |
51 | ], | |
52 | messages: { | |
53 | unexpectedBefore: "Unexpected space before unary operator '{{operator}}'.", | |
54 | unexpectedAfter: "Unexpected space after unary operator '{{operator}}'.", | |
55 | unexpectedAfterWord: "Unexpected space after unary word operator '{{word}}'.", | |
56 | wordOperator: "Unary word operator '{{word}}' must be followed by whitespace.", | |
57 | operator: "Unary operator '{{operator}}' must be followed by whitespace.", | |
58 | beforeUnaryExpressions: "Space is required before unary expressions '{{token}}'." | |
59 | } | |
60 | }, | |
61 | ||
62 | create(context) { | |
63 | const options = context.options[0] || { words: true, nonwords: false }; | |
64 | ||
65 | const sourceCode = context.getSourceCode(); | |
66 | ||
67 | //-------------------------------------------------------------------------- | |
68 | // Helpers | |
69 | //-------------------------------------------------------------------------- | |
70 | ||
71 | /** | |
72 | * Check if the node is the first "!" in a "!!" convert to Boolean expression | |
73 | * @param {ASTnode} node AST node | |
74 | * @returns {boolean} Whether or not the node is first "!" in "!!" | |
75 | */ | |
76 | function isFirstBangInBangBangExpression(node) { | |
77 | return node && node.type === "UnaryExpression" && node.argument.operator === "!" && | |
78 | node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!"; | |
79 | } | |
80 | ||
81 | /** | |
82 | * Checks if an override exists for a given operator. | |
83 | * @param {string} operator Operator | |
84 | * @returns {boolean} Whether or not an override has been provided for the operator | |
85 | */ | |
86 | function overrideExistsForOperator(operator) { | |
87 | return options.overrides && Object.prototype.hasOwnProperty.call(options.overrides, operator); | |
88 | } | |
89 | ||
90 | /** | |
91 | * Gets the value that the override was set to for this operator | |
92 | * @param {string} operator Operator | |
93 | * @returns {boolean} Whether or not an override enforces a space with this operator | |
94 | */ | |
95 | function overrideEnforcesSpaces(operator) { | |
96 | return options.overrides[operator]; | |
97 | } | |
98 | ||
99 | /** | |
100 | * Verify Unary Word Operator has spaces after the word operator | |
101 | * @param {ASTnode} node AST node | |
102 | * @param {Object} firstToken first token from the AST node | |
103 | * @param {Object} secondToken second token from the AST node | |
104 | * @param {string} word The word to be used for reporting | |
105 | * @returns {void} | |
106 | */ | |
107 | function verifyWordHasSpaces(node, firstToken, secondToken, word) { | |
108 | if (secondToken.range[0] === firstToken.range[1]) { | |
109 | context.report({ | |
110 | node, | |
111 | messageId: "wordOperator", | |
112 | data: { | |
113 | word | |
114 | }, | |
115 | fix(fixer) { | |
116 | return fixer.insertTextAfter(firstToken, " "); | |
117 | } | |
118 | }); | |
119 | } | |
120 | } | |
121 | ||
122 | /** | |
123 | * Verify Unary Word Operator doesn't have spaces after the word operator | |
124 | * @param {ASTnode} node AST node | |
125 | * @param {Object} firstToken first token from the AST node | |
126 | * @param {Object} secondToken second token from the AST node | |
127 | * @param {string} word The word to be used for reporting | |
128 | * @returns {void} | |
129 | */ | |
130 | function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) { | |
131 | if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) { | |
132 | if (secondToken.range[0] > firstToken.range[1]) { | |
133 | context.report({ | |
134 | node, | |
135 | messageId: "unexpectedAfterWord", | |
136 | data: { | |
137 | word | |
138 | }, | |
139 | fix(fixer) { | |
140 | return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); | |
141 | } | |
142 | }); | |
143 | } | |
144 | } | |
145 | } | |
146 | ||
147 | /** | |
148 | * Check Unary Word Operators for spaces after the word operator | |
149 | * @param {ASTnode} node AST node | |
150 | * @param {Object} firstToken first token from the AST node | |
151 | * @param {Object} secondToken second token from the AST node | |
152 | * @param {string} word The word to be used for reporting | |
153 | * @returns {void} | |
154 | */ | |
155 | function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) { | |
156 | if (overrideExistsForOperator(word)) { | |
157 | if (overrideEnforcesSpaces(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 | messageId: "operator", | |
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 | messageId: "beforeUnaryExpressions", | |
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 | messageId: "unexpectedAfter", | |
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 | messageId: "unexpectedBefore", | |
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, firstToken.value); | |
290 | return; | |
291 | } | |
292 | ||
293 | const operator = node.prefix ? tokens[0].value : tokens[1].value; | |
294 | ||
295 | if (overrideExistsForOperator(operator)) { | |
296 | if (overrideEnforcesSpaces(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 | }; |