• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Disallows or enforces spaces inside computed properties.
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 computed property brackets",
19            category: "Stylistic Issues",
20            recommended: false,
21            url: "https://eslint.org/docs/rules/computed-property-spacing"
22        },
23
24        fixable: "whitespace",
25
26        schema: [
27            {
28                enum: ["always", "never"]
29            },
30            {
31                type: "object",
32                properties: {
33                    enforceForClassMembers: {
34                        type: "boolean",
35                        default: true
36                    }
37                },
38                additionalProperties: false
39            }
40        ],
41
42        messages: {
43            unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.",
44            unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.",
45
46            missingSpaceBefore: "A space is required before '{{tokenValue}}'.",
47            missingSpaceAfter: "A space is required after '{{tokenValue}}'."
48        }
49    },
50
51    create(context) {
52        const sourceCode = context.getSourceCode();
53        const propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never"
54        const enforceForClassMembers = !context.options[1] || context.options[1].enforceForClassMembers;
55
56        //--------------------------------------------------------------------------
57        // Helpers
58        //--------------------------------------------------------------------------
59
60        /**
61         * Reports that there shouldn't be a space after the first token
62         * @param {ASTNode} node The node to report in the event of an error.
63         * @param {Token} token The token to use for the report.
64         * @param {Token} tokenAfter The token after `token`.
65         * @returns {void}
66         */
67        function reportNoBeginningSpace(node, token, tokenAfter) {
68            context.report({
69                node,
70                loc: { start: token.loc.end, end: tokenAfter.loc.start },
71                messageId: "unexpectedSpaceAfter",
72                data: {
73                    tokenValue: token.value
74                },
75                fix(fixer) {
76                    return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
77                }
78            });
79        }
80
81        /**
82         * Reports that there shouldn't be a space before the last token
83         * @param {ASTNode} node The node to report in the event of an error.
84         * @param {Token} token The token to use for the report.
85         * @param {Token} tokenBefore The token before `token`.
86         * @returns {void}
87         */
88        function reportNoEndingSpace(node, token, tokenBefore) {
89            context.report({
90                node,
91                loc: { start: tokenBefore.loc.end, end: token.loc.start },
92                messageId: "unexpectedSpaceBefore",
93                data: {
94                    tokenValue: token.value
95                },
96                fix(fixer) {
97                    return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
98                }
99            });
100        }
101
102        /**
103         * Reports that there should be a space after the first 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 reportRequiredBeginningSpace(node, token) {
109            context.report({
110                node,
111                loc: token.loc,
112                messageId: "missingSpaceAfter",
113                data: {
114                    tokenValue: token.value
115                },
116                fix(fixer) {
117                    return fixer.insertTextAfter(token, " ");
118                }
119            });
120        }
121
122        /**
123         * Reports that there should be a space before the last token
124         * @param {ASTNode} node The node to report in the event of an error.
125         * @param {Token} token The token to use for the report.
126         * @returns {void}
127         */
128        function reportRequiredEndingSpace(node, token) {
129            context.report({
130                node,
131                loc: token.loc,
132                messageId: "missingSpaceBefore",
133                data: {
134                    tokenValue: token.value
135                },
136                fix(fixer) {
137                    return fixer.insertTextBefore(token, " ");
138                }
139            });
140        }
141
142        /**
143         * Returns a function that checks the spacing of a node on the property name
144         * that was passed in.
145         * @param {string} propertyName The property on the node to check for spacing
146         * @returns {Function} A function that will check spacing on a node
147         */
148        function checkSpacing(propertyName) {
149            return function(node) {
150                if (!node.computed) {
151                    return;
152                }
153
154                const property = node[propertyName];
155
156                const before = sourceCode.getTokenBefore(property, astUtils.isOpeningBracketToken),
157                    first = sourceCode.getTokenAfter(before, { includeComments: true }),
158                    after = sourceCode.getTokenAfter(property, astUtils.isClosingBracketToken),
159                    last = sourceCode.getTokenBefore(after, { includeComments: true });
160
161                if (astUtils.isTokenOnSameLine(before, first)) {
162                    if (propertyNameMustBeSpaced) {
163                        if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) {
164                            reportRequiredBeginningSpace(node, before);
165                        }
166                    } else {
167                        if (sourceCode.isSpaceBetweenTokens(before, first)) {
168                            reportNoBeginningSpace(node, before, first);
169                        }
170                    }
171                }
172
173                if (astUtils.isTokenOnSameLine(last, after)) {
174                    if (propertyNameMustBeSpaced) {
175                        if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) {
176                            reportRequiredEndingSpace(node, after);
177                        }
178                    } else {
179                        if (sourceCode.isSpaceBetweenTokens(last, after)) {
180                            reportNoEndingSpace(node, after, last);
181                        }
182                    }
183                }
184            };
185        }
186
187
188        //--------------------------------------------------------------------------
189        // Public
190        //--------------------------------------------------------------------------
191
192        const listeners = {
193            Property: checkSpacing("key"),
194            MemberExpression: checkSpacing("property")
195        };
196
197        if (enforceForClassMembers) {
198            listeners.MethodDefinition = checkSpacing("key");
199        }
200
201        return listeners;
202
203    }
204};
205