| /** | |
|  * @fileoverview Rule to forbid or enforce dangling commas. | |
|  * @author Ian Christian Myers | |
|  * @deprecated in ESLint v8.53.0 | |
|  */ | |
| 
 | |
| "use strict"; | |
| 
 | |
| //------------------------------------------------------------------------------ | |
| // Requirements | |
| //------------------------------------------------------------------------------ | |
|  | |
| const astUtils = require("./utils/ast-utils"); | |
| 
 | |
| //------------------------------------------------------------------------------ | |
| // Helpers | |
| //------------------------------------------------------------------------------ | |
|  | |
| const DEFAULT_OPTIONS = Object.freeze({ | |
|     arrays: "never", | |
|     objects: "never", | |
|     imports: "never", | |
|     exports: "never", | |
|     functions: "never" | |
| }); | |
| 
 | |
| /** | |
|  * Checks whether or not a trailing comma is allowed in a given node. | |
|  * If the `lastItem` is `RestElement` or `RestProperty`, it disallows trailing commas. | |
|  * @param {ASTNode} lastItem The node of the last element in the given node. | |
|  * @returns {boolean} `true` if a trailing comma is allowed. | |
|  */ | |
| function isTrailingCommaAllowed(lastItem) { | |
|     return !( | |
|         lastItem.type === "RestElement" || | |
|         lastItem.type === "RestProperty" || | |
|         lastItem.type === "ExperimentalRestProperty" | |
|     ); | |
| } | |
| 
 | |
| /** | |
|  * Normalize option value. | |
|  * @param {string|Object|undefined} optionValue The 1st option value to normalize. | |
|  * @param {number} ecmaVersion The normalized ECMAScript version. | |
|  * @returns {Object} The normalized option value. | |
|  */ | |
| function normalizeOptions(optionValue, ecmaVersion) { | |
|     if (typeof optionValue === "string") { | |
|         return { | |
|             arrays: optionValue, | |
|             objects: optionValue, | |
|             imports: optionValue, | |
|             exports: optionValue, | |
|             functions: ecmaVersion < 2017 ? "ignore" : optionValue | |
|         }; | |
|     } | |
|     if (typeof optionValue === "object" && optionValue !== null) { | |
|         return { | |
|             arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays, | |
|             objects: optionValue.objects || DEFAULT_OPTIONS.objects, | |
|             imports: optionValue.imports || DEFAULT_OPTIONS.imports, | |
|             exports: optionValue.exports || DEFAULT_OPTIONS.exports, | |
|             functions: optionValue.functions || DEFAULT_OPTIONS.functions | |
|         }; | |
|     } | |
| 
 | |
|     return DEFAULT_OPTIONS; | |
| } | |
| 
 | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
