| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 | /** * @fileoverview Rule to forbid or enforce dangling commas. * @author Ian Christian Myers */"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: {        type: "layout",        docs: {            description: "Require or disallow trailing commas",            recommended: false,            url: "https://eslint.org/docs/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.getSourceCode();        /**         * 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]        };    }};
 |