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 //------------------------------------------------------------------------------
78 description
: "require or disallow trailing commas",
79 category
: "Stylistic Issues",
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
130 unexpected
: "Unexpected trailing comma.",
131 missing
: "Missing trailing comma."
136 const options
= normalizeOptions(context
.options
[0], context
.parserOptions
.ecmaVersion
);
138 const sourceCode
= context
.getSourceCode();
141 * Gets the last item of the given node.
142 * @param {ASTNode} node The node to get.
143 * @returns {ASTNode|null} The last node or null.
145 function getLastItem(node
) {
148 * Returns the last element of an array
149 * @param {any[]} array The input array
150 * @returns {any} The last element
152 function last(array
) {
153 return array
[array
.length
- 1];
157 case "ObjectExpression":
158 case "ObjectPattern":
159 return last(node
.properties
);
160 case "ArrayExpression":
162 return last(node
.elements
);
163 case "ImportDeclaration":
164 case "ExportNamedDeclaration":
165 return last(node
.specifiers
);
166 case "FunctionDeclaration":
167 case "FunctionExpression":
168 case "ArrowFunctionExpression":
169 return last(node
.params
);
170 case "CallExpression":
171 case "NewExpression":
172 return last(node
.arguments
);
179 * Gets the trailing comma token of the given node.
180 * If the trailing comma does not exist, this returns the token which is
181 * the insertion point of the trailing comma token.
182 * @param {ASTNode} node The node to get.
183 * @param {ASTNode} lastItem The last item of the node.
184 * @returns {Token} The trailing comma token or the insertion point.
186 function getTrailingToken(node
, lastItem
) {
188 case "ObjectExpression":
189 case "ArrayExpression":
190 case "CallExpression":
191 case "NewExpression":
192 return sourceCode
.getLastToken(node
, 1);
194 const nextToken
= sourceCode
.getTokenAfter(lastItem
);
196 if (astUtils
.isCommaToken(nextToken
)) {
199 return sourceCode
.getLastToken(lastItem
);
205 * Checks whether or not a given node is multiline.
206 * This rule handles a given node as multiline when the closing parenthesis
207 * and the last element are not on the same line.
208 * @param {ASTNode} node A node to check.
209 * @returns {boolean} `true` if the node is multiline.
211 function isMultiline(node
) {
212 const lastItem
= getLastItem(node
);
218 const penultimateToken
= getTrailingToken(node
, lastItem
);
219 const lastToken
= sourceCode
.getTokenAfter(penultimateToken
);
221 return lastToken
.loc
.end
.line
!== penultimateToken
.loc
.end
.line
;
225 * Reports a trailing comma if it exists.
226 * @param {ASTNode} node A node to check. Its type is one of
227 * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
228 * ImportDeclaration, and ExportNamedDeclaration.
231 function forbidTrailingComma(node
) {
232 const lastItem
= getLastItem(node
);
234 if (!lastItem
|| (node
.type
=== "ImportDeclaration" && lastItem
.type
!== "ImportSpecifier")) {
238 const trailingToken
= getTrailingToken(node
, lastItem
);
240 if (astUtils
.isCommaToken(trailingToken
)) {
243 loc
: trailingToken
.loc
,
244 messageId
: "unexpected",
246 return fixer
.remove(trailingToken
);
253 * Reports the last element of a given node if it does not have a trailing
256 * If a given node is `ArrayPattern` which has `RestElement`, the trailing
257 * comma is disallowed, so report if it exists.
258 * @param {ASTNode} node A node to check. Its type is one of
259 * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
260 * ImportDeclaration, and ExportNamedDeclaration.
263 function forceTrailingComma(node
) {
264 const lastItem
= getLastItem(node
);
266 if (!lastItem
|| (node
.type
=== "ImportDeclaration" && lastItem
.type
!== "ImportSpecifier")) {
269 if (!isTrailingCommaAllowed(lastItem
)) {
270 forbidTrailingComma(node
);
274 const trailingToken
= getTrailingToken(node
, lastItem
);
276 if (trailingToken
.value
!== ",") {
280 start
: trailingToken
.loc
.end
,
281 end
: astUtils
.getNextLocation(sourceCode
, trailingToken
.loc
.end
)
283 messageId
: "missing",
285 return fixer
.insertTextAfter(trailingToken
, ",");
292 * If a given node is multiline, reports the last element of a given node
293 * when it does not have a trailing comma.
294 * Otherwise, reports a trailing comma if it exists.
295 * @param {ASTNode} node A node to check. Its type is one of
296 * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
297 * ImportDeclaration, and ExportNamedDeclaration.
300 function forceTrailingCommaIfMultiline(node
) {
301 if (isMultiline(node
)) {
302 forceTrailingComma(node
);
304 forbidTrailingComma(node
);
309 * Only if a given node is not multiline, reports the last element of a given node
310 * when it does not have a trailing comma.
311 * Otherwise, reports a trailing comma if it exists.
312 * @param {ASTNode} node A node to check. Its type is one of
313 * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
314 * ImportDeclaration, and ExportNamedDeclaration.
317 function allowTrailingCommaIfMultiline(node
) {
318 if (!isMultiline(node
)) {
319 forbidTrailingComma(node
);
324 always
: forceTrailingComma
,
325 "always-multiline": forceTrailingCommaIfMultiline
,
326 "only-multiline": allowTrailingCommaIfMultiline
,
327 never
: forbidTrailingComma
,
332 ObjectExpression
: predicate
[options
.objects
],
333 ObjectPattern
: predicate
[options
.objects
],
335 ArrayExpression
: predicate
[options
.arrays
],
336 ArrayPattern
: predicate
[options
.arrays
],
338 ImportDeclaration
: predicate
[options
.imports
],
340 ExportNamedDeclaration
: predicate
[options
.exports
],
342 FunctionDeclaration
: predicate
[options
.functions
],
343 FunctionExpression
: predicate
[options
.functions
],
344 ArrowFunctionExpression
: predicate
[options
.functions
],
345 CallExpression
: predicate
[options
.functions
],
346 NewExpression
: predicate
[options
.functions
]