]> git.proxmox.com Git - pve-eslint.git/blame - eslint/lib/rules/space-in-parens.js
import 8.41.0 source
[pve-eslint.git] / eslint / lib / rules / space-in-parens.js
CommitLineData
eb39fafa
DC
1/**
2 * @fileoverview Disallows or enforces spaces inside of parentheses.
3 * @author Jonathan Rajavuori
4 */
5"use strict";
6
7const astUtils = require("./utils/ast-utils");
8
9//------------------------------------------------------------------------------
10// Rule Definition
11//------------------------------------------------------------------------------
12
34eeec05 13/** @type {import('../shared/types').Rule} */
eb39fafa
DC
14module.exports = {
15 meta: {
16 type: "layout",
17
18 docs: {
8f9d1d4d 19 description: "Enforce consistent spacing inside parentheses",
eb39fafa 20 recommended: false,
f2a92ac6 21 url: "https://eslint.org/docs/latest/rules/space-in-parens"
eb39fafa
DC
22 },
23
24 fixable: "whitespace",
25
26 schema: [
27 {
28 enum: ["always", "never"]
29 },
30 {
31 type: "object",
32 properties: {
33 exceptions: {
34 type: "array",
35 items: {
36 enum: ["{}", "[]", "()", "empty"]
37 },
38 uniqueItems: true
39 }
40 },
41 additionalProperties: false
42 }
43 ],
44
45 messages: {
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."
50 }
51 },
52
53 create(context) {
54 const ALWAYS = context.options[0] === "always",
55 exceptionsArrayOptions = (context.options[1] && context.options[1].exceptions) || [],
56 options = {};
57
58 let exceptions;
59
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");
65 }
66
67 /**
68 * Produces an object with the opener and closer exception values
69 * @returns {Object} `openers` and `closers` exception values
70 * @private
71 */
72 function getExceptions() {
73 const openers = [],
74 closers = [];
75
76 if (options.braceException) {
77 openers.push("{");
78 closers.push("}");
79 }
80
81 if (options.bracketException) {
82 openers.push("[");
83 closers.push("]");
84 }
85
86 if (options.parenException) {
87 openers.push("(");
88 closers.push(")");
89 }
90
91 if (options.empty) {
92 openers.push(")");
93 closers.push("(");
94 }
95
96 return {
97 openers,
98 closers
99 };
100 }
101
102 //--------------------------------------------------------------------------
103 // Helpers
104 //--------------------------------------------------------------------------
f2a92ac6 105 const sourceCode = context.sourceCode;
eb39fafa
DC
106
107 /**
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
111 */
112 function isOpenerException(token) {
113 return exceptions.openers.includes(token.value);
114 }
115
116 /**
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
120 */
121 function isCloserException(token) {
122 return exceptions.closers.includes(token.value);
123 }
124
125 /**
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
130 */
131 function openerMissingSpace(openingParenToken, tokenAfterOpeningParen) {
132 if (sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) {
133 return false;
134 }
135
136 if (!options.empty && astUtils.isClosingParenToken(tokenAfterOpeningParen)) {
137 return false;
138 }
139
140 if (ALWAYS) {
141 return !isOpenerException(tokenAfterOpeningParen);
142 }
143 return isOpenerException(tokenAfterOpeningParen);
144 }
145
146 /**
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
151 */
152 function openerRejectsSpace(openingParenToken, tokenAfterOpeningParen) {
153 if (!astUtils.isTokenOnSameLine(openingParenToken, tokenAfterOpeningParen)) {
154 return false;
155 }
156
157 if (tokenAfterOpeningParen.type === "Line") {
158 return false;
159 }
160
161 if (!sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) {
162 return false;
163 }
164
165 if (ALWAYS) {
166 return isOpenerException(tokenAfterOpeningParen);
167 }
168 return !isOpenerException(tokenAfterOpeningParen);
169 }
170
171 /**
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
176 */
177 function closerMissingSpace(tokenBeforeClosingParen, closingParenToken) {
178 if (sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) {
179 return false;
180 }
181
182 if (!options.empty && astUtils.isOpeningParenToken(tokenBeforeClosingParen)) {
183 return false;
184 }
185
186 if (ALWAYS) {
187 return !isCloserException(tokenBeforeClosingParen);
188 }
189 return isCloserException(tokenBeforeClosingParen);
190 }
191
192 /**
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
197 */
198 function closerRejectsSpace(tokenBeforeClosingParen, closingParenToken) {
199 if (!astUtils.isTokenOnSameLine(tokenBeforeClosingParen, closingParenToken)) {
200 return false;
201 }
202
203 if (!sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) {
204 return false;
205 }
206
207 if (ALWAYS) {
208 return isCloserException(tokenBeforeClosingParen);
209 }
210 return !isCloserException(tokenBeforeClosingParen);
211 }
212
213 //--------------------------------------------------------------------------
214 // Public
215 //--------------------------------------------------------------------------
216
217 return {
218 Program: function checkParenSpaces(node) {
219 exceptions = getExceptions();
220 const tokens = sourceCode.tokensAndComments;
221
222 tokens.forEach((token, i) => {
223 const prevToken = tokens[i - 1];
224 const nextToken = tokens[i + 1];
225
226 // if token is not an opening or closing paren token, do nothing
227 if (!astUtils.isOpeningParenToken(token) && !astUtils.isClosingParenToken(token)) {
228 return;
229 }
230
231 // if token is an opening paren and is not followed by a required space
232 if (token.value === "(" && openerMissingSpace(token, nextToken)) {
233 context.report({
234 node,
235 loc: token.loc,
236 messageId: "missingOpeningSpace",
237 fix(fixer) {
238 return fixer.insertTextAfter(token, " ");
239 }
240 });
241 }
242
243 // if token is an opening paren and is followed by a disallowed space
244 if (token.value === "(" && openerRejectsSpace(token, nextToken)) {
245 context.report({
246 node,
247 loc: { start: token.loc.end, end: nextToken.loc.start },
248 messageId: "rejectedOpeningSpace",
249 fix(fixer) {
250 return fixer.removeRange([token.range[1], nextToken.range[0]]);
251 }
252 });
253 }
254
255 // if token is a closing paren and is not preceded by a required space
256 if (token.value === ")" && closerMissingSpace(prevToken, token)) {
257 context.report({
258 node,
259 loc: token.loc,
260 messageId: "missingClosingSpace",
261 fix(fixer) {
262 return fixer.insertTextBefore(token, " ");
263 }
264 });
265 }
266
267 // if token is a closing paren and is preceded by a disallowed space
268 if (token.value === ")" && closerRejectsSpace(prevToken, token)) {
269 context.report({
270 node,
271 loc: { start: prevToken.loc.end, end: token.loc.start },
272 messageId: "rejectedClosingSpace",
273 fix(fixer) {
274 return fixer.removeRange([prevToken.range[1], token.range[0]]);
275 }
276 });
277 }
278 });
279 }
280 };
281 }
282};