| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 | /** * @fileoverview Disallows or enforces spaces inside of parentheses. * @author Jonathan Rajavuori */"use strict";const astUtils = require("../ast-utils");//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------module.exports = {    meta: {        docs: {            description: "enforce consistent spacing inside parentheses",            category: "Stylistic Issues",            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            }        ]    },    create(context) {        const MISSING_SPACE_MESSAGE = "There must be a space inside this paren.",            REJECTED_SPACE_MESSAGE = "There should be no spaces inside this paren.",            ALWAYS = context.options[0] === "always",            exceptionsArrayOptions = (context.options[1] && context.options[1].exceptions) || [],            options = {};        let exceptions;        if (exceptionsArrayOptions.length) {            options.braceException = exceptionsArrayOptions.indexOf("{}") !== -1;            options.bracketException = exceptionsArrayOptions.indexOf("[]") !== -1;            options.parenException = exceptionsArrayOptions.indexOf("()") !== -1;            options.empty = exceptionsArrayOptions.indexOf("empty") !== -1;        }        /**         * Produces an object with the opener and closer exception values         * @param {Object} opts The exception options         * @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 token.type === "Punctuator" && exceptions.openers.indexOf(token.value) >= 0;        }        /**         * 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 token.type === "Punctuator" && exceptions.closers.indexOf(token.value) >= 0;        }        /**         * Determines if an opener paren should have a missing space after it         * @param {Object} left The paren token         * @param {Object} right The token after it         * @returns {boolean} True if the paren should have a space         */        function shouldOpenerHaveSpace(left, right) {            if (sourceCode.isSpaceBetweenTokens(left, right)) {                return false;            }            if (ALWAYS) {                if (astUtils.isClosingParenToken(right)) {                    return false;                }                return !isOpenerException(right);            }            return isOpenerException(right);        }        /**         * Determines if an closer paren should have a missing space after it         * @param {Object} left The token before the paren         * @param {Object} right The paren token         * @returns {boolean} True if the paren should have a space         */        function shouldCloserHaveSpace(left, right) {            if (astUtils.isOpeningParenToken(left)) {                return false;            }            if (sourceCode.isSpaceBetweenTokens(left, right)) {                return false;            }            if (ALWAYS) {                return !isCloserException(left);            }            return isCloserException(left);        }        /**         * Determines if an opener paren should not have an existing space after it         * @param {Object} left The paren token         * @param {Object} right The token after it         * @returns {boolean} True if the paren should reject the space         */        function shouldOpenerRejectSpace(left, right) {            if (right.type === "Line") {                return false;            }            if (!astUtils.isTokenOnSameLine(left, right)) {                return false;            }            if (!sourceCode.isSpaceBetweenTokens(left, right)) {                return false;            }            if (ALWAYS) {                return isOpenerException(right);            }            return !isOpenerException(right);        }        /**         * Determines if an closer paren should not have an existing space after it         * @param {Object} left The token before the paren         * @param {Object} right The paren token         * @returns {boolean} True if the paren should reject the space         */        function shouldCloserRejectSpace(left, right) {            if (astUtils.isOpeningParenToken(left)) {                return false;            }            if (!astUtils.isTokenOnSameLine(left, right)) {                return false;            }            if (!sourceCode.isSpaceBetweenTokens(left, right)) {                return false;            }            if (ALWAYS) {                return isCloserException(left);            }            return !isCloserException(left);        }        //--------------------------------------------------------------------------        // 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 (!astUtils.isOpeningParenToken(token) && !astUtils.isClosingParenToken(token)) {                        return;                    }                    if (token.value === "(" && shouldOpenerHaveSpace(token, nextToken)) {                        context.report({                            node,                            loc: token.loc.start,                            message: MISSING_SPACE_MESSAGE,                            fix(fixer) {                                return fixer.insertTextAfter(token, " ");                            }                        });                    } else if (token.value === "(" && shouldOpenerRejectSpace(token, nextToken)) {                        context.report({                            node,                            loc: token.loc.start,                            message: REJECTED_SPACE_MESSAGE,                            fix(fixer) {                                return fixer.removeRange([token.range[1], nextToken.range[0]]);                            }                        });                    } else if (token.value === ")" && shouldCloserHaveSpace(prevToken, token)) {                        // context.report(node, token.loc.start, MISSING_SPACE_MESSAGE);                        context.report({                            node,                            loc: token.loc.start,                            message: MISSING_SPACE_MESSAGE,                            fix(fixer) {                                return fixer.insertTextBefore(token, " ");                            }                        });                    } else if (token.value === ")" && shouldCloserRejectSpace(prevToken, token)) {                        context.report({                            node,                            loc: token.loc.start,                            message: REJECTED_SPACE_MESSAGE,                            fix(fixer) {                                return fixer.removeRange([prevToken.range[1], token.range[0]]);                            }                        });                    }                });            }        };    }};
 |