UNPKG

9.08 kBJavaScriptView Raw
1/**
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",
19 category: "Stylistic Issues",
20 recommended: false,
21 url: "https://eslint.org/docs/rules/array-bracket-spacing"
22 },
23
24 fixable: "whitespace",
25
26 schema: [
27 {
28 enum: ["always", "never"]
29 },
30 {
31 type: "object",
32 properties: {
33 singleValue: {
34 type: "boolean"
35 },
36 objectsInArrays: {
37 type: "boolean"
38 },
39 arraysInArrays: {
40 type: "boolean"
41 }
42 },
43 additionalProperties: false
44 }
45 ],
46
47 messages: {
48 unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.",
49 unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.",
50 missingSpaceAfter: "A space is required after '{{tokenValue}}'.",
51 missingSpaceBefore: "A space is required before '{{tokenValue}}'."
52 }
53 },
54 create(context) {
55 const spaced = context.options[0] === "always",
56 sourceCode = context.getSourceCode();
57
58 /**
59 * Determines whether an option is set, relative to the spacing option.
60 * If spaced is "always", then check whether option is set to false.
61 * If spaced is "never", then check whether option is set to true.
62 * @param {Object} option The option to exclude.
63 * @returns {boolean} Whether or not the property is excluded.
64 */
65 function isOptionSet(option) {
66 return context.options[1] ? context.options[1][option] === !spaced : false;
67 }
68
69 const options = {
70 spaced,
71 singleElementException: isOptionSet("singleValue"),
72 objectsInArraysException: isOptionSet("objectsInArrays"),
73 arraysInArraysException: isOptionSet("arraysInArrays")
74 };
75
76 //--------------------------------------------------------------------------
77 // Helpers
78 //--------------------------------------------------------------------------
79
80 /**
81 * Reports that there shouldn't be a space after the first token
82 * @param {ASTNode} node The node to report in the event of an error.
83 * @param {Token} token The token to use for the report.
84 * @returns {void}
85 */
86 function reportNoBeginningSpace(node, token) {
87 const nextToken = sourceCode.getTokenAfter(token);
88
89 context.report({
90 node,
91 loc: { start: token.loc.end, end: nextToken.loc.start },
92 messageId: "unexpectedSpaceAfter",
93 data: {
94 tokenValue: token.value
95 },
96 fix(fixer) {
97 return fixer.removeRange([token.range[1], nextToken.range[0]]);
98 }
99 });
100 }
101
102 /**
103 * Reports that there shouldn't be a space before the last token
104 * @param {ASTNode} node The node to report in the event of an error.
105 * @param {Token} token The token to use for the report.
106 * @returns {void}
107 */
108 function reportNoEndingSpace(node, token) {
109 const previousToken = sourceCode.getTokenBefore(token);
110
111 context.report({
112 node,
113 loc: { start: previousToken.loc.end, end: token.loc.start },
114 messageId: "unexpectedSpaceBefore",
115 data: {
116 tokenValue: token.value
117 },
118 fix(fixer) {
119 return fixer.removeRange([previousToken.range[1], token.range[0]]);
120 }
121 });
122 }
123
124 /**
125 * Reports that there should be a space after the first token
126 * @param {ASTNode} node The node to report in the event of an error.
127 * @param {Token} token The token to use for the report.
128 * @returns {void}
129 */
130 function reportRequiredBeginningSpace(node, token) {
131 context.report({
132 node,
133 loc: token.loc,
134 messageId: "missingSpaceAfter",
135 data: {
136 tokenValue: token.value
137 },
138 fix(fixer) {
139 return fixer.insertTextAfter(token, " ");
140 }
141 });
142 }
143
144 /**
145 * Reports that there should be a space before the last token
146 * @param {ASTNode} node The node to report in the event of an error.
147 * @param {Token} token The token to use for the report.
148 * @returns {void}
149 */
150 function reportRequiredEndingSpace(node, token) {
151 context.report({
152 node,
153 loc: token.loc,
154 messageId: "missingSpaceBefore",
155 data: {
156 tokenValue: token.value
157 },
158 fix(fixer) {
159 return fixer.insertTextBefore(token, " ");
160 }
161 });
162 }
163
164 /**
165 * Determines if a node is an object type
166 * @param {ASTNode} node The node to check.
167 * @returns {boolean} Whether or not the node is an object type.
168 */
169 function isObjectType(node) {
170 return node && (node.type === "ObjectExpression" || node.type === "ObjectPattern");
171 }
172
173 /**
174 * Determines if a node is an array type
175 * @param {ASTNode} node The node to check.
176 * @returns {boolean} Whether or not the node is an array type.
177 */
178 function isArrayType(node) {
179 return node && (node.type === "ArrayExpression" || node.type === "ArrayPattern");
180 }
181
182 /**
183 * Validates the spacing around array brackets
184 * @param {ASTNode} node The node we're checking for spacing
185 * @returns {void}
186 */
187 function validateArraySpacing(node) {
188 if (options.spaced && node.elements.length === 0) {
189 return;
190 }
191
192 const first = sourceCode.getFirstToken(node),
193 second = sourceCode.getFirstToken(node, 1),
194 last = node.typeAnnotation
195 ? sourceCode.getTokenBefore(node.typeAnnotation)
196 : sourceCode.getLastToken(node),
197 penultimate = sourceCode.getTokenBefore(last),
198 firstElement = node.elements[0],
199 lastElement = node.elements[node.elements.length - 1];
200
201 const openingBracketMustBeSpaced =
202 options.objectsInArraysException && isObjectType(firstElement) ||
203 options.arraysInArraysException && isArrayType(firstElement) ||
204 options.singleElementException && node.elements.length === 1
205 ? !options.spaced : options.spaced;
206
207 const closingBracketMustBeSpaced =
208 options.objectsInArraysException && isObjectType(lastElement) ||
209 options.arraysInArraysException && isArrayType(lastElement) ||
210 options.singleElementException && node.elements.length === 1
211 ? !options.spaced : options.spaced;
212
213 if (astUtils.isTokenOnSameLine(first, second)) {
214 if (openingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(first, second)) {
215 reportRequiredBeginningSpace(node, first);
216 }
217 if (!openingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(first, second)) {
218 reportNoBeginningSpace(node, first);
219 }
220 }
221
222 if (first !== penultimate && astUtils.isTokenOnSameLine(penultimate, last)) {
223 if (closingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(penultimate, last)) {
224 reportRequiredEndingSpace(node, last);
225 }
226 if (!closingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(penultimate, last)) {
227 reportNoEndingSpace(node, last);
228 }
229 }
230 }
231
232 //--------------------------------------------------------------------------
233 // Public
234 //--------------------------------------------------------------------------
235
236 return {
237 ArrayPattern: validateArraySpacing,
238 ArrayExpression: validateArraySpacing
239 };
240 }
241};