text: /**
   * @fileoverview Rule to require or disallow line breaks inside braces.
   * @author Toru Nagashima
   */
  
  "use strict";
  
  //------------------------------------------------------------------------------
  // Requirements
  //------------------------------------------------------------------------------
  
  const astUtils = require("../util/ast-utils");
  const lodash = require("lodash");
  
  //------------------------------------------------------------------------------
  // Helpers
  //------------------------------------------------------------------------------
  
  // Schema objects.
  const OPTION_VALUE = {
      oneOf: [
          {
              enum: ["always", "never"]
          },
          {
              type: "object",
              properties: {
                  multiline: {
                      type: "boolean"
                  },
                  minProperties: {
                      type: "integer",
                      minimum: 0
                  },
                  consistent: {
                      type: "boolean"
                  }
              },
              additionalProperties: false,
              minProperties: 1
          }
      ]
  };
  
  /**
   * Normalizes a given option value.
   *
   * @param {string|Object|undefined} value - An option value to parse.
   * @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object.
   */
  function normalizeOptionValue(value) {
      let multiline = false;
      let minProperties = Number.POSITIVE_INFINITY;
      let consistent = false;
  
      if (value) {
          if (value === "always") {
              minProperties = 0;
          } else if (value === "never") {
              minProperties = Number.POSITIVE_INFINITY;
          } else {
              multiline = Boolean(value.multiline);
              minProperties = value.minProperties || Number.POSITIVE_INFINITY;
              consistent = Boolean(value.consistent);
          }
      } else {
          consistent = true;
      }
  
      return { multiline, minProperties, consistent };
  }
  
  /**
   * Normalizes a given option value.
   *
   * @param {string|Object|undefined} options - An option value to parse.
   * @returns {{
   *   ObjectExpression: {multiline: boolean, minProperties: number, consistent: boolean},
   *   ObjectPattern: {multiline: boolean, minProperties: number, consistent: boolean},
   *   ImportDeclaration: {multiline: boolean, minProperties: number, consistent: boolean},
   *   ExportNamedDeclaration : {multiline: boolean, minProperties: number, consistent: boolean}
   * }} Normalized option object.
   */
  function normalizeOptions(options) {
      const isNodeSpecificOption = lodash.overSome([lodash.isPlainObject, lodash.isString]);
  
      if (lodash.isPlainObject(options) && lodash.some(options, isNodeSpecificOption)) {
          return {
              ObjectExpression: normalizeOptionValue(options.ObjectExpression),
              ObjectPattern: normalizeOptionValue(options.ObjectPattern),
              ImportDeclaration: normalizeOptionValue(options.ImportDeclaration),
              ExportNamedDeclaration: normalizeOptionValue(options.ExportDeclaration)
          };
      }
  
      const value = normalizeOptionValue(options);
  
      return { ObjectExpression: value, ObjectPattern: value, ImportDeclaration: value, ExportNamedDeclaration: value };
  }
  
  /**
   * Determines if ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration
   * node needs to be checked for missing line breaks
   *
   * @param {ASTNode} node - Node under inspection
   * @param {Object} options - option specific to node type
   * @param {Token} first - First object property
   * @param {Token} last - Last object property
   * @returns {boolean} `true` if node needs to be checked for missing line breaks
   */
  function areLineBreaksRequired(node, options, first, last) {
      let objectProperties;
  
      if (node.type === "ObjectExpression" || node.type === "ObjectPattern") {
          objectProperties = node.properties;
      } else {
  
          // is ImportDeclaration or ExportNamedDeclaration
          objectProperties = node.specifiers
              .filter(s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier");
      }
  
      return objectProperties.length >= options.minProperties ||
          (
              options.multiline &&
              objectProperties.length > 0 &&
              first.loc.start.line !== last.loc.end.line
          );
  }
  
  //------------------------------------------------------------------------------
  // Rule Definition
  //------------------------------------------------------------------------------
  
  module.exports = {
      meta: {
          type: "layout",
  
          docs: {
              description: "enforce consistent line breaks inside braces",
              category: "Stylistic Issues",
              recommended: false,
              url: "https://eslint.org/docs/rules/object-curly-newline"
          },
  
          fixable: "whitespace",
  
          schema: [
              {
                  oneOf: [
                      OPTION_VALUE,
                      {
                          type: "object",
                          properties: {
                              ObjectExpression: OPTION_VALUE,
                              ObjectPattern: OPTION_VALUE,
                              ImportDeclaration: OPTION_VALUE,
                              ExportDeclaration: OPTION_VALUE
                          },
                          additionalProperties: false,
                          minProperties: 1
                      }
                  ]
              }
          ]
      },
  
      create(context) {
          const sourceCode = context.getSourceCode();
          const normalizedOptions = normalizeOptions(context.options[0]);
  
          /**
           * Reports a given node if it violated this rule.
           * @param {ASTNode} node - A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node.
           * @param {{multiline: boolean, minProperties: number, consistent: boolean}} options - An option object.
           * @returns {void}
           */
          function check(node) {
              const options = normalizedOptions[node.type];
  
              if (
                  (node.type === "ImportDeclaration" &&
                      !node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) ||
                  (node.type === "ExportNamedDeclaration" &&
                      !node.specifiers.some(specifier => specifier.type === "ExportSpecifier"))
              ) {
                  return;
              }
  
              const openBrace = sourceCode.getFirstToken(node, token => token.value === "{");
  
              let closeBrace;
  
              if (node.typeAnnotation) {
                  closeBrace = sourceCode.getTokenBefore(node.typeAnnotation);
              } else {
                  closeBrace = sourceCode.getLastToken(node, token => token.value === "}");
              }
  
              let first = sourceCode.getTokenAfter(openBrace, { includeComments: true });
              let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
  
              const needsLineBreaks = areLineBreaksRequired(node, options, first, last);
  
              const hasCommentsFirstToken = astUtils.isCommentToken(first);
              const hasCommentsLastToken = astUtils.isCommentToken(last);
  
              /*
               * Use tokens or comments to check multiline or not.
               * But use only tokens to check whether line breaks are needed.
               * This allows:
               *     var obj = { // eslint-disable-line foo
               *         a: 1
               *     }
               */
              first = sourceCode.getTokenAfter(openBrace);
              last = sourceCode.getTokenBefore(closeBrace);
  
              if (needsLineBreaks) {
                  if (astUtils.isTokenOnSameLine(openBrace, first)) {
                      context.report({
                          message: "Expected a line break after this opening brace.",
                          node,
                          loc: openBrace.loc.start,
                          fix(fixer) {
                              if (hasCommentsFirstToken) {
                                  return null;
                              }
  
                              return fixer.insertTextAfter(openBrace, "\n");
                          }
                      });
                  }
                  if (astUtils.isTokenOnSameLine(last, closeBrace)) {
                      context.report({
                          message: "Expected a line break before this closing brace.",
                          node,
                          loc: closeBrace.loc.start,
                          fix(fixer) {
                              if (hasCommentsLastToken) {
                                  return null;
                              }
  
                              return fixer.insertTextBefore(closeBrace, "\n");
                          }
                      });
                  }
              } else {
                  const consistent = options.consistent;
                  const hasLineBreakBetweenOpenBraceAndFirst = !astUtils.isTokenOnSameLine(openBrace, first);
                  const hasLineBreakBetweenCloseBraceAndLast = !astUtils.isTokenOnSameLine(last, closeBrace);
  
                  if (
                      (!consistent && hasLineBreakBetweenOpenBraceAndFirst) ||
                      (consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast)
                  ) {
                      context.report({
                          message: "Unexpected line break after this opening brace.",
                          node,
                          loc: openBrace.loc.start,
                          fix(fixer) {
                              if (hasCommentsFirstToken) {
                                  return null;
                              }
  
                              return fixer.removeRange([
                                  openBrace.range[1],
                                  first.range[0]
                              ]);
                          }
                      });
                  }
                  if (
                      (!consistent && hasLineBreakBetweenCloseBraceAndLast) ||
                      (consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast)
                  ) {
                      context.report({
                          message: "Unexpected line break before this closing brace.",
                          node,
                          loc: closeBrace.loc.start,
                          fix(fixer) {
                              if (hasCommentsLastToken) {
                                  return null;
                              }
  
                              return fixer.removeRange([
                                  last.range[1],
                                  closeBrace.range[0]
                              ]);
                          }
                      });
                  }
              }
          }
  
          return {
              ObjectExpression: check,
              ObjectPattern: check,
              ImportDeclaration: check,
              ExportNamedDeclaration: check
          };
      }
  };
  