.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / space-in-parens.js
1 /**
2  * @fileoverview Disallows or enforces spaces inside of parentheses.
3  * @author Jonathan Rajavuori
4  */
5 "use strict";
6
7 const astUtils = require("./utils/ast-utils");
8
9 //------------------------------------------------------------------------------
10 // Rule Definition
11 //------------------------------------------------------------------------------
12
13 module.exports = {
14     meta: {
15         type: "layout",
16
17         docs: {
18             description: "enforce consistent spacing inside parentheses",
19             category: "Stylistic Issues",
20             recommended: false,
21             url: "https://eslint.org/docs/rules/space-in-parens"
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         //--------------------------------------------------------------------------
105         const sourceCode = context.getSourceCode();
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 };