1 | /**
|
2 | * @fileoverview Comma spacing - validates spacing before and after comma
|
3 | * @author Vignesh Anand aka vegetableman.
|
4 | */
|
5 | ;
|
6 |
|
7 | const astUtils = require("../ast-utils");
|
8 |
|
9 | //------------------------------------------------------------------------------
|
10 | // Rule Definition
|
11 | //------------------------------------------------------------------------------
|
12 |
|
13 | module.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 | };
|