| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 | /** * @fileoverview Disallows or enforces spaces inside of parentheses. * @author Jonathan Rajavuori */"use strict";const astUtils = require("./utils/ast-utils");//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "layout",        docs: {            description: "Enforce consistent spacing inside parentheses",            recommended: false,            url: "https://eslint.org/docs/rules/space-in-parens"        },        fixable: "whitespace",        schema: [            {                enum: ["always", "never"]            },            {                type: "object",                properties: {                    exceptions: {                        type: "array",                        items: {                            enum: ["{}", "[]", "()", "empty"]                        },                        uniqueItems: true                    }                },                additionalProperties: false            }        ],        messages: {            missingOpeningSpace: "There must be a space after this paren.",            missingClosingSpace: "There must be a space before this paren.",            rejectedOpeningSpace: "There should be no space after this paren.",            rejectedClosingSpace: "There should be no space before this paren."        }    },    create(context) {        const ALWAYS = context.options[0] === "always",            exceptionsArrayOptions = (context.options[1] && context.options[1].exceptions) || [],            options = {};        let exceptions;        if (exceptionsArrayOptions.length) {            options.braceException = exceptionsArrayOptions.includes("{}");            options.bracketException = exceptionsArrayOptions.includes("[]");            options.parenException = exceptionsArrayOptions.includes("()");            options.empty = exceptionsArrayOptions.includes("empty");        }        /**         * Produces an object with the opener and closer exception values         * @returns {Object} `openers` and `closers` exception values         * @private         */        function getExceptions() {            const openers = [],                closers = [];            if (options.braceException) {                openers.push("{");                closers.push("}");            }            if (options.bracketException) {                openers.push("[");                closers.push("]");            }            if (options.parenException) {                openers.push("(");                closers.push(")");            }            if (options.empty) {                openers.push(")");                closers.push("(");            }            return {                openers,                closers            };        }        //--------------------------------------------------------------------------        // Helpers        //--------------------------------------------------------------------------        const sourceCode = context.getSourceCode();        /**         * Determines if a token is one of the exceptions for the opener paren         * @param {Object} token The token to check         * @returns {boolean} True if the token is one of the exceptions for the opener paren         */        function isOpenerException(token) {            return exceptions.openers.includes(token.value);        }        /**         * Determines if a token is one of the exceptions for the closer paren         * @param {Object} token The token to check         * @returns {boolean} True if the token is one of the exceptions for the closer paren         */        function isCloserException(token) {            return exceptions.closers.includes(token.value);        }        /**         * Determines if an opening paren is immediately followed by a required space         * @param {Object} openingParenToken The paren token         * @param {Object} tokenAfterOpeningParen The token after it         * @returns {boolean} True if the opening paren is missing a required space         */        function openerMissingSpace(openingParenToken, tokenAfterOpeningParen) {            if (sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) {                return false;            }            if (!options.empty && astUtils.isClosingParenToken(tokenAfterOpeningParen)) {                return false;            }            if (ALWAYS) {                return !isOpenerException(tokenAfterOpeningParen);            }            return isOpenerException(tokenAfterOpeningParen);        }        /**         * Determines if an opening paren is immediately followed by a disallowed space         * @param {Object} openingParenToken The paren token         * @param {Object} tokenAfterOpeningParen The token after it         * @returns {boolean} True if the opening paren has a disallowed space         */        function openerRejectsSpace(openingParenToken, tokenAfterOpeningParen) {            if (!astUtils.isTokenOnSameLine(openingParenToken, tokenAfterOpeningParen)) {                return false;            }            if (tokenAfterOpeningParen.type === "Line") {                return false;            }            if (!sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) {                return false;            }            if (ALWAYS) {                return isOpenerException(tokenAfterOpeningParen);            }            return !isOpenerException(tokenAfterOpeningParen);        }        /**         * Determines if a closing paren is immediately preceded by a required space         * @param {Object} tokenBeforeClosingParen The token before the paren         * @param {Object} closingParenToken The paren token         * @returns {boolean} True if the closing paren is missing a required space         */        function closerMissingSpace(tokenBeforeClosingParen, closingParenToken) {            if (sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) {                return false;            }            if (!options.empty && astUtils.isOpeningParenToken(tokenBeforeClosingParen)) {                return false;            }            if (ALWAYS) {                return !isCloserException(tokenBeforeClosingParen);            }            return isCloserException(tokenBeforeClosingParen);        }        /**         * Determines if a closer paren is immediately preceded by a disallowed space         * @param {Object} tokenBeforeClosingParen The token before the paren         * @param {Object} closingParenToken The paren token         * @returns {boolean} True if the closing paren has a disallowed space         */        function closerRejectsSpace(tokenBeforeClosingParen, closingParenToken) {            if (!astUtils.isTokenOnSameLine(tokenBeforeClosingParen, closingParenToken)) {                return false;            }            if (!sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) {                return false;            }            if (ALWAYS) {                return isCloserException(tokenBeforeClosingParen);            }            return !isCloserException(tokenBeforeClosingParen);        }        //--------------------------------------------------------------------------        // Public        //--------------------------------------------------------------------------        return {            Program: function checkParenSpaces(node) {                exceptions = getExceptions();                const tokens = sourceCode.tokensAndComments;                tokens.forEach((token, i) => {                    const prevToken = tokens[i - 1];                    const nextToken = tokens[i + 1];                    // if token is not an opening or closing paren token, do nothing                    if (!astUtils.isOpeningParenToken(token) && !astUtils.isClosingParenToken(token)) {                        return;                    }                    // if token is an opening paren and is not followed by a required space                    if (token.value === "(" && openerMissingSpace(token, nextToken)) {                        context.report({                            node,                            loc: token.loc,                            messageId: "missingOpeningSpace",                            fix(fixer) {                                return fixer.insertTextAfter(token, " ");                            }                        });                    }                    // if token is an opening paren and is followed by a disallowed space                    if (token.value === "(" && openerRejectsSpace(token, nextToken)) {                        context.report({                            node,                            loc: { start: token.loc.end, end: nextToken.loc.start },                            messageId: "rejectedOpeningSpace",                            fix(fixer) {                                return fixer.removeRange([token.range[1], nextToken.range[0]]);                            }                        });                    }                    // if token is a closing paren and is not preceded by a required space                    if (token.value === ")" && closerMissingSpace(prevToken, token)) {                        context.report({                            node,                            loc: token.loc,                            messageId: "missingClosingSpace",                            fix(fixer) {                                return fixer.insertTextBefore(token, " ");                            }                        });                    }                    // if token is a closing paren and is preceded by a disallowed space                    if (token.value === ")" && closerRejectsSpace(prevToken, token)) {                        context.report({                            node,                            loc: { start: prevToken.loc.end, end: token.loc.start },                            messageId: "rejectedClosingSpace",                            fix(fixer) {                                return fixer.removeRange([prevToken.range[1], token.range[0]]);                            }                        });                    }                });            }        };    }};
 |