UNPKG

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