blob: 1eea99c14ca1fba3d20ba6854fe369ec7335cedd [file] [log] [blame]
Yang Guo4fd355c2019-09-19 10:59:03 +02001/**
2 * @fileoverview Disallows or enforces spaces inside of array brackets.
3 * @author Jamund Ferguson
4 */
5"use strict";
6
7const astUtils = require("./utils/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 array brackets",
Yang Guo4fd355c2019-09-19 10:59:03 +020019 recommended: false,
20 url: "https://eslint.org/docs/rules/array-bracket-spacing"
21 },
22
23 fixable: "whitespace",
24
25 schema: [
26 {
27 enum: ["always", "never"]
28 },
29 {
30 type: "object",
31 properties: {
32 singleValue: {
33 type: "boolean"
34 },
35 objectsInArrays: {
36 type: "boolean"
37 },
38 arraysInArrays: {
39 type: "boolean"
40 }
41 },
42 additionalProperties: false
43 }
44 ],
45
46 messages: {
47 unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.",
48 unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.",
49 missingSpaceAfter: "A space is required after '{{tokenValue}}'.",
50 missingSpaceBefore: "A space is required before '{{tokenValue}}'."
51 }
52 },
53 create(context) {
54 const spaced = context.options[0] === "always",
55 sourceCode = context.getSourceCode();
56
57 /**
58 * Determines whether an option is set, relative to the spacing option.
59 * If spaced is "always", then check whether option is set to false.
60 * If spaced is "never", then check whether option is set to true.
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010061 * @param {Object} option The option to exclude.
Yang Guo4fd355c2019-09-19 10:59:03 +020062 * @returns {boolean} Whether or not the property is excluded.
63 */
64 function isOptionSet(option) {
65 return context.options[1] ? context.options[1][option] === !spaced : false;
66 }
67
68 const options = {
69 spaced,
70 singleElementException: isOptionSet("singleValue"),
71 objectsInArraysException: isOptionSet("objectsInArrays"),
72 arraysInArraysException: isOptionSet("arraysInArrays")
73 };
74
75 //--------------------------------------------------------------------------
76 // Helpers
77 //--------------------------------------------------------------------------
78
79 /**
80 * Reports that there shouldn't be a space after the first token
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010081 * @param {ASTNode} node The node to report in the event of an error.
82 * @param {Token} token The token to use for the report.
Yang Guo4fd355c2019-09-19 10:59:03 +020083 * @returns {void}
84 */
85 function reportNoBeginningSpace(node, token) {
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010086 const nextToken = sourceCode.getTokenAfter(token);
87
Yang Guo4fd355c2019-09-19 10:59:03 +020088 context.report({
89 node,
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +010090 loc: { start: token.loc.end, end: nextToken.loc.start },
Yang Guo4fd355c2019-09-19 10:59:03 +020091 messageId: "unexpectedSpaceAfter",
92 data: {
93 tokenValue: token.value
94 },
95 fix(fixer) {
Yang Guo4fd355c2019-09-19 10:59:03 +020096 return fixer.removeRange([token.range[1], nextToken.range[0]]);
97 }
98 });
99 }
100
101 /**
102 * Reports that there shouldn't be a space before the last token
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100103 * @param {ASTNode} node The node to report in the event of an error.
104 * @param {Token} token The token to use for the report.
Yang Guo4fd355c2019-09-19 10:59:03 +0200105 * @returns {void}
106 */
107 function reportNoEndingSpace(node, token) {
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100108 const previousToken = sourceCode.getTokenBefore(token);
109
Yang Guo4fd355c2019-09-19 10:59:03 +0200110 context.report({
111 node,
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100112 loc: { start: previousToken.loc.end, end: token.loc.start },
Yang Guo4fd355c2019-09-19 10:59:03 +0200113 messageId: "unexpectedSpaceBefore",
114 data: {
115 tokenValue: token.value
116 },
117 fix(fixer) {
Yang Guo4fd355c2019-09-19 10:59:03 +0200118 return fixer.removeRange([previousToken.range[1], token.range[0]]);
119 }
120 });
121 }
122
123 /**
124 * Reports that there should be a space after the first token
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100125 * @param {ASTNode} node The node to report in the event of an error.
126 * @param {Token} token The token to use for the report.
Yang Guo4fd355c2019-09-19 10:59:03 +0200127 * @returns {void}
128 */
129 function reportRequiredBeginningSpace(node, token) {
130 context.report({
131 node,
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100132 loc: token.loc,
Yang Guo4fd355c2019-09-19 10:59:03 +0200133 messageId: "missingSpaceAfter",
134 data: {
135 tokenValue: token.value
136 },
137 fix(fixer) {
138 return fixer.insertTextAfter(token, " ");
139 }
140 });
141 }
142
143 /**
144 * Reports that there should be a space before the last token
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100145 * @param {ASTNode} node The node to report in the event of an error.
146 * @param {Token} token The token to use for the report.
Yang Guo4fd355c2019-09-19 10:59:03 +0200147 * @returns {void}
148 */
149 function reportRequiredEndingSpace(node, token) {
150 context.report({
151 node,
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100152 loc: token.loc,
Yang Guo4fd355c2019-09-19 10:59:03 +0200153 messageId: "missingSpaceBefore",
154 data: {
155 tokenValue: token.value
156 },
157 fix(fixer) {
158 return fixer.insertTextBefore(token, " ");
159 }
160 });
161 }
162
163 /**
164 * Determines if a node is an object type
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100165 * @param {ASTNode} node The node to check.
Yang Guo4fd355c2019-09-19 10:59:03 +0200166 * @returns {boolean} Whether or not the node is an object type.
167 */
168 function isObjectType(node) {
169 return node && (node.type === "ObjectExpression" || node.type === "ObjectPattern");
170 }
171
172 /**
173 * Determines if a node is an array type
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100174 * @param {ASTNode} node The node to check.
Yang Guo4fd355c2019-09-19 10:59:03 +0200175 * @returns {boolean} Whether or not the node is an array type.
176 */
177 function isArrayType(node) {
178 return node && (node.type === "ArrayExpression" || node.type === "ArrayPattern");
179 }
180
181 /**
182 * Validates the spacing around array brackets
Tim van der Lippec8f6ffd2020-04-06 13:42:00 +0100183 * @param {ASTNode} node The node we're checking for spacing
Yang Guo4fd355c2019-09-19 10:59:03 +0200184 * @returns {void}
185 */
186 function validateArraySpacing(node) {
187 if (options.spaced && node.elements.length === 0) {
188 return;
189 }
190
191 const first = sourceCode.getFirstToken(node),
192 second = sourceCode.getFirstToken(node, 1),
193 last = node.typeAnnotation
194 ? sourceCode.getTokenBefore(node.typeAnnotation)
195 : sourceCode.getLastToken(node),
196 penultimate = sourceCode.getTokenBefore(last),
197 firstElement = node.elements[0],
198 lastElement = node.elements[node.elements.length - 1];
199
200 const openingBracketMustBeSpaced =
201 options.objectsInArraysException && isObjectType(firstElement) ||
202 options.arraysInArraysException && isArrayType(firstElement) ||
203 options.singleElementException && node.elements.length === 1
204 ? !options.spaced : options.spaced;
205
206 const closingBracketMustBeSpaced =
207 options.objectsInArraysException && isObjectType(lastElement) ||
208 options.arraysInArraysException && isArrayType(lastElement) ||
209 options.singleElementException && node.elements.length === 1
210 ? !options.spaced : options.spaced;
211
212 if (astUtils.isTokenOnSameLine(first, second)) {
213 if (openingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(first, second)) {
214 reportRequiredBeginningSpace(node, first);
215 }
216 if (!openingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(first, second)) {
217 reportNoBeginningSpace(node, first);
218 }
219 }
220
221 if (first !== penultimate && astUtils.isTokenOnSameLine(penultimate, last)) {
222 if (closingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(penultimate, last)) {
223 reportRequiredEndingSpace(node, last);
224 }
225 if (!closingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(penultimate, last)) {
226 reportNoEndingSpace(node, last);
227 }
228 }
229 }
230
231 //--------------------------------------------------------------------------
232 // Public
233 //--------------------------------------------------------------------------
234
235 return {
236 ArrayPattern: validateArraySpacing,
237 ArrayExpression: validateArraySpacing
238 };
239 }
240};