|  | |
| /** @type {import('../shared/types').Rule} */ | |
| module.exports = { | |
|     meta: { | |
|         deprecated: true, | |
|         replacedBy: [], | |
|         type: "layout", | |
| 
 | |
|         docs: { | |
|             description: "Require or disallow trailing commas", | |
|             recommended: false, | |
|             url: "https://eslint.org/docs/latest/rules/comma-dangle" | |
|         }, | |
| 
 | |
|         fixable: "code", | |
| 
 | |
|         schema: { | |
|             definitions: { | |
|                 value: { | |
|                     enum: [ | |
|                         "always-multiline", | |
|                         "always", | |
|                         "never", | |
|                         "only-multiline" | |
|                     ] | |
|                 }, | |
|                 valueWithIgnore: { | |
|                     enum: [ | |
|                         "always-multiline", | |
|                         "always", | |
|                         "ignore", | |
|                         "never", | |
|                         "only-multiline" | |
|                     ] | |
|                 } | |
|             }, | |
|             type: "array", | |
|             items: [ | |
|                 { | |
|                     oneOf: [ | |
|                         { | |
|                             $ref: "#/definitions/value" | |
|                         }, | |
|                         { | |
|                             type: "object", | |
|                             properties: { | |
|                                 arrays: { $ref: "#/definitions/valueWithIgnore" }, | |
|                                 objects: { $ref: "#/definitions/valueWithIgnore" }, | |
|                                 imports: { $ref: "#/definitions/valueWithIgnore" }, | |
|                                 exports: { $ref: "#/definitions/valueWithIgnore" }, | |
|                                 functions: { $ref: "#/definitions/valueWithIgnore" } | |
|                             }, | |
|                             additionalProperties: false | |
|                         } | |
|                     ] | |
|                 } | |
|             ], | |
|             additionalItems: false | |
|         }, | |
| 
 | |
|         messages: { | |
|             unexpected: "Unexpected trailing comma.", | |
|             missing: "Missing trailing comma." | |
|         } | |
|     }, | |
| 
 | |
|     create(context) { | |
|         const options = normalizeOptions(context.options[0], context.languageOptions.ecmaVersion); | |
| 
 | |
|         const sourceCode = context.sourceCode; | |
| 
 | |
|         /** | |
|          * Gets the last item of the given node. | |
|          * @param {ASTNode} node The node to get. | |
|          * @returns {ASTNode|null} The last node or null. | |
|          */ | |
|         function getLastItem(node) { | |
| 
 | |
|             /** | |
|              * Returns the last element of an array | |
|              * @param {any[]} array The input array | |
|              * @returns {any} The last element | |
|              */ | |
|             function last(array) { | |
|                 return array[array.length - 1]; | |
|             } | |
| 
 | |
|             switch (node.type) { | |
|                 case "ObjectExpression": | |
|                 case "ObjectPattern": | |
|                     return last(node.properties); | |
|                 case "ArrayExpression": | |
|                 case "ArrayPattern": | |
|                     return last(node.elements); | |
|                 case "ImportDeclaration": | |
|                 case "ExportNamedDeclaration": | |
|                     return last(node.specifiers); | |
|                 case "FunctionDeclaration": | |
|                 case "FunctionExpression": | |
|                 case "ArrowFunctionExpression": | |
|                     return last(node.params); | |
|                 case "CallExpression": | |
|                 case "NewExpression": | |
|                     return last(node.arguments); | |
|                 default: | |
|                     return null; | |
|             } | |
|         } | |
| 
 | |
|         /** | |
|          * Gets the trailing comma token of the given node. | |
|          * If the trailing comma does not exist, this returns the token which is | |
|          * the insertion point of the trailing comma token. | |
|          * @param {ASTNode} node The node to get. | |
|          * @param {ASTNode} lastItem The last item of the node. | |
|          * @returns {Token} The trailing comma token or the insertion point. | |
|          */ | |
|         function getTrailingToken(node, lastItem) { | |
|             switch (node.type) { | |
|                 case "ObjectExpression": | |
|                 case "ArrayExpression": | |
|                 case "CallExpression": | |
|                 case "NewExpression": | |
|                     return sourceCode.getLastToken(node, 1); | |
|                 default: { | |
|                     const nextToken = sourceCode.getTokenAfter(lastItem); | |
| 
 | |
|                     if (astUtils.isCommaToken(nextToken)) { | |
|                         return nextToken; | |
|                     } | |
|                     return sourceCode.getLastToken(lastItem); | |
|                 } | |
|             } | |
|         } | |
| 
 | |
|         /** | |
|          * Checks whether or not a given node is multiline. | |
|          * This rule handles a given node as multiline when the closing parenthesis | |
|          * and the last element are not on the same line. | |
|          * @param {ASTNode} node A node to check. | |
|          * @returns {boolean} `true` if the node is multiline. | |
|          */ | |
|         function isMultiline(node) { | |
|             const lastItem = getLastItem(node); | |
| 
 | |
|             if (!lastItem) { | |
|                 return false; | |
|             } | |
| 
 | |
|             const penultimateToken = getTrailingToken(node, lastItem); | |
|             const lastToken = sourceCode.getTokenAfter(penultimateToken); | |
| 
 | |
|             return lastToken.loc.end.line !== penultimateToken.loc.end.line; | |
|         } | |
| 
 | |
|         /** | |
|          * Reports a trailing comma if it exists. | |
|          * @param {ASTNode} node A node to check. Its type is one of | |
|          *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, | |
|          *   ImportDeclaration, and ExportNamedDeclaration. | |
|          * @returns {void} | |
|          */ | |
|         function forbidTrailingComma(node) { | |
|             const lastItem = getLastItem(node); | |
| 
 | |
|             if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { | |
|                 return; | |
|             } | |
| 
 | |
|             const trailingToken = getTrailingToken(node, lastItem); | |
| 
 | |
|             if (astUtils.isCommaToken(trailingToken)) { | |
|                 context.report({ | |
|                     node: lastItem, | |
|                     loc: trailingToken.loc, | |
|                     messageId: "unexpected", | |
|                     *fix(fixer) { | |
|                         yield fixer.remove(trailingToken); | |
| 
 | |
|                         /* | |
|                          * Extend the range of the fix to include surrounding tokens to ensure | |
|                          * that the element after which the comma is removed stays _last_. | |
|                          * This intentionally makes conflicts in fix ranges with rules that may be | |
|                          * adding or removing elements in the same autofix pass. | |
|                          * https://github.com/eslint/eslint/issues/15660 | |
|                          */ | |
|                         yield fixer.insertTextBefore(sourceCode.getTokenBefore(trailingToken), ""); | |
|                         yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), ""); | |
|                     } | |
|                 }); | |
|             } | |
|         } | |
| 
 | |
