2 * @fileoverview Disallows or enforces spaces inside of parentheses.
3 * @author Jonathan Rajavuori
7 const astUtils = require("./utils/ast-utils");
9 //------------------------------------------------------------------------------
11 //------------------------------------------------------------------------------
18 description: "enforce consistent spacing inside parentheses",
19 category: "Stylistic Issues",
21 url: "https://eslint.org/docs/rules/space-in-parens"
24 fixable: "whitespace",
28 enum: ["always", "never"]
36 enum: ["{}", "[]", "()", "empty"]
41 additionalProperties: false
46 missingOpeningSpace: "There must be a space after this paren.",
47 missingClosingSpace: "There must be a space before this paren.",
48 rejectedOpeningSpace: "There should be no space after this paren.",
49 rejectedClosingSpace: "There should be no space before this paren."
54 const ALWAYS = context.options[0] === "always",
55 exceptionsArrayOptions = (context.options[1] && context.options[1].exceptions) || [],
60 if (exceptionsArrayOptions.length) {
61 options.braceException = exceptionsArrayOptions.includes("{}");
62 options.bracketException = exceptionsArrayOptions.includes("[]");
63 options.parenException = exceptionsArrayOptions.includes("()");
64 options.empty = exceptionsArrayOptions.includes("empty");
68 * Produces an object with the opener and closer exception values
69 * @returns {Object} `openers` and `closers` exception values
72 function getExceptions() {
76 if (options.braceException) {
81 if (options.bracketException) {
86 if (options.parenException) {
102 //--------------------------------------------------------------------------
104 //--------------------------------------------------------------------------
105 const sourceCode = context.getSourceCode();
108 * Determines if a token is one of the exceptions for the opener paren
109 * @param {Object} token The token to check
110 * @returns {boolean} True if the token is one of the exceptions for the opener paren
112 function isOpenerException(token) {
113 return exceptions.openers.includes(token.value);
117 * Determines if a token is one of the exceptions for the closer paren
118 * @param {Object} token The token to check
119 * @returns {boolean} True if the token is one of the exceptions for the closer paren
121 function isCloserException(token) {
122 return exceptions.closers.includes(token.value);
126 * Determines if an opening paren is immediately followed by a required space
127 * @param {Object} openingParenToken The paren token
128 * @param {Object} tokenAfterOpeningParen The token after it
129 * @returns {boolean} True if the opening paren is missing a required space
131 function openerMissingSpace(openingParenToken, tokenAfterOpeningParen) {
132 if (sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) {
136 if (!options.empty && astUtils.isClosingParenToken(tokenAfterOpeningParen)) {
141 return !isOpenerException(tokenAfterOpeningParen);
143 return isOpenerException(tokenAfterOpeningParen);
147 * Determines if an opening paren is immediately followed by a disallowed space
148 * @param {Object} openingParenToken The paren token
149 * @param {Object} tokenAfterOpeningParen The token after it
150 * @returns {boolean} True if the opening paren has a disallowed space
152 function openerRejectsSpace(openingParenToken, tokenAfterOpeningParen) {
153 if (!astUtils.isTokenOnSameLine(openingParenToken, tokenAfterOpeningParen)) {
157 if (tokenAfterOpeningParen.type === "Line") {
161 if (!sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) {
166 return isOpenerException(tokenAfterOpeningParen);
168 return !isOpenerException(tokenAfterOpeningParen);
172 * Determines if a closing paren is immediately preceded by a required space
173 * @param {Object} tokenBeforeClosingParen The token before the paren
174 * @param {Object} closingParenToken The paren token
175 * @returns {boolean} True if the closing paren is missing a required space
177 function closerMissingSpace(tokenBeforeClosingParen, closingParenToken) {
178 if (sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) {
182 if (!options.empty && astUtils.isOpeningParenToken(tokenBeforeClosingParen)) {
187 return !isCloserException(tokenBeforeClosingParen);
189 return isCloserException(tokenBeforeClosingParen);
193 * Determines if a closer paren is immediately preceded by a disallowed space
194 * @param {Object} tokenBeforeClosingParen The token before the paren
195 * @param {Object} closingParenToken The paren token
196 * @returns {boolean} True if the closing paren has a disallowed space
198 function closerRejectsSpace(tokenBeforeClosingParen, closingParenToken) {
199 if (!astUtils.isTokenOnSameLine(tokenBeforeClosingParen, closingParenToken)) {
203 if (!sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) {
208 return isCloserException(tokenBeforeClosingParen);
210 return !isCloserException(tokenBeforeClosingParen);
213 //--------------------------------------------------------------------------
215 //--------------------------------------------------------------------------
218 Program: function checkParenSpaces(node) {
219 exceptions = getExceptions();
220 const tokens = sourceCode.tokensAndComments;
222 tokens.forEach((token, i) => {
223 const prevToken = tokens[i - 1];
224 const nextToken = tokens[i + 1];
226 // if token is not an opening or closing paren token, do nothing
227 if (!astUtils.isOpeningParenToken(token) && !astUtils.isClosingParenToken(token)) {
231 // if token is an opening paren and is not followed by a required space
232 if (token.value === "(" && openerMissingSpace(token, nextToken)) {
236 messageId: "missingOpeningSpace",
238 return fixer.insertTextAfter(token, " ");
243 // if token is an opening paren and is followed by a disallowed space
244 if (token.value === "(" && openerRejectsSpace(token, nextToken)) {
247 loc: { start: token.loc.end, end: nextToken.loc.start },
248 messageId: "rejectedOpeningSpace",
250 return fixer.removeRange([token.range[1], nextToken.range[0]]);
255 // if token is a closing paren and is not preceded by a required space
256 if (token.value === ")" && closerMissingSpace(prevToken, token)) {
260 messageId: "missingClosingSpace",
262 return fixer.insertTextBefore(token, " ");
267 // if token is a closing paren and is preceded by a disallowed space
268 if (token.value === ")" && closerRejectsSpace(prevToken, token)) {
271 loc: { start: prevToken.loc.end, end: token.loc.start },
272 messageId: "rejectedClosingSpace",
274 return fixer.removeRange([prevToken.range[1], token.range[0]]);