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