text: /**
   * @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
  //------------------------------------------------------------------------------
  
  module.exports = {
      meta: {
          type: "layout",
  
          docs: {
              description: "enforce line breaks after each array element",
              category: "Stylistic Issues",
              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"
                                  },
                                  direction: {
                                      enum: ["fold", "unfold", "both"]
                                  },
                                  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;
              }
              let direction = option.direction && option.direction.toLowerCase() || "both";
              
              if (direction !== "fold" && direction !== "unfold") {
                  direction = "both";
              }
              return { consistent, multiline, minItems, direction };
          }
  
          /**
           * 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
                  )
              ) && (options.direction === "both" || options.direction === "unfold");
  
              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 ((options.direction === "both" || options.direction === "fold") && !astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
                          reportNoLineBreak(firstTokenOfCurrentElement);
                      }
                  }
              });
          }
  
          //----------------------------------------------------------------------
          // Public
          //----------------------------------------------------------------------
  
          return {
              ArrayPattern: check,
              ArrayExpression: check
          };
      }
  };
  