• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Comma spacing - validates spacing before and after comma
3 * @author Vignesh Anand aka vegetableman.
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 before and after commas",
19            category: "Stylistic Issues",
20            recommended: false,
21            url: "https://eslint.org/docs/rules/comma-spacing"
22        },
23
24        fixable: "whitespace",
25
26        schema: [
27            {
28                type: "object",
29                properties: {
30                    before: {
31                        type: "boolean",
32                        default: false
33                    },
34                    after: {
35                        type: "boolean",
36                        default: true
37                    }
38                },
39                additionalProperties: false
40            }
41        ],
42
43        messages: {
44            missing: "A space is required {{loc}} ','.",
45            unexpected: "There should be no space {{loc}} ','."
46        }
47    },
48
49    create(context) {
50
51        const sourceCode = context.getSourceCode();
52        const tokensAndComments = sourceCode.tokensAndComments;
53
54        const options = {
55            before: context.options[0] ? context.options[0].before : false,
56            after: context.options[0] ? context.options[0].after : true
57        };
58
59        //--------------------------------------------------------------------------
60        // Helpers
61        //--------------------------------------------------------------------------
62
63        // list of comma tokens to ignore for the check of leading whitespace
64        const commaTokensToIgnore = [];
65
66        /**
67         * Reports a spacing error with an appropriate message.
68         * @param {ASTNode} node The binary expression node to report.
69         * @param {string} loc Is the error "before" or "after" the comma?
70         * @param {ASTNode} otherNode The node at the left or right of `node`
71         * @returns {void}
72         * @private
73         */
74        function report(node, loc, otherNode) {
75            context.report({
76                node,
77                fix(fixer) {
78                    if (options[loc]) {
79                        if (loc === "before") {
80                            return fixer.insertTextBefore(node, " ");
81                        }
82                        return fixer.insertTextAfter(node, " ");
83
84                    }
85                    let start, end;
86                    const newText = "";
87
88                    if (loc === "before") {
89                        start = otherNode.range[1];
90                        end = node.range[0];
91                    } else {
92                        start = node.range[1];
93                        end = otherNode.range[0];
94                    }
95
96                    return fixer.replaceTextRange([start, end], newText);
97
98                },
99                messageId: options[loc] ? "missing" : "unexpected",
100                data: {
101                    loc
102                }
103            });
104        }
105
106        /**
107         * Validates the spacing around a comma token.
108         * @param {Object} tokens The tokens to be validated.
109         * @param {Token} tokens.comma The token representing the comma.
110         * @param {Token} [tokens.left] The last token before the comma.
111         * @param {Token} [tokens.right] The first token after the comma.
112         * @param {Token|ASTNode} reportItem The item to use when reporting an error.
113         * @returns {void}
114         * @private
115         */
116        function validateCommaItemSpacing(tokens, reportItem) {
117            if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) &&
118                    (options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma))
119            ) {
120                report(reportItem, "before", tokens.left);
121            }
122
123            if (tokens.right && astUtils.isClosingParenToken(tokens.right)) {
124                return;
125            }
126
127            if (tokens.right && !options.after && tokens.right.type === "Line") {
128                return;
129            }
130
131            if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) &&
132                    (options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right))
133            ) {
134                report(reportItem, "after", tokens.right);
135            }
136        }
137
138        /**
139         * Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
140         * @param {ASTNode} node An ArrayExpression or ArrayPattern node.
141         * @returns {void}
142         */
143        function addNullElementsToIgnoreList(node) {
144            let previousToken = sourceCode.getFirstToken(node);
145
146            node.elements.forEach(element => {
147                let token;
148
149                if (element === null) {
150                    token = sourceCode.getTokenAfter(previousToken);
151
152                    if (astUtils.isCommaToken(token)) {
153                        commaTokensToIgnore.push(token);
154                    }
155                } else {
156                    token = sourceCode.getTokenAfter(element);
157                }
158
159                previousToken = token;
160            });
161        }
162
163        //--------------------------------------------------------------------------
164        // Public
165        //--------------------------------------------------------------------------
166
167        return {
168            "Program:exit"() {
169                tokensAndComments.forEach((token, i) => {
170
171                    if (!astUtils.isCommaToken(token)) {
172                        return;
173                    }
174
175                    if (token && token.type === "JSXText") {
176                        return;
177                    }
178
179                    const previousToken = tokensAndComments[i - 1];
180                    const nextToken = tokensAndComments[i + 1];
181
182                    validateCommaItemSpacing({
183                        comma: token,
184                        left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
185                        right: astUtils.isCommaToken(nextToken) ? null : nextToken
186                    }, token);
187                });
188            },
189            ArrayExpression: addNullElementsToIgnoreList,
190            ArrayPattern: addNullElementsToIgnoreList
191
192        };
193
194    }
195};
196