UNPKG

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