text: /**
   * @fileoverview A rule to control the use of single variable declarations.
   * @author Ian Christian Myers
   */
  
  "use strict";
  
  //------------------------------------------------------------------------------
  // Rule Definition
  //------------------------------------------------------------------------------
  
  module.exports = {
      meta: {
          type: "suggestion",
  
          docs: {
              description: "enforce variables to be declared either together or separately in functions",
              category: "Stylistic Issues",
              recommended: false,
              url: "https://eslint.org/docs/rules/one-var"
          },
  
          fixable: "code",
  
          schema: [
              {
                  oneOf: [
                      {
                          enum: ["always", "never", "consecutive"]
                      },
                      {
                          type: "object",
                          properties: {
                              separateRequires: {
                                  type: "boolean"
                              },
                              var: {
                                  enum: ["always", "never", "consecutive"]
                              },
                              let: {
                                  enum: ["always", "never", "consecutive"]
                              },
                              const: {
                                  enum: ["always", "never", "consecutive"]
                              }
                          },
                          additionalProperties: false
                      },
                      {
                          type: "object",
                          properties: {
                              initialized: {
                                  enum: ["always", "never", "consecutive"]
                              },
                              uninitialized: {
                                  enum: ["always", "never", "consecutive"]
                              }
                          },
                          additionalProperties: false
                      }
                  ]
              }
          ]
      },
  
      create(context) {
          const MODE_ALWAYS = "always";
          const MODE_NEVER = "never";
          const MODE_CONSECUTIVE = "consecutive";
          const mode = context.options[0] || MODE_ALWAYS;
  
          const options = {};
  
          if (typeof mode === "string") { // simple options configuration with just a string
              options.var = { uninitialized: mode, initialized: mode };
              options.let = { uninitialized: mode, initialized: mode };
              options.const = { uninitialized: mode, initialized: mode };
          } else if (typeof mode === "object") { // options configuration is an object
              options.separateRequires = !!mode.separateRequires;
              options.var = { uninitialized: mode.var, initialized: mode.var };
              options.let = { uninitialized: mode.let, initialized: mode.let };
              options.const = { uninitialized: mode.const, initialized: mode.const };
              if (Object.prototype.hasOwnProperty.call(mode, "uninitialized")) {
                  options.var.uninitialized = mode.uninitialized;
                  options.let.uninitialized = mode.uninitialized;
                  options.const.uninitialized = mode.uninitialized;
              }
              if (Object.prototype.hasOwnProperty.call(mode, "initialized")) {
                  options.var.initialized = mode.initialized;
                  options.let.initialized = mode.initialized;
                  options.const.initialized = mode.initialized;
              }
          }
  
          const sourceCode = context.getSourceCode();
  
          //--------------------------------------------------------------------------
          // Helpers
          //--------------------------------------------------------------------------
  
          const functionStack = [];
          const blockStack = [];
  
          /**
           * Increments the blockStack counter.
           * @returns {void}
           * @private
           */
          function startBlock() {
              blockStack.push({
                  let: { initialized: false, uninitialized: false },
                  const: { initialized: false, uninitialized: false }
              });
          }
  
          /**
           * Increments the functionStack counter.
           * @returns {void}
           * @private
           */
          function startFunction() {
              functionStack.push({ initialized: false, uninitialized: false });
              startBlock();
          }
  
          /**
           * Decrements the blockStack counter.
           * @returns {void}
           * @private
           */
          function endBlock() {
              blockStack.pop();
          }
  
          /**
           * Decrements the functionStack counter.
           * @returns {void}
           * @private
           */
          function endFunction() {
              functionStack.pop();
              endBlock();
          }
  
          /**
           * Check if a variable declaration is a require.
           * @param {ASTNode} decl variable declaration Node
           * @returns {bool} if decl is a require, return true; else return false.
           * @private
           */
          function isRequire(decl) {
              return decl.init && decl.init.type === "CallExpression" && decl.init.callee.name === "require";
          }
  
          /**
           * Records whether initialized/uninitialized/required variables are defined in current scope.
           * @param {string} statementType node.kind, one of: "var", "let", or "const"
           * @param {ASTNode[]} declarations List of declarations
           * @param {Object} currentScope The scope being investigated
           * @returns {void}
           * @private
           */
          function recordTypes(statementType, declarations, currentScope) {
              for (let i = 0; i < declarations.length; i++) {
                  if (declarations[i].init === null) {
                      if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) {
                          currentScope.uninitialized = true;
                      }
                  } else {
                      if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) {
                          if (options.separateRequires && isRequire(declarations[i])) {
                              currentScope.required = true;
                          } else {
                              currentScope.initialized = true;
                          }
                      }
                  }
              }
          }
  
          /**
           * Determines the current scope (function or block)
           * @param  {string} statementType node.kind, one of: "var", "let", or "const"
           * @returns {Object} The scope associated with statementType
           */
          function getCurrentScope(statementType) {
              let currentScope;
  
              if (statementType === "var") {
                  currentScope = functionStack[functionStack.length - 1];
              } else if (statementType === "let") {
                  currentScope = blockStack[blockStack.length - 1].let;
              } else if (statementType === "const") {
                  currentScope = blockStack[blockStack.length - 1].const;
              }
              return currentScope;
          }
  
          /**
           * Counts the number of initialized and uninitialized declarations in a list of declarations
           * @param {ASTNode[]} declarations List of declarations
           * @returns {Object} Counts of 'uninitialized' and 'initialized' declarations
           * @private
           */
          function countDeclarations(declarations) {
              const counts = { uninitialized: 0, initialized: 0 };
  
              for (let i = 0; i < declarations.length; i++) {
                  if (declarations[i].init === null) {
                      counts.uninitialized++;
                  } else {
                      counts.initialized++;
                  }
              }
              return counts;
          }
  
          /**
           * Determines if there is more than one var statement in the current scope.
           * @param {string} statementType node.kind, one of: "var", "let", or "const"
           * @param {ASTNode[]} declarations List of declarations
           * @returns {boolean} Returns true if it is the first var declaration, false if not.
           * @private
           */
          function hasOnlyOneStatement(statementType, declarations) {
  
              const declarationCounts = countDeclarations(declarations);
              const currentOptions = options[statementType] || {};
              const currentScope = getCurrentScope(statementType);
              const hasRequires = declarations.some(isRequire);
  
              if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) {
                  if (currentScope.uninitialized || currentScope.initialized) {
                      if (!hasRequires) {
                          return false;
                      }
                  }
              }
  
              if (declarationCounts.uninitialized > 0) {
                  if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) {
                      return false;
                  }
              }
              if (declarationCounts.initialized > 0) {
                  if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) {
                      if (!hasRequires) {
                          return false;
                      }
                  }
              }
              if (currentScope.required && hasRequires) {
                  return false;
              }
              recordTypes(statementType, declarations, currentScope);
              return true;
          }
  
          /**
           * Fixer to join VariableDeclaration's into a single declaration
           * @param   {VariableDeclarator[]} declarations The `VariableDeclaration` to join
           * @returns {Function}                         The fixer function
           */
          function joinDeclarations(declarations) {
              const declaration = declarations[0];
              const body = Array.isArray(declaration.parent.parent.body) ? declaration.parent.parent.body : [];
              const currentIndex = body.findIndex(node => node.range[0] === declaration.parent.range[0]);
              const previousNode = body[currentIndex - 1];
  
              return fixer => {
                  const type = sourceCode.getTokenBefore(declaration);
                  const prevSemi = sourceCode.getTokenBefore(type);
                  const res = [];
  
                  if (previousNode && previousNode.kind === sourceCode.getText(type)) {
                      if (prevSemi.value === ";") {
                          res.push(fixer.replaceText(prevSemi, ","));
                      } else {
                          res.push(fixer.insertTextAfter(prevSemi, ","));
                      }
                      res.push(fixer.replaceText(type, ""));
                  }
  
                  return res;
              };
          }
  
          /**
           * Fixer to split a VariableDeclaration into individual declarations
           * @param   {VariableDeclaration}   declaration The `VariableDeclaration` to split
           * @returns {Function}                          The fixer function
           */
          function splitDeclarations(declaration) {
              return fixer => declaration.declarations.map(declarator => {
                  const tokenAfterDeclarator = sourceCode.getTokenAfter(declarator);
  
                  if (tokenAfterDeclarator === null) {
                      return null;
                  }
  
                  const afterComma = sourceCode.getTokenAfter(tokenAfterDeclarator, { includeComments: true });
  
                  if (tokenAfterDeclarator.value !== ",") {
                      return null;
                  }
  
                  /*
                   * `var x,y`
                   * tokenAfterDeclarator ^^ afterComma
                   */
                  if (afterComma.range[0] === tokenAfterDeclarator.range[1]) {
                      return fixer.replaceText(tokenAfterDeclarator, `; ${declaration.kind} `);
                  }
  
                  /*
                   * `var x,
                   * tokenAfterDeclarator ^
                   *      y`
                   *      ^ afterComma
                   */
                  if (
                      afterComma.loc.start.line > tokenAfterDeclarator.loc.end.line ||
                      afterComma.type === "Line" ||
                      afterComma.type === "Block"
                  ) {
                      let lastComment = afterComma;
  
                      while (lastComment.type === "Line" || lastComment.type === "Block") {
                          lastComment = sourceCode.getTokenAfter(lastComment, { includeComments: true });
                      }
  
                      return fixer.replaceTextRange(
                          [tokenAfterDeclarator.range[0], lastComment.range[0]],
                          `;${sourceCode.text.slice(tokenAfterDeclarator.range[1], lastComment.range[0])}${declaration.kind} `
                      );
                  }
  
                  return fixer.replaceText(tokenAfterDeclarator, `; ${declaration.kind}`);
              }).filter(x => x);
          }
  
          /**
           * Checks a given VariableDeclaration node for errors.
           * @param {ASTNode} node The VariableDeclaration node to check
           * @returns {void}
           * @private
           */
          function checkVariableDeclaration(node) {
              const parent = node.parent;
              const type = node.kind;
  
              if (!options[type]) {
                  return;
              }
  
              const declarations = node.declarations;
              const declarationCounts = countDeclarations(declarations);
              const mixedRequires = declarations.some(isRequire) && !declarations.every(isRequire);
  
              if (options[type].initialized === MODE_ALWAYS) {
                  if (options.separateRequires && mixedRequires) {
                      context.report({
                          node,
                          message: "Split requires to be separated into a single block."
                      });
                  }
              }
  
              // consecutive
              const nodeIndex = (parent.body && parent.body.length > 0 && parent.body.indexOf(node)) || 0;
  
              if (nodeIndex > 0) {
                  const previousNode = parent.body[nodeIndex - 1];
                  const isPreviousNodeDeclaration = previousNode.type === "VariableDeclaration";
                  const declarationsWithPrevious = declarations.concat(previousNode.declarations || []);
  
                  if (
                      isPreviousNodeDeclaration &&
                      previousNode.kind === type &&
                      !(declarationsWithPrevious.some(isRequire) && !declarationsWithPrevious.every(isRequire))
                  ) {
                      const previousDeclCounts = countDeclarations(previousNode.declarations);
  
                      if (options[type].initialized === MODE_CONSECUTIVE && options[type].uninitialized === MODE_CONSECUTIVE) {
                          context.report({
                              node,
                              message: "Combine this with the previous '{{type}}' statement.",
                              data: {
                                  type
                              },
                              fix: joinDeclarations(declarations)
                          });
                      } else if (options[type].initialized === MODE_CONSECUTIVE && declarationCounts.initialized > 0 && previousDeclCounts.initialized > 0) {
                          context.report({
                              node,
                              message: "Combine this with the previous '{{type}}' statement with initialized variables.",
                              data: {
                                  type
                              },
                              fix: joinDeclarations(declarations)
                          });
                      } else if (options[type].uninitialized === MODE_CONSECUTIVE &&
                              declarationCounts.uninitialized > 0 &&
                              previousDeclCounts.uninitialized > 0) {
                          context.report({
                              node,
                              message: "Combine this with the previous '{{type}}' statement with uninitialized variables.",
                              data: {
                                  type
                              },
                              fix: joinDeclarations(declarations)
                          });
                      }
                  }
              }
  
              // always
              if (!hasOnlyOneStatement(type, declarations)) {
                  if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) {
                      context.report({
                          node,
                          message: "Combine this with the previous '{{type}}' statement.",
                          data: {
                              type
                          },
                          fix: joinDeclarations(declarations)
                      });
                  } else {
                      if (options[type].initialized === MODE_ALWAYS && declarationCounts.initialized > 0) {
                          context.report({
                              node,
                              message: "Combine this with the previous '{{type}}' statement with initialized variables.",
                              data: {
                                  type
                              },
                              fix: joinDeclarations(declarations)
                          });
                      }
                      if (options[type].uninitialized === MODE_ALWAYS && declarationCounts.uninitialized > 0) {
                          if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) {
                              return;
                          }
                          context.report({
                              node,
                              message: "Combine this with the previous '{{type}}' statement with uninitialized variables.",
                              data: {
                                  type
                              },
                              fix: joinDeclarations(declarations)
                          });
                      }
                  }
              }
  
              // never
              if (parent.type !== "ForStatement" || parent.init !== node) {
                  const totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized;
  
                  if (totalDeclarations > 1) {
                      if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) {
  
                          // both initialized and uninitialized
                          context.report({
                              node,
                              message: "Split '{{type}}' declarations into multiple statements.",
                              data: {
                                  type
                              },
                              fix: splitDeclarations(node)
                          });
                      } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) {
  
                          // initialized
                          context.report({
                              node,
                              message: "Split initialized '{{type}}' declarations into multiple statements.",
                              data: {
                                  type
                              },
                              fix: splitDeclarations(node)
                          });
                      } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) {
  
                          // uninitialized
                          context.report({
                              node,
                              message: "Split uninitialized '{{type}}' declarations into multiple statements.",
                              data: {
                                  type
                              },
                              fix: splitDeclarations(node)
                          });
                      }
                  }
              }
          }
  
          //--------------------------------------------------------------------------
          // Public API
          //--------------------------------------------------------------------------
  
          return {
              Program: startFunction,
              FunctionDeclaration: startFunction,
              FunctionExpression: startFunction,
              ArrowFunctionExpression: startFunction,
              BlockStatement: startBlock,
              ForStatement: startBlock,
              ForInStatement: startBlock,
              ForOfStatement: startBlock,
              SwitchStatement: startBlock,
              VariableDeclaration: checkVariableDeclaration,
              "ForStatement:exit": endBlock,
              "ForOfStatement:exit": endBlock,
              "ForInStatement:exit": endBlock,
              "SwitchStatement:exit": endBlock,
              "BlockStatement:exit": endBlock,
              "Program:exit": endFunction,
              "FunctionDeclaration:exit": endFunction,
              "FunctionExpression:exit": endFunction,
              "ArrowFunctionExpression:exit": endFunction
          };
  
      }
  };
  