• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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};
242