1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 | const astUtils = require("./utils/ast-utils");
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | module.exports = {
|
15 | meta: {
|
16 | type: "layout",
|
17 |
|
18 | docs: {
|
19 | description: "enforce consistent comma style",
|
20 | category: "Stylistic Issues",
|
21 | recommended: false,
|
22 | url: "https://eslint.org/docs/rules/comma-style"
|
23 | },
|
24 |
|
25 | fixable: "code",
|
26 |
|
27 | schema: [
|
28 | {
|
29 | enum: ["first", "last"]
|
30 | },
|
31 | {
|
32 | type: "object",
|
33 | properties: {
|
34 | exceptions: {
|
35 | type: "object",
|
36 | additionalProperties: {
|
37 | type: "boolean"
|
38 | }
|
39 | }
|
40 | },
|
41 | additionalProperties: false
|
42 | }
|
43 | ],
|
44 |
|
45 | messages: {
|
46 | unexpectedLineBeforeAndAfterComma: "Bad line breaking before and after ','.",
|
47 | expectedCommaFirst: "',' should be placed first.",
|
48 | expectedCommaLast: "',' should be placed last."
|
49 | }
|
50 | },
|
51 |
|
52 | create(context) {
|
53 | const style = context.options[0] || "last",
|
54 | sourceCode = context.getSourceCode();
|
55 | const exceptions = {
|
56 | ArrayPattern: true,
|
57 | ArrowFunctionExpression: true,
|
58 | CallExpression: true,
|
59 | FunctionDeclaration: true,
|
60 | FunctionExpression: true,
|
61 | ImportDeclaration: true,
|
62 | ObjectPattern: true,
|
63 | NewExpression: true
|
64 | };
|
65 |
|
66 | if (context.options.length === 2 && Object.prototype.hasOwnProperty.call(context.options[1], "exceptions")) {
|
67 | const keys = Object.keys(context.options[1].exceptions);
|
68 |
|
69 | for (let i = 0; i < keys.length; i++) {
|
70 | exceptions[keys[i]] = context.options[1].exceptions[keys[i]];
|
71 | }
|
72 | }
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | |
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | function getReplacedText(styleType, text) {
|
86 | switch (styleType) {
|
87 | case "between":
|
88 | return `,${text.replace(astUtils.LINEBREAK_MATCHER, "")}`;
|
89 |
|
90 | case "first":
|
91 | return `${text},`;
|
92 |
|
93 | case "last":
|
94 | return `,${text}`;
|
95 |
|
96 | default:
|
97 | return "";
|
98 | }
|
99 | }
|
100 |
|
101 | |
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 | function getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken) {
|
111 | const text =
|
112 | sourceCode.text.slice(previousItemToken.range[1], commaToken.range[0]) +
|
113 | sourceCode.text.slice(commaToken.range[1], currentItemToken.range[0]);
|
114 | const range = [previousItemToken.range[1], currentItemToken.range[0]];
|
115 |
|
116 | return function(fixer) {
|
117 | return fixer.replaceTextRange(range, getReplacedText(styleType, text));
|
118 | };
|
119 | }
|
120 |
|
121 | |
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 | function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) {
|
131 |
|
132 |
|
133 | if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
|
134 | astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
|
135 |
|
136 |
|
137 |
|
138 | } else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
|
139 | !astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
|
140 |
|
141 | const comment = sourceCode.getCommentsAfter(commaToken)[0];
|
142 | const styleType = comment && comment.type === "Block" && astUtils.isTokenOnSameLine(commaToken, comment)
|
143 | ? style
|
144 | : "between";
|
145 |
|
146 |
|
147 | context.report({
|
148 | node: reportItem,
|
149 | loc: commaToken.loc,
|
150 | messageId: "unexpectedLineBeforeAndAfterComma",
|
151 | fix: getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken)
|
152 | });
|
153 |
|
154 | } else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
|
155 |
|
156 | context.report({
|
157 | node: reportItem,
|
158 | loc: commaToken.loc,
|
159 | messageId: "expectedCommaFirst",
|
160 | fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
|
161 | });
|
162 |
|
163 | } else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
|
164 |
|
165 | context.report({
|
166 | node: reportItem,
|
167 | loc: commaToken.loc,
|
168 | messageId: "expectedCommaLast",
|
169 | fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
|
170 | });
|
171 | }
|
172 | }
|
173 |
|
174 | |
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 | function validateComma(node, property) {
|
182 | const items = node[property],
|
183 | arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern");
|
184 |
|
185 | if (items.length > 1 || arrayLiteral) {
|
186 |
|
187 |
|
188 | let previousItemToken = sourceCode.getFirstToken(node);
|
189 |
|
190 | items.forEach(item => {
|
191 | const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken,
|
192 | currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken),
|
193 | reportItem = item || currentItemToken;
|
194 |
|
195 | |
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 | if (astUtils.isCommaToken(commaToken)) {
|
210 | validateCommaItemSpacing(previousItemToken, commaToken,
|
211 | currentItemToken, reportItem);
|
212 | }
|
213 |
|
214 | if (item) {
|
215 | const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken);
|
216 |
|
217 | previousItemToken = tokenAfterItem
|
218 | ? sourceCode.getTokenBefore(tokenAfterItem)
|
219 | : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
|
220 | }
|
221 | });
|
222 |
|
223 | |
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 | if (arrayLiteral) {
|
230 |
|
231 | const lastToken = sourceCode.getLastToken(node),
|
232 | nextToLastToken = sourceCode.getTokenBefore(lastToken);
|
233 |
|
234 | if (astUtils.isCommaToken(nextToLastToken)) {
|
235 | validateCommaItemSpacing(
|
236 | sourceCode.getTokenBefore(nextToLastToken),
|
237 | nextToLastToken,
|
238 | lastToken,
|
239 | lastToken
|
240 | );
|
241 | }
|
242 | }
|
243 | }
|
244 | }
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 | const nodes = {};
|
251 |
|
252 | if (!exceptions.VariableDeclaration) {
|
253 | nodes.VariableDeclaration = function(node) {
|
254 | validateComma(node, "declarations");
|
255 | };
|
256 | }
|
257 | if (!exceptions.ObjectExpression) {
|
258 | nodes.ObjectExpression = function(node) {
|
259 | validateComma(node, "properties");
|
260 | };
|
261 | }
|
262 | if (!exceptions.ObjectPattern) {
|
263 | nodes.ObjectPattern = function(node) {
|
264 | validateComma(node, "properties");
|
265 | };
|
266 | }
|
267 | if (!exceptions.ArrayExpression) {
|
268 | nodes.ArrayExpression = function(node) {
|
269 | validateComma(node, "elements");
|
270 | };
|
271 | }
|
272 | if (!exceptions.ArrayPattern) {
|
273 | nodes.ArrayPattern = function(node) {
|
274 | validateComma(node, "elements");
|
275 | };
|
276 | }
|
277 | if (!exceptions.FunctionDeclaration) {
|
278 | nodes.FunctionDeclaration = function(node) {
|
279 | validateComma(node, "params");
|
280 | };
|
281 | }
|
282 | if (!exceptions.FunctionExpression) {
|
283 | nodes.FunctionExpression = function(node) {
|
284 | validateComma(node, "params");
|
285 | };
|
286 | }
|
287 | if (!exceptions.ArrowFunctionExpression) {
|
288 | nodes.ArrowFunctionExpression = function(node) {
|
289 | validateComma(node, "params");
|
290 | };
|
291 | }
|
292 | if (!exceptions.CallExpression) {
|
293 | nodes.CallExpression = function(node) {
|
294 | validateComma(node, "arguments");
|
295 | };
|
296 | }
|
297 | if (!exceptions.ImportDeclaration) {
|
298 | nodes.ImportDeclaration = function(node) {
|
299 | validateComma(node, "specifiers");
|
300 | };
|
301 | }
|
302 | if (!exceptions.NewExpression) {
|
303 | nodes.NewExpression = function(node) {
|
304 | validateComma(node, "arguments");
|
305 | };
|
306 | }
|
307 |
|
308 | return nodes;
|
309 | }
|
310 | };
|