374 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			374 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @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]
 | |
|         };
 | |
|     }
 | |
| };
 |