text: /**
   * @fileoverview Rule to flag statements without curly braces
   * @author Nicholas C. Zakas
   */
  "use strict";
  
  //------------------------------------------------------------------------------
  // Requirements
  //------------------------------------------------------------------------------
  
  const astUtils = require("../ast-utils");
  
  //------------------------------------------------------------------------------
  // Rule Definition
  //------------------------------------------------------------------------------
  
  module.exports = {
      meta: {
          docs: {
              description: "enforce consistent brace style for all control statements",
              category: "Best Practices",
              recommended: false
          },
  
          schema: {
              anyOf: [
                  {
                      type: "array",
                      items: [
                          {
                              enum: ["all"]
                          }
                      ],
                      minItems: 0,
                      maxItems: 1
                  },
                  {
                      type: "array",
                      items: [
                          {
                              enum: ["multi", "multi-line", "multi-or-nest"]
                          },
                          {
                              enum: ["consistent"]
                          }
                      ],
                      minItems: 0,
                      maxItems: 2
                  }
              ]
          }
      },
  
      create: function(context) {
  
          const multiOnly = (context.options[0] === "multi");
          const multiLine = (context.options[0] === "multi-line");
          const multiOrNest = (context.options[0] === "multi-or-nest");
          const consistent = (context.options[1] === "consistent");
  
          const sourceCode = context.getSourceCode();
  
          //--------------------------------------------------------------------------
          // Helpers
          //--------------------------------------------------------------------------
  
          /**
           * Determines if a given node is a one-liner that's on the same line as it's preceding code.
           * @param {ASTNode} node The node to check.
           * @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code.
           * @private
           */
          function isCollapsedOneLiner(node) {
              const before = sourceCode.getTokenBefore(node),
                  last = sourceCode.getLastToken(node);
  
              return before.loc.start.line === last.loc.end.line;
          }
  
          /**
           * Determines if a given node is a one-liner.
           * @param {ASTNode} node The node to check.
           * @returns {boolean} True if the node is a one-liner.
           * @private
           */
          function isOneLiner(node) {
              const first = sourceCode.getFirstToken(node),
                  last = sourceCode.getLastToken(node);
  
              return first.loc.start.line === last.loc.end.line;
          }
  
          /**
           * Gets the `else` keyword token of a given `IfStatement` node.
           * @param {ASTNode} node - A `IfStatement` node to get.
           * @returns {Token} The `else` keyword token.
           */
          function getElseKeyword(node) {
              let token = sourceCode.getTokenAfter(node.consequent);
  
              while (token.type !== "Keyword" || token.value !== "else") {
                  token = sourceCode.getTokenAfter(token);
              }
  
              return token;
          }
  
          /**
           * Checks a given IfStatement node requires braces of the consequent chunk.
           * This returns `true` when below:
           *
           * 1. The given node has the `alternate` node.
           * 2. There is a `IfStatement` which doesn't have `alternate` node in the
           *    trailing statement chain of the `consequent` node.
           *
           * @param {ASTNode} node - A IfStatement node to check.
           * @returns {boolean} `true` if the node requires braces of the consequent chunk.
           */
          function requiresBraceOfConsequent(node) {
              if (node.alternate && node.consequent.type === "BlockStatement") {
                  if (node.consequent.body.length >= 2) {
                      return true;
                  }
  
                  node = node.consequent.body[0];
                  while (node) {
                      if (node.type === "IfStatement" && !node.alternate) {
                          return true;
                      }
                      node = astUtils.getTrailingStatement(node);
                  }
              }
  
              return false;
          }
  
          /**
           * Reports "Expected { after ..." error
           * @param {ASTNode} node The node to report.
           * @param {string} name The name to report.
           * @param {string} suffix Additional string to add to the end of a report.
           * @returns {void}
           * @private
           */
          function reportExpectedBraceError(node, name, suffix) {
              context.report({
                  node: node,
                  loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
                  message: "Expected { after '{{name}}'{{suffix}}.",
                  data: {
                      name: name,
                      suffix: (suffix ? " " + suffix : "")
                  }
              });
          }
  
          /**
           * Reports "Unnecessary { after ..." error
           * @param {ASTNode} node The node to report.
           * @param {string} name The name to report.
           * @param {string} suffix Additional string to add to the end of a report.
           * @returns {void}
           * @private
           */
          function reportUnnecessaryBraceError(node, name, suffix) {
              context.report({
                  node: node,
                  loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
                  message: "Unnecessary { after '{{name}}'{{suffix}}.",
                  data: {
                      name: name,
                      suffix: (suffix ? " " + suffix : "")
                  }
              });
          }
  
          /**
           * Prepares to check the body of a node to see if it's a block statement.
           * @param {ASTNode} node The node to report if there's a problem.
           * @param {ASTNode} body The body node to check for blocks.
           * @param {string} name The name to report if there's a problem.
           * @param {string} suffix Additional string to add to the end of a report.
           * @returns {Object} a prepared check object, with "actual", "expected", "check" properties.
           *   "actual" will be `true` or `false` whether the body is already a block statement.
           *   "expected" will be `true` or `false` if the body should be a block statement or not, or
           *   `null` if it doesn't matter, depending on the rule options. It can be modified to change
           *   the final behavior of "check".
           *   "check" will be a function reporting appropriate problems depending on the other
           *   properties.
           */
          function prepareCheck(node, body, name, suffix) {
              const hasBlock = (body.type === "BlockStatement");
              let expected = null;
  
              if (node.type === "IfStatement" && node.consequent === body && requiresBraceOfConsequent(node)) {
                  expected = true;
              } else if (multiOnly) {
                  if (hasBlock && body.body.length === 1) {
                      expected = false;
                  }
              } else if (multiLine) {
                  if (!isCollapsedOneLiner(body)) {
                      expected = true;
                  }
              } else if (multiOrNest) {
                  if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) {
                      expected = false;
                  } else if (!isOneLiner(body)) {
                      expected = true;
                  }
              } else {
                  expected = true;
              }
  
              return {
                  actual: hasBlock,
                  expected: expected,
                  check: function() {
                      if (this.expected !== null && this.expected !== this.actual) {
                          if (this.expected) {
                              reportExpectedBraceError(node, name, suffix);
                          } else {
                              reportUnnecessaryBraceError(node, name, suffix);
                          }
                      }
                  }
              };
          }
  
          /**
           * Prepares to check the bodies of a "if", "else if" and "else" chain.
           * @param {ASTNode} node The first IfStatement node of the chain.
           * @returns {Object[]} prepared checks for each body of the chain. See `prepareCheck` for more
           *   information.
           */
          function prepareIfChecks(node) {
              const preparedChecks = [];
  
              do {
                  preparedChecks.push(prepareCheck(node, node.consequent, "if", "condition"));
                  if (node.alternate && node.alternate.type !== "IfStatement") {
                      preparedChecks.push(prepareCheck(node, node.alternate, "else"));
                      break;
                  }
                  node = node.alternate;
              } while (node);
  
              if (consistent) {
  
                  /*
                   * If any node should have or already have braces, make sure they
                   * all have braces.
                   * If all nodes shouldn't have braces, make sure they don't.
                   */
                  const expected = preparedChecks.some(function(preparedCheck) {
                      if (preparedCheck.expected !== null) {
                          return preparedCheck.expected;
                      }
                      return preparedCheck.actual;
                  });
  
                  preparedChecks.forEach(function(preparedCheck) {
                      preparedCheck.expected = expected;
                  });
              }
  
              return preparedChecks;
          }
  
          //--------------------------------------------------------------------------
          // Public
          //--------------------------------------------------------------------------
  
          return {
              IfStatement: function(node) {
                  if (node.parent.type !== "IfStatement") {
                      prepareIfChecks(node).forEach(function(preparedCheck) {
                          preparedCheck.check();
                      });
                  }
              },
  
              WhileStatement: function(node) {
                  prepareCheck(node, node.body, "while", "condition").check();
              },
  
              DoWhileStatement: function(node) {
                  prepareCheck(node, node.body, "do").check();
              },
  
              ForStatement: function(node) {
                  prepareCheck(node, node.body, "for", "condition").check();
              },
  
              ForInStatement: function(node) {
                  prepareCheck(node, node.body, "for-in").check();
              },
  
              ForOfStatement: function(node) {
                  prepareCheck(node, node.body, "for-of").check();
              }
          };
      }
  };
  