UNPKG

6.68 kBJavaScriptView Raw
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};