Permalink
Cannot retrieve contributors at this time
| /** | |
| * @fileoverview This rule shoud require or disallow spaces before or after unary operations. | |
| * @author Marcin Kumorek | |
| */ | |
| "use strict"; | |
| //------------------------------------------------------------------------------ | |
| // Requirements | |
| //------------------------------------------------------------------------------ | |
| const astUtils = require("./utils/ast-utils"); | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
| module.exports = { | |
| meta: { | |
| type: "layout", | |
| docs: { | |
| description: "enforce consistent spacing before or after unary operators", | |
| category: "Stylistic Issues", | |
| recommended: false, | |
| url: "https://eslint.org/docs/rules/space-unary-ops" | |
| }, | |
| fixable: "whitespace", | |
| schema: [ | |
| { | |
| type: "object", | |
| properties: { | |
| words: { | |
| type: "boolean", | |
| default: true | |
| }, | |
| nonwords: { | |
| type: "boolean", | |
| default: false | |
| }, | |
| overrides: { | |
| type: "object", | |
| additionalProperties: { | |
| type: "boolean" | |
| } | |
| } | |
| }, | |
| additionalProperties: false | |
| } | |
| ], | |
| messages: { | |
| unexpectedBefore: "Unexpected space before unary operator '{{operator}}'.", | |
| unexpectedAfter: "Unexpected space after unary operator '{{operator}}'.", | |
| unexpectedAfterWord: "Unexpected space after unary word operator '{{word}}'.", | |
| wordOperator: "Unary word operator '{{word}}' must be followed by whitespace.", | |
| operator: "Unary operator '{{operator}}' must be followed by whitespace.", | |
| beforeUnaryExpressions: "Space is required before unary expressions '{{token}}'." | |
| } | |
| }, | |
| create(context) { | |
| const options = context.options[0] || { words: true, nonwords: false }; | |
| const sourceCode = context.getSourceCode(); | |
| //-------------------------------------------------------------------------- | |
| // Helpers | |
| //-------------------------------------------------------------------------- | |
| /** | |
| * Check if the node is the first "!" in a "!!" convert to Boolean expression | |
| * @param {ASTnode} node AST node | |
| * @returns {boolean} Whether or not the node is first "!" in "!!" | |
| */ | |
| function isFirstBangInBangBangExpression(node) { | |
| return node && node.type === "UnaryExpression" && node.argument.operator === "!" && | |
| node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!"; | |
| } | |
| /** | |
| * Checks if an override exists for a given operator. | |
| * @param {string} operator Operator | |
| * @returns {boolean} Whether or not an override has been provided for the operator | |
| */ | |
| function overrideExistsForOperator(operator) { | |
| return options.overrides && Object.prototype.hasOwnProperty.call(options.overrides, operator); | |
| } | |
| /** | |
| * Gets the value that the override was set to for this operator | |
| * @param {string} operator Operator | |
| * @returns {boolean} Whether or not an override enforces a space with this operator | |
| */ | |
| function overrideEnforcesSpaces(operator) { | |
| return options.overrides[operator]; | |
| } | |
| /** | |
| * Verify Unary Word Operator has spaces after the word operator | |
| * @param {ASTnode} node AST node | |
| * @param {Object} firstToken first token from the AST node | |
| * @param {Object} secondToken second token from the AST node | |
| * @param {string} word The word to be used for reporting | |
| * @returns {void} | |
| */ | |
| function verifyWordHasSpaces(node, firstToken, secondToken, word) { | |
| if (secondToken.range[0] === firstToken.range[1]) { | |
| context.report({ | |
| node, | |
| messageId: "wordOperator", | |
| data: { | |
| word | |
| }, | |
| fix(fixer) { | |
| return fixer.insertTextAfter(firstToken, " "); | |
| } | |
| }); | |
| } | |
| } | |
| /** | |
| * Verify Unary Word Operator doesn't have spaces after the word operator | |
| * @param {ASTnode} node AST node | |
| * @param {Object} firstToken first token from the AST node | |
| * @param {Object} secondToken second token from the AST node | |
| * @param {string} word The word to be used for reporting | |
| * @returns {void} | |
| */ | |
| function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) { | |
| if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) { | |
| if (secondToken.range[0] > firstToken.range[1]) { | |
| context.report({ | |
| node, | |
| messageId: "unexpectedAfterWord", | |
| data: { | |
| word | |
| }, | |
| fix(fixer) { | |
| return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| /** | |
| * Check Unary Word Operators for spaces after the word operator | |
| * @param {ASTnode} node AST node | |
| * @param {Object} firstToken first token from the AST node | |
| * @param {Object} secondToken second token from the AST node | |
| * @param {string} word The word to be used for reporting | |
| * @returns {void} | |
| */ | |
| function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) { | |
| if (overrideExistsForOperator(word)) { | |
| if (overrideEnforcesSpaces(word)) { | |
| verifyWordHasSpaces(node, firstToken, secondToken, word); | |
| } else { | |
| verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word); | |
| } | |
| } else if (options.words) { | |
| verifyWordHasSpaces(node, firstToken, secondToken, word); | |
| } else { | |
| verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word); | |
| } | |
| } | |
| /** | |
| * Verifies YieldExpressions satisfy spacing requirements | |
| * @param {ASTnode} node AST node | |
| * @returns {void} | |
| */ | |
| function checkForSpacesAfterYield(node) { | |
| const tokens = sourceCode.getFirstTokens(node, 3), | |
| word = "yield"; | |
| if (!node.argument || node.delegate) { | |
| return; | |
| } | |
| checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word); | |
| } | |
| /** | |
| * Verifies AwaitExpressions satisfy spacing requirements | |
| * @param {ASTNode} node AwaitExpression AST node | |
| * @returns {void} | |
| */ | |
| function checkForSpacesAfterAwait(node) { | |
| const tokens = sourceCode.getFirstTokens(node, 3); | |
| checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], "await"); | |
| } | |
| /** | |
| * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator | |
| * @param {ASTnode} node AST node | |
| * @param {Object} firstToken First token in the expression | |
| * @param {Object} secondToken Second token in the expression | |
| * @returns {void} | |
| */ | |
| function verifyNonWordsHaveSpaces(node, firstToken, secondToken) { | |
| if (node.prefix) { | |
| if (isFirstBangInBangBangExpression(node)) { | |
| return; | |
| } | |
| if (firstToken.range[1] === secondToken.range[0]) { | |
| context.report({ | |
| node, | |
| messageId: "operator", | |
| data: { | |
| operator: firstToken.value | |
| }, | |
| fix(fixer) { | |
| return fixer.insertTextAfter(firstToken, " "); | |
| } | |
| }); | |
| } | |
| } else { | |
| if (firstToken.range[1] === secondToken.range[0]) { | |
| context.report({ | |
| node, | |
| messageId: "beforeUnaryExpressions", | |
| data: { | |
| token: secondToken.value | |
| }, | |
| fix(fixer) { | |
| return fixer.insertTextBefore(secondToken, " "); | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| /** | |
| * Verifies UnaryExpression, UpdateExpression and NewExpression don't have spaces before or after the operator | |
| * @param {ASTnode} node AST node | |
| * @param {Object} firstToken First token in the expression | |
| * @param {Object} secondToken Second token in the expression | |
| * @returns {void} | |
| */ | |
| function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) { | |
| if (node.prefix) { | |
| if (secondToken.range[0] > firstToken.range[1]) { | |
| context.report({ | |
| node, | |
| messageId: "unexpectedAfter", | |
| data: { | |
| operator: firstToken.value | |
| }, | |
| fix(fixer) { | |
| if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) { | |
| return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); | |
| } | |
| return null; | |
| } | |
| }); | |
| } | |
| } else { | |
| if (secondToken.range[0] > firstToken.range[1]) { | |
| context.report({ | |
| node, | |
| messageId: "unexpectedBefore", | |
| data: { | |
| operator: secondToken.value | |
| }, | |
| fix(fixer) { | |
| return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| /** | |
| * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements | |
| * @param {ASTnode} node AST node | |
| * @returns {void} | |
| */ | |
| function checkForSpaces(node) { | |
| const tokens = node.type === "UpdateExpression" && !node.prefix | |
| ? sourceCode.getLastTokens(node, 2) | |
| : sourceCode.getFirstTokens(node, 2); | |
| const firstToken = tokens[0]; | |
| const secondToken = tokens[1]; | |
| if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") { | |
| checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, firstToken.value); | |
| return; | |
| } | |
| const operator = node.prefix ? tokens[0].value : tokens[1].value; | |
| if (overrideExistsForOperator(operator)) { | |
| if (overrideEnforcesSpaces(operator)) { | |
| verifyNonWordsHaveSpaces(node, firstToken, secondToken); | |
| } else { | |
| verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); | |
| } | |
| } else if (options.nonwords) { | |
| verifyNonWordsHaveSpaces(node, firstToken, secondToken); | |
| } else { | |
| verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); | |
| } | |
| } | |
| //-------------------------------------------------------------------------- | |
| // Public | |
| //-------------------------------------------------------------------------- | |
| return { | |
| UnaryExpression: checkForSpaces, | |
| UpdateExpression: checkForSpaces, | |
| NewExpression: checkForSpaces, | |
| YieldExpression: checkForSpacesAfterYield, | |
| AwaitExpression: checkForSpacesAfterAwait | |
| }; | |
| } | |
| }; |