2 * @fileoverview This rule should require or disallow spaces before or after unary operations.
3 * @author Marcin Kumorek
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const astUtils = require("./utils/ast-utils");
13 //------------------------------------------------------------------------------
15 //------------------------------------------------------------------------------
22 description: "enforce consistent spacing before or after unary operators",
23 category: "Stylistic Issues",
25 url: "https://eslint.org/docs/rules/space-unary-ops"
28 fixable: "whitespace",
44 additionalProperties: {
49 additionalProperties: false
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}}'."
63 const options = context.options[0] || { words: true, nonwords: false };
65 const sourceCode = context.getSourceCode();
67 //--------------------------------------------------------------------------
69 //--------------------------------------------------------------------------
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 "!!"
76 function isFirstBangInBangBangExpression(node) {
77 return node && node.type === "UnaryExpression" && node.argument.operator === "!" &&
78 node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!";
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
86 function overrideExistsForOperator(operator) {
87 return options.overrides && Object.prototype.hasOwnProperty.call(options.overrides, operator);
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
95 function overrideEnforcesSpaces(operator) {
96 return options.overrides[operator];
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
107 function verifyWordHasSpaces(node, firstToken, secondToken, word) {
108 if (secondToken.range[0] === firstToken.range[1]) {
111 messageId: "wordOperator",
116 return fixer.insertTextAfter(firstToken, " ");
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
130 function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) {
131 if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) {
132 if (secondToken.range[0] > firstToken.range[1]) {
135 messageId: "unexpectedAfterWord",
140 return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
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
155 function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) {
156 if (overrideExistsForOperator(word)) {
157 if (overrideEnforcesSpaces(word)) {
158 verifyWordHasSpaces(node, firstToken, secondToken, word);
160 verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
162 } else if (options.words) {
163 verifyWordHasSpaces(node, firstToken, secondToken, word);
165 verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
170 * Verifies YieldExpressions satisfy spacing requirements
171 * @param {ASTnode} node AST node
174 function checkForSpacesAfterYield(node) {
175 const tokens = sourceCode.getFirstTokens(node, 3),
178 if (!node.argument || node.delegate) {
182 checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word);
186 * Verifies AwaitExpressions satisfy spacing requirements
187 * @param {ASTNode} node AwaitExpression AST node
190 function checkForSpacesAfterAwait(node) {
191 const tokens = sourceCode.getFirstTokens(node, 3);
193 checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], "await");
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
203 function verifyNonWordsHaveSpaces(node, firstToken, secondToken) {
205 if (isFirstBangInBangBangExpression(node)) {
208 if (firstToken.range[1] === secondToken.range[0]) {
211 messageId: "operator",
213 operator: firstToken.value
216 return fixer.insertTextAfter(firstToken, " ");
221 if (firstToken.range[1] === secondToken.range[0]) {
224 messageId: "beforeUnaryExpressions",
226 token: secondToken.value
229 return fixer.insertTextBefore(secondToken, " ");
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
243 function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) {
245 if (secondToken.range[0] > firstToken.range[1]) {
248 messageId: "unexpectedAfter",
250 operator: firstToken.value
253 if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) {
254 return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
261 if (secondToken.range[0] > firstToken.range[1]) {
264 messageId: "unexpectedBefore",
266 operator: secondToken.value
269 return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
277 * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements
278 * @param {ASTnode} node AST node
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];
288 if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") {
289 checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, firstToken.value);
293 const operator = node.prefix ? tokens[0].value : tokens[1].value;
295 if (overrideExistsForOperator(operator)) {
296 if (overrideEnforcesSpaces(operator)) {
297 verifyNonWordsHaveSpaces(node, firstToken, secondToken);
299 verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
301 } else if (options.nonwords) {
302 verifyNonWordsHaveSpaces(node, firstToken, secondToken);
304 verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
308 //--------------------------------------------------------------------------
310 //--------------------------------------------------------------------------
313 UnaryExpression: checkForSpaces,
314 UpdateExpression: checkForSpaces,
315 NewExpression: checkForSpaces,
316 YieldExpression: checkForSpacesAfterYield,
317 AwaitExpression: checkForSpacesAfterAwait