Spaces:
Sleeping
Sleeping
| /** | |
| * @fileoverview Rule to forbid or enforce dangling commas. | |
| * @author Ian Christian Myers | |
| * @deprecated in ESLint v8.53.0 | |
| */ | |
| ; | |
| //------------------------------------------------------------------------------ | |
| // 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('../types').Rule.RuleModule} */ | |
| module.exports = { | |
| meta: { | |
| deprecated: { | |
| message: "Formatting rules are being moved out of ESLint core.", | |
| url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", | |
| deprecatedSince: "8.53.0", | |
| availableUntil: "11.0.0", | |
| replacedBy: [ | |
| { | |
| message: | |
| "ESLint Stylistic now maintains deprecated stylistic core rules.", | |
| url: "https://eslint.style/guide/migration", | |
| plugin: { | |
| name: "@stylistic/eslint-plugin", | |
| url: "https://eslint.style", | |
| }, | |
| rule: { | |
| name: "comma-dangle", | |
| url: "https://eslint.style/rules/comma-dangle", | |
| }, | |
| }, | |
| ], | |
| }, | |
| 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.at(-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], | |
| }; | |
| }, | |
| }; | |