2 * @fileoverview Rule to forbid or enforce dangling commas.
3 * @author Ian Christian Myers
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const astUtils
= require("./utils/ast-utils");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
18 const DEFAULT_OPTIONS
= Object
.freeze({
27 * Checks whether or not a trailing comma is allowed in a given node.
28 * If the `lastItem` is `RestElement` or `RestProperty`, it disallows trailing commas.
29 * @param {ASTNode} lastItem The node of the last element in the given node.
30 * @returns {boolean} `true` if a trailing comma is allowed.
32 function isTrailingCommaAllowed(lastItem
) {
34 lastItem
.type
=== "RestElement" ||
35 lastItem
.type
=== "RestProperty" ||
36 lastItem
.type
=== "ExperimentalRestProperty"
41 * Normalize option value.
42 * @param {string|Object|undefined} optionValue The 1st option value to normalize.
43 * @param {number} ecmaVersion The normalized ECMAScript version.
44 * @returns {Object} The normalized option value.
46 function normalizeOptions(optionValue
, ecmaVersion
) {
47 if (typeof optionValue
=== "string") {
53 functions
: (!ecmaVersion
|| ecmaVersion
< 8) ? "ignore" : optionValue
56 if (typeof optionValue
=== "object" && optionValue
!== null) {
58 arrays
: optionValue
.arrays
|| DEFAULT_OPTIONS
.arrays
,
59 objects
: optionValue
.objects
|| DEFAULT_OPTIONS
.objects
,
60 imports
: optionValue
.imports
|| DEFAULT_OPTIONS
.imports
,
61 exports
: optionValue
.exports
|| DEFAULT_OPTIONS
.exports
,
62 functions
: optionValue
.functions
|| DEFAULT_OPTIONS
.functions
66 return DEFAULT_OPTIONS
;
69 //------------------------------------------------------------------------------
71 //------------------------------------------------------------------------------
73 /** @type {import('../shared/types').Rule} */
79 description
: "Require or disallow trailing commas",
81 url
: "https://eslint.org/docs/rules/comma-dangle"
111 $ref
: "#/definitions/value"
116 arrays
: { $ref
: "#/definitions/valueWithIgnore" },
117 objects
: { $ref
: "#/definitions/valueWithIgnore" },
118 imports
: { $ref
: "#/definitions/valueWithIgnore" },
119 exports
: { $ref
: "#/definitions/valueWithIgnore" },
120 functions
: { $ref
: "#/definitions/valueWithIgnore" }
122 additionalProperties
: false
127 additionalItems
: false
131 unexpected
: "Unexpected trailing comma.",
132 missing
: "Missing trailing comma."
137 const options
= normalizeOptions(context
.options
[0], context
.parserOptions
.ecmaVersion
);
139 const sourceCode
= context
.getSourceCode();
142 * Gets the last item of the given node.
143 * @param {ASTNode} node The node to get.
144 * @returns {ASTNode|null} The last node or null.
146 function getLastItem(node
) {
149 * Returns the last element of an array
150 * @param {any[]} array The input array
151 * @returns {any} The last element
153 function last(array
) {
154 return array
[array
.length
- 1];
158 case "ObjectExpression":
159 case "ObjectPattern":
160 return last(node
.properties
);
161 case "ArrayExpression":
163 return last(node
.elements
);
164 case "ImportDeclaration":
165 case "ExportNamedDeclaration":
166 return last(node
.specifiers
);
167 case "FunctionDeclaration":
168 case "FunctionExpression":
169 case "ArrowFunctionExpression":
170 return last(node
.params
);
171 case "CallExpression":
172 case "NewExpression":
173 return last(node
.arguments
);
180 * Gets the trailing comma token of the given node.
181 * If the trailing comma does not exist, this returns the token which is
182 * the insertion point of the trailing comma token.
183 * @param {ASTNode} node The node to get.
184 * @param {ASTNode} lastItem The last item of the node.
185 * @returns {Token} The trailing comma token or the insertion point.
187 function getTrailingToken(node
, lastItem
) {
189 case "ObjectExpression":
190 case "ArrayExpression":
191 case "CallExpression":
192 case "NewExpression":
193 return sourceCode
.getLastToken(node
, 1);
195 const nextToken
= sourceCode
.getTokenAfter(lastItem
);
197 if (astUtils
.isCommaToken(nextToken
)) {
200 return sourceCode
.getLastToken(lastItem
);
206 * Checks whether or not a given node is multiline.
207 * This rule handles a given node as multiline when the closing parenthesis
208 * and the last element are not on the same line.
209 * @param {ASTNode} node A node to check.
210 * @returns {boolean} `true` if the node is multiline.
212 function isMultiline(node
) {
213 const lastItem
= getLastItem(node
);
219 const penultimateToken
= getTrailingToken(node
, lastItem
);
220 const lastToken
= sourceCode
.getTokenAfter(penultimateToken
);
222 return lastToken
.loc
.end
.line
!== penultimateToken
.loc
.end
.line
;
226 * Reports a trailing comma if it exists.
227 * @param {ASTNode} node A node to check. Its type is one of
228 * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
229 * ImportDeclaration, and ExportNamedDeclaration.
232 function forbidTrailingComma(node
) {
233 const lastItem
= getLastItem(node
);
235 if (!lastItem
|| (node
.type
=== "ImportDeclaration" && lastItem
.type
!== "ImportSpecifier")) {
239 const trailingToken
= getTrailingToken(node
, lastItem
);
241 if (astUtils
.isCommaToken(trailingToken
)) {
244 loc
: trailingToken
.loc
,
245 messageId
: "unexpected",
247 yield fixer
.remove(trailingToken
);
250 * Extend the range of the fix to include surrounding tokens to ensure
251 * that the element after which the comma is removed stays _last_.
252 * This intentionally makes conflicts in fix ranges with rules that may be
253 * adding or removing elements in the same autofix pass.
254 * https://github.com/eslint/eslint/issues/15660
256 yield fixer
.insertTextBefore(sourceCode
.getTokenBefore(trailingToken
), "");
257 yield fixer
.insertTextAfter(sourceCode
.getTokenAfter(trailingToken
), "");
264 * Reports the last element of a given node if it does not have a trailing
267 * If a given node is `ArrayPattern` which has `RestElement`, the trailing
268 * comma is disallowed, so report if it exists.
269 * @param {ASTNode} node A node to check. Its type is one of
270 * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
271 * ImportDeclaration, and ExportNamedDeclaration.
274 function forceTrailingComma(node
) {
275 const lastItem
= getLastItem(node
);
277 if (!lastItem
|| (node
.type
=== "ImportDeclaration" && lastItem
.type
!== "ImportSpecifier")) {
280 if (!isTrailingCommaAllowed(lastItem
)) {
281 forbidTrailingComma(node
);
285 const trailingToken
= getTrailingToken(node
, lastItem
);
287 if (trailingToken
.value
!== ",") {
291 start
: trailingToken
.loc
.end
,
292 end
: astUtils
.getNextLocation(sourceCode
, trailingToken
.loc
.end
)
294 messageId
: "missing",
296 yield fixer
.insertTextAfter(trailingToken
, ",");
299 * Extend the range of the fix to include surrounding tokens to ensure
300 * that the element after which the comma is inserted stays _last_.
301 * This intentionally makes conflicts in fix ranges with rules that may be
302 * adding or removing elements in the same autofix pass.
303 * https://github.com/eslint/eslint/issues/15660
305 yield fixer
.insertTextBefore(trailingToken
, "");
306 yield fixer
.insertTextAfter(sourceCode
.getTokenAfter(trailingToken
), "");
313 * If a given node is multiline, reports the last element of a given node
314 * when it does not have a trailing comma.
315 * Otherwise, reports a trailing comma if it exists.
316 * @param {ASTNode} node A node to check. Its type is one of
317 * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
318 * ImportDeclaration, and ExportNamedDeclaration.
321 function forceTrailingCommaIfMultiline(node
) {
322 if (isMultiline(node
)) {
323 forceTrailingComma(node
);
325 forbidTrailingComma(node
);
330 * Only if a given node is not multiline, reports the last element of a given node
331 * when it does not have a trailing comma.
332 * Otherwise, reports a trailing comma if it exists.
333 * @param {ASTNode} node A node to check. Its type is one of
334 * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
335 * ImportDeclaration, and ExportNamedDeclaration.
338 function allowTrailingCommaIfMultiline(node
) {
339 if (!isMultiline(node
)) {
340 forbidTrailingComma(node
);
345 always
: forceTrailingComma
,
346 "always-multiline": forceTrailingCommaIfMultiline
,
347 "only-multiline": allowTrailingCommaIfMultiline
,
348 never
: forbidTrailingComma
,
353 ObjectExpression
: predicate
[options
.objects
],
354 ObjectPattern
: predicate
[options
.objects
],
356 ArrayExpression
: predicate
[options
.arrays
],
357 ArrayPattern
: predicate
[options
.arrays
],
359 ImportDeclaration
: predicate
[options
.imports
],
361 ExportNamedDeclaration
: predicate
[options
.exports
],
363 FunctionDeclaration
: predicate
[options
.functions
],
364 FunctionExpression
: predicate
[options
.functions
],
365 ArrowFunctionExpression
: predicate
[options
.functions
],
366 CallExpression
: predicate
[options
.functions
],
367 NewExpression
: predicate
[options
.functions
]