text: /**
   * @fileoverview A rule to ensure whitespace before blocks.
   * @author Mathias Schreck <https://github.com/lo1tuma>
   */
  
  "use strict";
  
  //------------------------------------------------------------------------------
  // Requirements
  //------------------------------------------------------------------------------
  
  const astUtils = require("./utils/ast-utils");
  
  //------------------------------------------------------------------------------
  // Helpers
  //------------------------------------------------------------------------------
  
  /**
   * Checks whether the given node represents the body of a function.
   * @param {ASTNode} node the node to check.
   * @returns {boolean} `true` if the node is function body.
   */
  function isFunctionBody(node) {
      const parent = node.parent;
  
      return (
          node.type === "BlockStatement" &&
          astUtils.isFunction(parent) &&
          parent.body === node
      );
  }
  
  //------------------------------------------------------------------------------
  // Rule Definition
  //------------------------------------------------------------------------------
  
  /** @type {import('../shared/types').Rule} */
  module.exports = {
      meta: {
          type: "layout",
  
          docs: {
              description: "enforce consistent spacing before blocks",
              recommended: false,
              url: "https://eslint.org/docs/rules/space-before-blocks"
          },
  
          fixable: "whitespace",
  
          schema: [
              {
                  oneOf: [
                      {
                          enum: ["always", "never"]
                      },
                      {
                          type: "object",
                          properties: {
                              keywords: {
                                  enum: ["always", "never", "off"]
                              },
                              functions: {
                                  enum: ["always", "never", "off"]
                              },
                              classes: {
                                  enum: ["always", "never", "off"]
                              }
                          },
                          additionalProperties: false
                      }
                  ]
              }
          ],
  
          messages: {
              unexpectedSpace: "Unexpected space before opening brace.",
              missingSpace: "Missing space before opening brace."
          }
      },
  
      create(context) {
          const config = context.options[0],
              sourceCode = context.getSourceCode();
          let alwaysFunctions = true,
              alwaysKeywords = true,
              alwaysClasses = true,
              neverFunctions = false,
              neverKeywords = false,
              neverClasses = false;
  
          if (typeof config === "object") {
              alwaysFunctions = config.functions === "always";
              alwaysKeywords = config.keywords === "always";
              alwaysClasses = config.classes === "always";
              neverFunctions = config.functions === "never";
              neverKeywords = config.keywords === "never";
              neverClasses = config.classes === "never";
          } else if (config === "never") {
              alwaysFunctions = false;
              alwaysKeywords = false;
              alwaysClasses = false;
              neverFunctions = true;
              neverKeywords = true;
              neverClasses = true;
          }
  
          /**
           * Checks whether the spacing before the given block is already controlled by another rule:
           * - `arrow-spacing` checks spaces after `=>`.
           * - `keyword-spacing` checks spaces after keywords in certain contexts.
           * - `switch-colon-spacing` checks spaces after `:` of switch cases.
           * @param {Token} precedingToken first token before the block.
           * @param {ASTNode|Token} node `BlockStatement` node or `{` token of a `SwitchStatement` node.
           * @returns {boolean} `true` if requiring or disallowing spaces before the given block could produce conflicts with other rules.
           */
          function isConflicted(precedingToken, node) {
              return (
                  astUtils.isArrowToken(precedingToken) ||
                  (
                      astUtils.isKeywordToken(precedingToken) &&
                      !isFunctionBody(node)
                  ) ||
                  (
                      astUtils.isColonToken(precedingToken) &&
                      node.parent &&
                      node.parent.type === "SwitchCase" &&
                      precedingToken === astUtils.getSwitchCaseColonToken(node.parent, sourceCode)
                  )
              );
          }
  
          /**
           * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line.
           * @param {ASTNode|Token} node The AST node of a BlockStatement.
           * @returns {void} undefined.
           */
          function checkPrecedingSpace(node) {
              const precedingToken = sourceCode.getTokenBefore(node);
  
              if (precedingToken && !isConflicted(precedingToken, node) && astUtils.isTokenOnSameLine(precedingToken, node)) {
                  const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node);
                  let requireSpace;
                  let requireNoSpace;
  
                  if (isFunctionBody(node)) {
                      requireSpace = alwaysFunctions;
                      requireNoSpace = neverFunctions;
                  } else if (node.type === "ClassBody") {
                      requireSpace = alwaysClasses;
                      requireNoSpace = neverClasses;
                  } else {
                      requireSpace = alwaysKeywords;
                      requireNoSpace = neverKeywords;
                  }
  
                  if (requireSpace && !hasSpace) {
                      context.report({
                          node,
                          messageId: "missingSpace",
                          fix(fixer) {
                              return fixer.insertTextBefore(node, " ");
                          }
                      });
                  } else if (requireNoSpace && hasSpace) {
                      context.report({
                          node,
                          messageId: "unexpectedSpace",
                          fix(fixer) {
                              return fixer.removeRange([precedingToken.range[1], node.range[0]]);
                          }
                      });
                  }
              }
          }
  
          /**
           * Checks if the CaseBlock of an given SwitchStatement node has a preceding space.
           * @param {ASTNode} node The node of a SwitchStatement.
           * @returns {void} undefined.
           */
          function checkSpaceBeforeCaseBlock(node) {
              const cases = node.cases;
              let openingBrace;
  
              if (cases.length > 0) {
                  openingBrace = sourceCode.getTokenBefore(cases[0]);
              } else {
                  openingBrace = sourceCode.getLastToken(node, 1);
              }
  
              checkPrecedingSpace(openingBrace);
          }
  
          return {
              BlockStatement: checkPrecedingSpace,
              ClassBody: checkPrecedingSpace,
              SwitchStatement: checkSpaceBeforeCaseBlock
          };
  
      }
  };
  