|         /** | |
|          * Reports the last element of a given node if it does not have a trailing | |
|          * comma. | |
|          * | |
|          * If a given node is `ArrayPattern` which has `RestElement`, the trailing | |
|          * comma is disallowed, so report if it exists. | |
|          * @param {ASTNode} node A node to check. Its type is one of | |
|          *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, | |
|          *   ImportDeclaration, and ExportNamedDeclaration. | |
|          * @returns {void} | |
|          */ | |
|         function forceTrailingComma(node) { | |
|             const lastItem = getLastItem(node); | |
| 
 | |
|             if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { | |
|                 return; | |
|             } | |
|             if (!isTrailingCommaAllowed(lastItem)) { | |
|                 forbidTrailingComma(node); | |
|                 return; | |
|             } | |
| 
 | |
|             const trailingToken = getTrailingToken(node, lastItem); | |
| 
 | |
|             if (trailingToken.value !== ",") { | |
|                 context.report({ | |
|                     node: lastItem, | |
|                     loc: { | |
|                         start: trailingToken.loc.end, | |
|                         end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end) | |
|                     }, | |
|                     messageId: "missing", | |
|                     *fix(fixer) { | |
|                         yield fixer.insertTextAfter(trailingToken, ","); | |
| 
 | |
|                         /* | |
|                          * Extend the range of the fix to include surrounding tokens to ensure | |
|                          * that the element after which the comma is inserted stays _last_. | |
|                          * This intentionally makes conflicts in fix ranges with rules that may be | |
|                          * adding or removing elements in the same autofix pass. | |
|                          * https://github.com/eslint/eslint/issues/15660 | |
|                          */ | |
|                         yield fixer.insertTextBefore(trailingToken, ""); | |
|                         yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), ""); | |
|                     } | |
|                 }); | |
|             } | |
|         } | |
| 
 | |
|         /** | |
|          * If a given node is multiline, reports the last element of a given node | |
|          * when it does not have a trailing comma. | |
|          * Otherwise, reports a trailing comma if it exists. | |
|          * @param {ASTNode} node A node to check. Its type is one of | |
|          *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, | |
|          *   ImportDeclaration, and ExportNamedDeclaration. | |
|          * @returns {void} | |
|          */ | |
|         function forceTrailingCommaIfMultiline(node) { | |
|             if (isMultiline(node)) { | |
|                 forceTrailingComma(node); | |
|             } else { | |
|                 forbidTrailingComma(node); | |
|             } | |
|         } | |
| 
 | |
|         /** | |
|          * Only if a given node is not multiline, reports the last element of a given node | |
|          * when it does not have a trailing comma. | |
|          * Otherwise, reports a trailing comma if it exists. | |
|          * @param {ASTNode} node A node to check. Its type is one of | |
|          *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, | |
|          *   ImportDeclaration, and ExportNamedDeclaration. | |
|          * @returns {void} | |
|          */ | |
|         function allowTrailingCommaIfMultiline(node) { | |
|             if (!isMultiline(node)) { | |
|                 forbidTrailingComma(node); | |
|             } | |
|         } | |
| 
 | |
|         const predicate = { | |
|             always: forceTrailingComma, | |
|             "always-multiline": forceTrailingCommaIfMultiline, | |
|             "only-multiline": allowTrailingCommaIfMultiline, | |
|             never: forbidTrailingComma, | |
|             ignore() {} | |
|         }; | |
| 
 | |
|         return { | |
|             ObjectExpression: predicate[options.objects], | |
|             ObjectPattern: predicate[options.objects], | |
| 
 | |
|             ArrayExpression: predicate[options.arrays], | |
|             ArrayPattern: predicate[options.arrays], | |
| 
 | |
|             ImportDeclaration: predicate[options.imports], | |
| 
 | |
|             ExportNamedDeclaration: predicate[options.exports], | |
| 
 | |
|             FunctionDeclaration: predicate[options.functions], | |
|             FunctionExpression: predicate[options.functions], | |
|             ArrowFunctionExpression: predicate[options.functions], | |
|             CallExpression: predicate[options.functions], | |
|             NewExpression: predicate[options.functions] | |
|         }; | |
|     } | |
| };
 |