UNPKG

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