| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 | /** * @fileoverview Rule to enforce line breaks after each array element * @author Jan Peer Stöcklmair <https://github.com/JPeer264> */"use strict";const astUtils = require("./utils/ast-utils");//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "layout",        docs: {            description: "Enforce line breaks after each array element",            recommended: false,            url: "https://eslint.org/docs/rules/array-element-newline"        },        fixable: "whitespace",        schema: {            definitions: {                basicConfig: {                    oneOf: [                        {                            enum: ["always", "never", "consistent"]                        },                        {                            type: "object",                            properties: {                                multiline: {                                    type: "boolean"                                },                                minItems: {                                    type: ["integer", "null"],                                    minimum: 0                                }                            },                            additionalProperties: false                        }                    ]                }            },            items: [                {                    oneOf: [                        {                            $ref: "#/definitions/basicConfig"                        },                        {                            type: "object",                            properties: {                                ArrayExpression: {                                    $ref: "#/definitions/basicConfig"                                },                                ArrayPattern: {                                    $ref: "#/definitions/basicConfig"                                }                            },                            additionalProperties: false,                            minProperties: 1                        }                    ]                }            ]        },        messages: {            unexpectedLineBreak: "There should be no linebreak here.",            missingLineBreak: "There should be a linebreak after this element."        }    },    create(context) {        const sourceCode = context.getSourceCode();        //----------------------------------------------------------------------        // Helpers        //----------------------------------------------------------------------        /**         * Normalizes a given option value.         * @param {string|Object|undefined} providedOption An option value to parse.         * @returns {{multiline: boolean, minItems: number}} Normalized option object.         */        function normalizeOptionValue(providedOption) {            let consistent = false;            let multiline = false;            let minItems;            const option = providedOption || "always";            if (!option || option === "always" || option.minItems === 0) {                minItems = 0;            } else if (option === "never") {                minItems = Number.POSITIVE_INFINITY;            } else if (option === "consistent") {                consistent = true;                minItems = Number.POSITIVE_INFINITY;            } else {                multiline = Boolean(option.multiline);                minItems = option.minItems || Number.POSITIVE_INFINITY;            }            return { consistent, multiline, minItems };        }        /**         * Normalizes a given option value.         * @param {string|Object|undefined} options An option value to parse.         * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.         */        function normalizeOptions(options) {            if (options && (options.ArrayExpression || options.ArrayPattern)) {                let expressionOptions, patternOptions;                if (options.ArrayExpression) {                    expressionOptions = normalizeOptionValue(options.ArrayExpression);                }                if (options.ArrayPattern) {                    patternOptions = normalizeOptionValue(options.ArrayPattern);                }                return { ArrayExpression: expressionOptions, ArrayPattern: patternOptions };            }            const value = normalizeOptionValue(options);            return { ArrayExpression: value, ArrayPattern: value };        }        /**         * Reports that there shouldn't be a line break after the first token         * @param {Token} token The token to use for the report.         * @returns {void}         */        function reportNoLineBreak(token) {            const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });            context.report({                loc: {                    start: tokenBefore.loc.end,                    end: token.loc.start                },                messageId: "unexpectedLineBreak",                fix(fixer) {                    if (astUtils.isCommentToken(tokenBefore)) {                        return null;                    }                    if (!astUtils.isTokenOnSameLine(tokenBefore, token)) {                        return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ");                    }                    /*                     * This will check if the comma is on the same line as the next element                     * Following array:                     * [                     *     1                     *     , 2                     *     , 3                     * ]                     *                     * will be fixed to:                     * [                     *     1, 2, 3                     * ]                     */                    const twoTokensBefore = sourceCode.getTokenBefore(tokenBefore, { includeComments: true });                    if (astUtils.isCommentToken(twoTokensBefore)) {                        return null;                    }                    return fixer.replaceTextRange([twoTokensBefore.range[1], tokenBefore.range[0]], "");                }            });        }        /**         * Reports that there should be a line break after the first token         * @param {Token} token The token to use for the report.         * @returns {void}         */        function reportRequiredLineBreak(token) {            const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });            context.report({                loc: {                    start: tokenBefore.loc.end,                    end: token.loc.start                },                messageId: "missingLineBreak",                fix(fixer) {                    return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n");                }            });        }        /**         * Reports a given node if it violated this rule.         * @param {ASTNode} node A node to check. This is an ObjectExpression node or an ObjectPattern node.         * @returns {void}         */        function check(node) {            const elements = node.elements;            const normalizedOptions = normalizeOptions(context.options[0]);            const options = normalizedOptions[node.type];            if (!options) {                return;            }            let elementBreak = false;            /*             * MULTILINE: true             * loop through every element and check             * if at least one element has linebreaks inside             * this ensures that following is not valid (due to elements are on the same line):             *             * [             *      1,             *      2,             *      3             * ]             */            if (options.multiline) {                elementBreak = elements                    .filter(element => element !== null)                    .some(element => element.loc.start.line !== element.loc.end.line);            }            const linebreaksCount = node.elements.map((element, i) => {                const previousElement = elements[i - 1];                if (i === 0 || element === null || previousElement === null) {                    return false;                }                const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken);                const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);                const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);                return !astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement);            }).filter(isBreak => isBreak === true).length;            const needsLinebreaks = (                elements.length >= options.minItems ||                (                    options.multiline &&                    elementBreak                ) ||                (                    options.consistent &&                    linebreaksCount > 0 &&                    linebreaksCount < node.elements.length                )            );            elements.forEach((element, i) => {                const previousElement = elements[i - 1];                if (i === 0 || element === null || previousElement === null) {                    return;                }                const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken);                const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);                const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);                if (needsLinebreaks) {                    if (astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {                        reportRequiredLineBreak(firstTokenOfCurrentElement);                    }                } else {                    if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {                        reportNoLineBreak(firstTokenOfCurrentElement);                    }                }            });        }        //----------------------------------------------------------------------        // Public        //----------------------------------------------------------------------        return {            ArrayPattern: check,            ArrayExpression: check        };    }};
 |