UNPKG

9.57 kBJavaScriptView Raw
1/**
2 * @fileoverview Disallows or enforces spaces inside of parentheses.
3 * @author Jonathan Rajavuori
4 */
5"use strict";
6
7const astUtils = require("../util/ast-utils");
8
9//------------------------------------------------------------------------------
10// Rule Definition
11//------------------------------------------------------------------------------
12
13module.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
46 create(context) {
47
48 const MISSING_SPACE_MESSAGE = "There must be a space inside this paren.",
49 REJECTED_SPACE_MESSAGE = "There should be no spaces inside this paren.",
50 ALWAYS = context.options[0] === "always",
51 exceptionsArrayOptions = (context.options[1] && context.options[1].exceptions) || [],
52 options = {};
53 let exceptions;
54
55 if (exceptionsArrayOptions.length) {
56 options.braceException = exceptionsArrayOptions.indexOf("{}") !== -1;
57 options.bracketException = exceptionsArrayOptions.indexOf("[]") !== -1;
58 options.parenException = exceptionsArrayOptions.indexOf("()") !== -1;
59 options.empty = exceptionsArrayOptions.indexOf("empty") !== -1;
60 }
61
62 /**
63 * Produces an object with the opener and closer exception values
64 * @returns {Object} `openers` and `closers` exception values
65 * @private
66 */
67 function getExceptions() {
68 const openers = [],
69 closers = [];
70
71 if (options.braceException) {
72 openers.push("{");
73 closers.push("}");
74 }
75
76 if (options.bracketException) {
77 openers.push("[");
78 closers.push("]");
79 }
80
81 if (options.parenException) {
82 openers.push("(");
83 closers.push(")");
84 }
85
86 if (options.empty) {
87 openers.push(")");
88 closers.push("(");
89 }
90
91 return {
92 openers,
93 closers
94 };
95 }
96
97 //--------------------------------------------------------------------------
98 // Helpers
99 //--------------------------------------------------------------------------
100 const sourceCode = context.getSourceCode();
101
102 /**
103 * Determines if a token is one of the exceptions for the opener paren
104 * @param {Object} token The token to check
105 * @returns {boolean} True if the token is one of the exceptions for the opener paren
106 */
107 function isOpenerException(token) {
108 return token.type === "Punctuator" && exceptions.openers.indexOf(token.value) >= 0;
109 }
110
111 /**
112 * Determines if a token is one of the exceptions for the closer paren
113 * @param {Object} token The token to check
114 * @returns {boolean} True if the token is one of the exceptions for the closer paren
115 */
116 function isCloserException(token) {
117 return token.type === "Punctuator" && exceptions.closers.indexOf(token.value) >= 0;
118 }
119
120 /**
121 * Determines if an opener paren should have a missing space after it
122 * @param {Object} left The paren token
123 * @param {Object} right The token after it
124 * @returns {boolean} True if the paren should have a space
125 */
126 function shouldOpenerHaveSpace(left, right) {
127 if (sourceCode.isSpaceBetweenTokens(left, right)) {
128 return false;
129 }
130
131 if (ALWAYS) {
132 if (astUtils.isClosingParenToken(right)) {
133 return false;
134 }
135 return !isOpenerException(right);
136 }
137 return isOpenerException(right);
138
139 }
140
141 /**
142 * Determines if an closer paren should have a missing space after it
143 * @param {Object} left The token before the paren
144 * @param {Object} right The paren token
145 * @returns {boolean} True if the paren should have a space
146 */
147 function shouldCloserHaveSpace(left, right) {
148 if (astUtils.isOpeningParenToken(left)) {
149 return false;
150 }
151
152 if (sourceCode.isSpaceBetweenTokens(left, right)) {
153 return false;
154 }
155
156 if (ALWAYS) {
157 return !isCloserException(left);
158 }
159 return isCloserException(left);
160
161 }
162
163 /**
164 * Determines if an opener paren should not have an existing space after it
165 * @param {Object} left The paren token
166 * @param {Object} right The token after it
167 * @returns {boolean} True if the paren should reject the space
168 */
169 function shouldOpenerRejectSpace(left, right) {
170 if (right.type === "Line") {
171 return false;
172 }
173
174 if (!astUtils.isTokenOnSameLine(left, right)) {
175 return false;
176 }
177
178 if (!sourceCode.isSpaceBetweenTokens(left, right)) {
179 return false;
180 }
181
182 if (ALWAYS) {
183 return isOpenerException(right);
184 }
185 return !isOpenerException(right);
186
187 }
188
189 /**
190 * Determines if an closer paren should not have an existing space after it
191 * @param {Object} left The token before the paren
192 * @param {Object} right The paren token
193 * @returns {boolean} True if the paren should reject the space
194 */
195 function shouldCloserRejectSpace(left, right) {
196 if (astUtils.isOpeningParenToken(left)) {
197 return false;
198 }
199
200 if (!astUtils.isTokenOnSameLine(left, right)) {
201 return false;
202 }
203
204 if (!sourceCode.isSpaceBetweenTokens(left, right)) {
205 return false;
206 }
207
208 if (ALWAYS) {
209 return isCloserException(left);
210 }
211 return !isCloserException(left);
212
213 }
214
215 //--------------------------------------------------------------------------
216 // Public
217 //--------------------------------------------------------------------------
218
219 return {
220 Program: function checkParenSpaces(node) {
221 exceptions = getExceptions();
222 const tokens = sourceCode.tokensAndComments;
223
224 tokens.forEach((token, i) => {
225 const prevToken = tokens[i - 1];
226 const nextToken = tokens[i + 1];
227
228 if (!astUtils.isOpeningParenToken(token) && !astUtils.isClosingParenToken(token)) {
229 return;
230 }
231
232 if (token.value === "(" && shouldOpenerHaveSpace(token, nextToken)) {
233 context.report({
234 node,
235 loc: token.loc.start,
236 message: MISSING_SPACE_MESSAGE,
237 fix(fixer) {
238 return fixer.insertTextAfter(token, " ");
239 }
240 });
241 } else if (token.value === "(" && shouldOpenerRejectSpace(token, nextToken)) {
242 context.report({
243 node,
244 loc: token.loc.start,
245 message: REJECTED_SPACE_MESSAGE,
246 fix(fixer) {
247 return fixer.removeRange([token.range[1], nextToken.range[0]]);
248 }
249 });
250 } else if (token.value === ")" && shouldCloserHaveSpace(prevToken, token)) {
251
252 // context.report(node, token.loc.start, MISSING_SPACE_MESSAGE);
253 context.report({
254 node,
255 loc: token.loc.start,
256 message: MISSING_SPACE_MESSAGE,
257 fix(fixer) {
258 return fixer.insertTextBefore(token, " ");
259 }
260 });
261 } else if (token.value === ")" && shouldCloserRejectSpace(prevToken, token)) {
262 context.report({
263 node,
264 loc: token.loc.start,
265 message: REJECTED_SPACE_MESSAGE,
266 fix(fixer) {
267 return fixer.removeRange([prevToken.range[1], token.range[0]]);
268 }
269 });
270 }
271 });
272 }
273 };
274
275 }
276};