text: /**
   * @fileoverview This option sets a specific tab width for your code
   *
   * This rule has been ported and modified from nodeca.
   * @author Vitaly Puzrin
   * @author Gyandeep Singh
   */
  
  "use strict";
  
  //------------------------------------------------------------------------------
  // Requirements
  //------------------------------------------------------------------------------
  
  const astUtils = require("./utils/ast-utils");
  
  //------------------------------------------------------------------------------
  // Rule Definition
  //------------------------------------------------------------------------------
  
  /* istanbul ignore next: this rule has known coverage issues, but it's deprecated and shouldn't be updated in the future anyway. */
  module.exports = {
      meta: {
          type: "layout",
  
          docs: {
              description: "enforce consistent indentation",
              category: "Stylistic Issues",
              recommended: false,
              url: "https://eslint.org/docs/rules/indent-legacy"
          },
  
          deprecated: true,
  
          replacedBy: ["indent"],
  
          fixable: "whitespace",
  
          schema: [
              {
                  oneOf: [
                      {
                          enum: ["tab"]
                      },
                      {
                          type: "integer",
                          minimum: 0
                      }
                  ]
              },
              {
                  type: "object",
                  properties: {
                      SwitchCase: {
                          type: "integer",
                          minimum: 0
                      },
                      VariableDeclarator: {
                          oneOf: [
                              {
                                  type: "integer",
                                  minimum: 0
                              },
                              {
                                  type: "object",
                                  properties: {
                                      var: {
                                          type: "integer",
                                          minimum: 0
                                      },
                                      let: {
                                          type: "integer",
                                          minimum: 0
                                      },
                                      const: {
                                          type: "integer",
                                          minimum: 0
                                      }
                                  }
                              }
                          ]
                      },
                      outerIIFEBody: {
                          type: "integer",
                          minimum: 0
                      },
                      MemberExpression: {
                          type: "integer",
                          minimum: 0
                      },
                      FunctionDeclaration: {
                          type: "object",
                          properties: {
                              parameters: {
                                  oneOf: [
                                      {
                                          type: "integer",
                                          minimum: 0
                                      },
                                      {
                                          enum: ["first"]
                                      }
                                  ]
                              },
                              body: {
                                  type: "integer",
                                  minimum: 0
                              }
                          }
                      },
                      FunctionExpression: {
                          type: "object",
                          properties: {
                              parameters: {
                                  oneOf: [
                                      {
                                          type: "integer",
                                          minimum: 0
                                      },
                                      {
                                          enum: ["first"]
                                      }
                                  ]
                              },
                              body: {
                                  type: "integer",
                                  minimum: 0
                              }
                          }
                      },
                      CallExpression: {
                          type: "object",
                          properties: {
                              parameters: {
                                  oneOf: [
                                      {
                                          type: "integer",
                                          minimum: 0
                                      },
                                      {
                                          enum: ["first"]
                                      }
                                  ]
                              }
                          }
                      },
                      ArrayExpression: {
                          oneOf: [
                              {
                                  type: "integer",
                                  minimum: 0
                              },
                              {
                                  enum: ["first"]
                              }
                          ]
                      },
                      ObjectExpression: {
                          oneOf: [
                              {
                                  type: "integer",
                                  minimum: 0
                              },
                              {
                                  enum: ["first"]
                              }
                          ]
                      }
                  },
                  additionalProperties: false
              }
          ],
          messages: {
              expected: "Expected indentation of {{expected}} but found {{actual}}."
          }
      },
  
      create(context) {
          const DEFAULT_VARIABLE_INDENT = 1;
          const DEFAULT_PARAMETER_INDENT = null; // For backwards compatibility, don't check parameter indentation unless specified in the config
          const DEFAULT_FUNCTION_BODY_INDENT = 1;
  
          let indentType = "space";
          let indentSize = 4;
          const options = {
              SwitchCase: 0,
              VariableDeclarator: {
                  var: DEFAULT_VARIABLE_INDENT,
                  let: DEFAULT_VARIABLE_INDENT,
                  const: DEFAULT_VARIABLE_INDENT
              },
              outerIIFEBody: null,
              FunctionDeclaration: {
                  parameters: DEFAULT_PARAMETER_INDENT,
                  body: DEFAULT_FUNCTION_BODY_INDENT
              },
              FunctionExpression: {
                  parameters: DEFAULT_PARAMETER_INDENT,
                  body: DEFAULT_FUNCTION_BODY_INDENT
              },
              CallExpression: {
                  arguments: DEFAULT_PARAMETER_INDENT
              },
              ArrayExpression: 1,
              ObjectExpression: 1
          };
  
          const sourceCode = context.getSourceCode();
  
          if (context.options.length) {
              if (context.options[0] === "tab") {
                  indentSize = 1;
                  indentType = "tab";
              } else /* istanbul ignore else : this will be caught by options validation */ if (typeof context.options[0] === "number") {
                  indentSize = context.options[0];
                  indentType = "space";
              }
  
              if (context.options[1]) {
                  const opts = context.options[1];
  
                  options.SwitchCase = opts.SwitchCase || 0;
                  const variableDeclaratorRules = opts.VariableDeclarator;
  
                  if (typeof variableDeclaratorRules === "number") {
                      options.VariableDeclarator = {
                          var: variableDeclaratorRules,
                          let: variableDeclaratorRules,
                          const: variableDeclaratorRules
                      };
                  } else if (typeof variableDeclaratorRules === "object") {
                      Object.assign(options.VariableDeclarator, variableDeclaratorRules);
                  }
  
                  if (typeof opts.outerIIFEBody === "number") {
                      options.outerIIFEBody = opts.outerIIFEBody;
                  }
  
                  if (typeof opts.MemberExpression === "number") {
                      options.MemberExpression = opts.MemberExpression;
                  }
  
                  if (typeof opts.FunctionDeclaration === "object") {
                      Object.assign(options.FunctionDeclaration, opts.FunctionDeclaration);
                  }
  
                  if (typeof opts.FunctionExpression === "object") {
                      Object.assign(options.FunctionExpression, opts.FunctionExpression);
                  }
  
                  if (typeof opts.CallExpression === "object") {
                      Object.assign(options.CallExpression, opts.CallExpression);
                  }
  
                  if (typeof opts.ArrayExpression === "number" || typeof opts.ArrayExpression === "string") {
                      options.ArrayExpression = opts.ArrayExpression;
                  }
  
                  if (typeof opts.ObjectExpression === "number" || typeof opts.ObjectExpression === "string") {
                      options.ObjectExpression = opts.ObjectExpression;
                  }
              }
          }
  
          const caseIndentStore = {};
  
          /**
           * Creates an error message for a line, given the expected/actual indentation.
           * @param {int} expectedAmount The expected amount of indentation characters for this line
           * @param {int} actualSpaces The actual number of indentation spaces that were found on this line
           * @param {int} actualTabs The actual number of indentation tabs that were found on this line
           * @returns {string} An error message for this line
           */
          function createErrorMessageData(expectedAmount, actualSpaces, actualTabs) {
              const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs"
              const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space"
              const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs"
              let foundStatement;
  
              if (actualSpaces > 0 && actualTabs > 0) {
                  foundStatement = `${actualSpaces} ${foundSpacesWord} and ${actualTabs} ${foundTabsWord}`; // e.g. "1 space and 2 tabs"
              } else if (actualSpaces > 0) {
  
                  /*
                   * Abbreviate the message if the expected indentation is also spaces.
                   * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
                   */
                  foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`;
              } else if (actualTabs > 0) {
                  foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`;
              } else {
                  foundStatement = "0";
              }
              return {
                  expected: expectedStatement,
                  actual: foundStatement
              };
          }
  
          /**
           * Reports a given indent violation
           * @param {ASTNode} node Node violating the indent rule
           * @param {int} needed Expected indentation character count
           * @param {int} gottenSpaces Indentation space count in the actual node/code
           * @param {int} gottenTabs Indentation tab count in the actual node/code
           * @param {Object} [loc] Error line and column location
           * @param {boolean} isLastNodeCheck Is the error for last node check
           * @returns {void}
           */
          function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) {
              if (gottenSpaces && gottenTabs) {
  
                  // To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs.
                  return;
              }
  
              const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed);
  
              const textRange = isLastNodeCheck
                  ? [node.range[1] - node.loc.end.column, node.range[1] - node.loc.end.column + gottenSpaces + gottenTabs]
                  : [node.range[0] - node.loc.start.column, node.range[0] - node.loc.start.column + gottenSpaces + gottenTabs];
  
              context.report({
                  node,
                  loc,
                  messageId: "expected",
                  data: createErrorMessageData(needed, gottenSpaces, gottenTabs),
                  fix: fixer => fixer.replaceTextRange(textRange, desiredIndent)
              });
          }
  
          /**
           * Get the actual indent of node
           * @param {ASTNode|Token} node Node to examine
           * @param {boolean} [byLastLine=false] get indent of node's last line
           * @returns {Object} The node's indent. Contains keys `space` and `tab`, representing the indent of each character. Also
           * contains keys `goodChar` and `badChar`, where `goodChar` is the amount of the user's desired indentation character, and
           * `badChar` is the amount of the other indentation character.
           */
          function getNodeIndent(node, byLastLine) {
              const token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node);
              const srcCharsBeforeNode = sourceCode.getText(token, token.loc.start.column).split("");
              const indentChars = srcCharsBeforeNode.slice(0, srcCharsBeforeNode.findIndex(char => char !== " " && char !== "\t"));
              const spaces = indentChars.filter(char => char === " ").length;
              const tabs = indentChars.filter(char => char === "\t").length;
  
              return {
                  space: spaces,
                  tab: tabs,
                  goodChar: indentType === "space" ? spaces : tabs,
                  badChar: indentType === "space" ? tabs : spaces
              };
          }
  
          /**
           * Checks node is the first in its own start line. By default it looks by start line.
           * @param {ASTNode} node The node to check
           * @param {boolean} [byEndLocation=false] Lookup based on start position or end
           * @returns {boolean} true if its the first in the its start line
           */
          function isNodeFirstInLine(node, byEndLocation) {
              const firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node),
                  startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line,
                  endLine = firstToken ? firstToken.loc.end.line : -1;
  
              return startLine !== endLine;
          }
  
          /**
           * Check indent for node
           * @param {ASTNode} node Node to check
           * @param {int} neededIndent needed indent
           * @returns {void}
           */
          function checkNodeIndent(node, neededIndent) {
              const actualIndent = getNodeIndent(node, false);
  
              if (
                  node.type !== "ArrayExpression" &&
                  node.type !== "ObjectExpression" &&
                  (actualIndent.goodChar !== neededIndent || actualIndent.badChar !== 0) &&
                  isNodeFirstInLine(node)
              ) {
                  report(node, neededIndent, actualIndent.space, actualIndent.tab);
              }
  
              if (node.type === "IfStatement" && node.alternate) {
                  const elseToken = sourceCode.getTokenBefore(node.alternate);
  
                  checkNodeIndent(elseToken, neededIndent);
  
                  if (!isNodeFirstInLine(node.alternate)) {
                      checkNodeIndent(node.alternate, neededIndent);
                  }
              }
  
              if (node.type === "TryStatement" && node.handler) {
                  const catchToken = sourceCode.getFirstToken(node.handler);
  
                  checkNodeIndent(catchToken, neededIndent);
              }
  
              if (node.type === "TryStatement" && node.finalizer) {
                  const finallyToken = sourceCode.getTokenBefore(node.finalizer);
  
                  checkNodeIndent(finallyToken, neededIndent);
              }
  
              if (node.type === "DoWhileStatement") {
                  const whileToken = sourceCode.getTokenAfter(node.body);
  
                  checkNodeIndent(whileToken, neededIndent);
              }
          }
  
          /**
           * Check indent for nodes list
           * @param {ASTNode[]} nodes list of node objects
           * @param {int} indent needed indent
           * @returns {void}
           */
          function checkNodesIndent(nodes, indent) {
              nodes.forEach(node => checkNodeIndent(node, indent));
          }
  
          /**
           * Check last node line indent this detects, that block closed correctly
           * @param {ASTNode} node Node to examine
           * @param {int} lastLineIndent needed indent
           * @returns {void}
           */
          function checkLastNodeLineIndent(node, lastLineIndent) {
              const lastToken = sourceCode.getLastToken(node);
              const endIndent = getNodeIndent(lastToken, true);
  
              if ((endIndent.goodChar !== lastLineIndent || endIndent.badChar !== 0) && isNodeFirstInLine(node, true)) {
                  report(
                      node,
                      lastLineIndent,
                      endIndent.space,
                      endIndent.tab,
                      { line: lastToken.loc.start.line, column: lastToken.loc.start.column },
                      true
                  );
              }
          }
  
          /**
           * Check last node line indent this detects, that block closed correctly
           * This function for more complicated return statement case, where closing parenthesis may be followed by ';'
           * @param {ASTNode} node Node to examine
           * @param {int} firstLineIndent first line needed indent
           * @returns {void}
           */
          function checkLastReturnStatementLineIndent(node, firstLineIndent) {
  
              /*
               * in case if return statement ends with ');' we have traverse back to ')'
               * otherwise we'll measure indent for ';' and replace ')'
               */
              const lastToken = sourceCode.getLastToken(node, astUtils.isClosingParenToken);
              const textBeforeClosingParenthesis = sourceCode.getText(lastToken, lastToken.loc.start.column).slice(0, -1);
  
              if (textBeforeClosingParenthesis.trim()) {
  
                  // There are tokens before the closing paren, don't report this case
                  return;
              }
  
              const endIndent = getNodeIndent(lastToken, true);
  
              if (endIndent.goodChar !== firstLineIndent) {
                  report(
                      node,
                      firstLineIndent,
                      endIndent.space,
                      endIndent.tab,
                      { line: lastToken.loc.start.line, column: lastToken.loc.start.column },
                      true
                  );
              }
          }
  
          /**
           * Check first node line indent is correct
           * @param {ASTNode} node Node to examine
           * @param {int} firstLineIndent needed indent
           * @returns {void}
           */
          function checkFirstNodeLineIndent(node, firstLineIndent) {
              const startIndent = getNodeIndent(node, false);
  
              if ((startIndent.goodChar !== firstLineIndent || startIndent.badChar !== 0) && isNodeFirstInLine(node)) {
                  report(
                      node,
                      firstLineIndent,
                      startIndent.space,
                      startIndent.tab,
                      { line: node.loc.start.line, column: node.loc.start.column }
                  );
              }
          }
  
          /**
           * Returns a parent node of given node based on a specified type
           * if not present then return null
           * @param {ASTNode} node node to examine
           * @param {string} type type that is being looked for
           * @param {string} stopAtList end points for the evaluating code
           * @returns {ASTNode|void} if found then node otherwise null
           */
          function getParentNodeByType(node, type, stopAtList) {
              let parent = node.parent;
              const stopAtSet = new Set(stopAtList || ["Program"]);
  
              while (parent.type !== type && !stopAtSet.has(parent.type) && parent.type !== "Program") {
                  parent = parent.parent;
              }
  
              return parent.type === type ? parent : null;
          }
  
          /**
           * Returns the VariableDeclarator based on the current node
           * if not present then return null
           * @param {ASTNode} node node to examine
           * @returns {ASTNode|void} if found then node otherwise null
           */
          function getVariableDeclaratorNode(node) {
              return getParentNodeByType(node, "VariableDeclarator");
          }
  
          /**
           * Check to see if the node is part of the multi-line variable declaration.
           * Also if its on the same line as the varNode
           * @param {ASTNode} node node to check
           * @param {ASTNode} varNode variable declaration node to check against
           * @returns {boolean} True if all the above condition satisfy
           */
          function isNodeInVarOnTop(node, varNode) {
              return varNode &&
                  varNode.parent.loc.start.line === node.loc.start.line &&
                  varNode.parent.declarations.length > 1;
          }
  
          /**
           * Check to see if the argument before the callee node is multi-line and
           * there should only be 1 argument before the callee node
           * @param {ASTNode} node node to check
           * @returns {boolean} True if arguments are multi-line
           */
          function isArgBeforeCalleeNodeMultiline(node) {
              const parent = node.parent;
  
              if (parent.arguments.length >= 2 && parent.arguments[1] === node) {
                  return parent.arguments[0].loc.end.line > parent.arguments[0].loc.start.line;
              }
  
              return false;
          }
  
          /**
           * Check to see if the node is a file level IIFE
           * @param {ASTNode} node The function node to check.
           * @returns {boolean} True if the node is the outer IIFE
           */
          function isOuterIIFE(node) {
              const parent = node.parent;
              let stmt = parent.parent;
  
              /*
               * Verify that the node is an IIEF
               */
              if (
                  parent.type !== "CallExpression" ||
                  parent.callee !== node) {
  
                  return false;
              }
  
              /*
               * Navigate legal ancestors to determine whether this IIEF is outer
               */
              while (
                  stmt.type === "UnaryExpression" && (
                      stmt.operator === "!" ||
                      stmt.operator === "~" ||
                      stmt.operator === "+" ||
                      stmt.operator === "-") ||
                  stmt.type === "AssignmentExpression" ||
                  stmt.type === "LogicalExpression" ||
                  stmt.type === "SequenceExpression" ||
                  stmt.type === "VariableDeclarator") {
  
                  stmt = stmt.parent;
              }
  
              return ((
                  stmt.type === "ExpressionStatement" ||
                  stmt.type === "VariableDeclaration") &&
                  stmt.parent && stmt.parent.type === "Program"
              );
          }
  
          /**
           * Check indent for function block content
           * @param {ASTNode} node A BlockStatement node that is inside of a function.
           * @returns {void}
           */
          function checkIndentInFunctionBlock(node) {
  
              /*
               * Search first caller in chain.
               * Ex.:
               *
               * Models <- Identifier
               *   .User
               *   .find()
               *   .exec(function() {
               *   // function body
               * });
               *
               * Looks for 'Models'
               */
              const calleeNode = node.parent; // FunctionExpression
              let indent;
  
              if (calleeNode.parent &&
                  (calleeNode.parent.type === "Property" ||
                  calleeNode.parent.type === "ArrayExpression")) {
  
                  // If function is part of array or object, comma can be put at left
                  indent = getNodeIndent(calleeNode, false).goodChar;
              } else {
  
                  // If function is standalone, simple calculate indent
                  indent = getNodeIndent(calleeNode).goodChar;
              }
  
              if (calleeNode.parent.type === "CallExpression") {
                  const calleeParent = calleeNode.parent;
  
                  if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") {
                      if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) {
                          indent = getNodeIndent(calleeParent).goodChar;
                      }
                  } else {
                      if (isArgBeforeCalleeNodeMultiline(calleeNode) &&
                          calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line &&
                          !isNodeFirstInLine(calleeNode)) {
                          indent = getNodeIndent(calleeParent).goodChar;
                      }
                  }
              }
  
              /*
               * function body indent should be indent + indent size, unless this
               * is a FunctionDeclaration, FunctionExpression, or outer IIFE and the corresponding options are enabled.
               */
              let functionOffset = indentSize;
  
              if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) {
                  functionOffset = options.outerIIFEBody * indentSize;
              } else if (calleeNode.type === "FunctionExpression") {
                  functionOffset = options.FunctionExpression.body * indentSize;
              } else if (calleeNode.type === "FunctionDeclaration") {
                  functionOffset = options.FunctionDeclaration.body * indentSize;
              }
              indent += functionOffset;
  
              // check if the node is inside a variable
              const parentVarNode = getVariableDeclaratorNode(node);
  
              if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) {
                  indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
              }
  
              if (node.body.length > 0) {
                  checkNodesIndent(node.body, indent);
              }
  
              checkLastNodeLineIndent(node, indent - functionOffset);
          }
  
  
          /**
           * Checks if the given node starts and ends on the same line
           * @param {ASTNode} node The node to check
           * @returns {boolean} Whether or not the block starts and ends on the same line.
           */
          function isSingleLineNode(node) {
              const lastToken = sourceCode.getLastToken(node),
                  startLine = node.loc.start.line,
                  endLine = lastToken.loc.end.line;
  
              return startLine === endLine;
          }
  
          /**
           * Check to see if the first element inside an array is an object and on the same line as the node
           * If the node is not an array then it will return false.
           * @param {ASTNode} node node to check
           * @returns {boolean} success/failure
           */
          function isFirstArrayElementOnSameLine(node) {
              if (node.type === "ArrayExpression" && node.elements[0]) {
                  return node.elements[0].loc.start.line === node.loc.start.line && node.elements[0].type === "ObjectExpression";
              }
              return false;
  
          }
  
          /**
           * Check indent for array block content or object block content
           * @param {ASTNode} node node to examine
           * @returns {void}
           */
          function checkIndentInArrayOrObjectBlock(node) {
  
              // Skip inline
              if (isSingleLineNode(node)) {
                  return;
              }
  
              let elements = (node.type === "ArrayExpression") ? node.elements : node.properties;
  
              // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null
              elements = elements.filter(elem => elem !== null);
  
              let nodeIndent;
              let elementsIndent;
              const parentVarNode = getVariableDeclaratorNode(node);
  
              // TODO - come up with a better strategy in future
              if (isNodeFirstInLine(node)) {
                  const parent = node.parent;
  
                  nodeIndent = getNodeIndent(parent).goodChar;
                  if (!parentVarNode || parentVarNode.loc.start.line !== node.loc.start.line) {
                      if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
                          if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === parent.loc.start.line) {
                              nodeIndent += (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
                          } else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
                              const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
  
                              if (parentElements[0] &&
                                      parentElements[0].loc.start.line === parent.loc.start.line &&
                                      parentElements[0].loc.end.line !== parent.loc.start.line) {
  
                                  /*
                                   * If the first element of the array spans multiple lines, don't increase the expected indentation of the rest.
                                   * e.g. [{
                                   *        foo: 1
                                   *      },
                                   *      {
                                   *        bar: 1
                                   *      }]
                                   * the second object is not indented.
                                   */
                              } else if (typeof options[parent.type] === "number") {
                                  nodeIndent += options[parent.type] * indentSize;
                              } else {
                                  nodeIndent = parentElements[0].loc.start.column;
                              }
                          } else if (parent.type === "CallExpression" || parent.type === "NewExpression") {
                              if (typeof options.CallExpression.arguments === "number") {
                                  nodeIndent += options.CallExpression.arguments * indentSize;
                              } else if (options.CallExpression.arguments === "first") {
                                  if (parent.arguments.indexOf(node) !== -1) {
                                      nodeIndent = parent.arguments[0].loc.start.column;
                                  }
                              } else {
                                  nodeIndent += indentSize;
                              }
                          } else if (parent.type === "LogicalExpression" || parent.type === "ArrowFunctionExpression") {
                              nodeIndent += indentSize;
                          }
                      }
                  } else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && parent.type !== "MemberExpression" && parent.type !== "ExpressionStatement" && parent.type !== "AssignmentExpression" && parent.type !== "Property") {
                      nodeIndent += indentSize;
                  }
  
                  checkFirstNodeLineIndent(node, nodeIndent);
              } else {
                  nodeIndent = getNodeIndent(node).goodChar;
              }
  
              if (options[node.type] === "first") {
                  elementsIndent = elements.length ? elements[0].loc.start.column : 0; // If there are no elements, elementsIndent doesn't matter.
              } else {
                  elementsIndent = nodeIndent + indentSize * options[node.type];
              }
  
              /*
               * Check if the node is a multiple variable declaration; if so, then
               * make sure indentation takes that into account.
               */
              if (isNodeInVarOnTop(node, parentVarNode)) {
                  elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
              }
  
              checkNodesIndent(elements, elementsIndent);
  
              if (elements.length > 0) {
  
                  // Skip last block line check if last item in same line
                  if (elements[elements.length - 1].loc.end.line === node.loc.end.line) {
                      return;
                  }
              }
  
              checkLastNodeLineIndent(node, nodeIndent +
                  (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
          }
  
          /**
           * Check if the node or node body is a BlockStatement or not
           * @param {ASTNode} node node to test
           * @returns {boolean} True if it or its body is a block statement
           */
          function isNodeBodyBlock(node) {
              return node.type === "BlockStatement" || node.type === "ClassBody" || (node.body && node.body.type === "BlockStatement") ||
                  (node.consequent && node.consequent.type === "BlockStatement");
          }
  
          /**
           * Check indentation for blocks
           * @param {ASTNode} node node to check
           * @returns {void}
           */
          function blockIndentationCheck(node) {
  
              // Skip inline blocks
              if (isSingleLineNode(node)) {
                  return;
              }
  
              if (node.parent && (
                  node.parent.type === "FunctionExpression" ||
                  node.parent.type === "FunctionDeclaration" ||
                  node.parent.type === "ArrowFunctionExpression")
              ) {
                  checkIndentInFunctionBlock(node);
                  return;
              }
  
              let indent;
              let nodesToCheck = [];
  
              /*
               * For this statements we should check indent from statement beginning,
               * not from the beginning of the block.
               */
              const statementsWithProperties = [
                  "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration", "TryStatement"
              ];
  
              if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) {
                  indent = getNodeIndent(node.parent).goodChar;
              } else if (node.parent && node.parent.type === "CatchClause") {
                  indent = getNodeIndent(node.parent.parent).goodChar;
              } else {
                  indent = getNodeIndent(node).goodChar;
              }
  
              if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") {
                  nodesToCheck = [node.consequent];
              } else if (Array.isArray(node.body)) {
                  nodesToCheck = node.body;
              } else {
                  nodesToCheck = [node.body];
              }
  
              if (nodesToCheck.length > 0) {
                  checkNodesIndent(nodesToCheck, indent + indentSize);
              }
  
              if (node.type === "BlockStatement") {
                  checkLastNodeLineIndent(node, indent);
              }
          }
  
          /**
           * Filter out the elements which are on the same line of each other or the node.
           * basically have only 1 elements from each line except the variable declaration line.
           * @param {ASTNode} node Variable declaration node
           * @returns {ASTNode[]} Filtered elements
           */
          function filterOutSameLineVars(node) {
              return node.declarations.reduce((finalCollection, elem) => {
                  const lastElem = finalCollection[finalCollection.length - 1];
  
                  if ((elem.loc.start.line !== node.loc.start.line && !lastElem) ||
                      (lastElem && lastElem.loc.start.line !== elem.loc.start.line)) {
                      finalCollection.push(elem);
                  }
  
                  return finalCollection;
              }, []);
          }
  
          /**
           * Check indentation for variable declarations
           * @param {ASTNode} node node to examine
           * @returns {void}
           */
          function checkIndentInVariableDeclarations(node) {
              const elements = filterOutSameLineVars(node);
              const nodeIndent = getNodeIndent(node).goodChar;
              const lastElement = elements[elements.length - 1];
  
              const elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind];
  
              checkNodesIndent(elements, elementsIndent);
  
              // Only check the last line if there is any token after the last item
              if (sourceCode.getLastToken(node).loc.end.line <= lastElement.loc.end.line) {
                  return;
              }
  
              const tokenBeforeLastElement = sourceCode.getTokenBefore(lastElement);
  
              if (tokenBeforeLastElement.value === ",") {
  
                  // Special case for comma-first syntax where the semicolon is indented
                  checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement).goodChar);
              } else {
                  checkLastNodeLineIndent(node, elementsIndent - indentSize);
              }
          }
  
          /**
           * Check and decide whether to check for indentation for blockless nodes
           * Scenarios are for or while statements without braces around them
           * @param {ASTNode} node node to examine
           * @returns {void}
           */
          function blockLessNodes(node) {
              if (node.body.type !== "BlockStatement") {
                  blockIndentationCheck(node);
              }
          }
  
          /**
           * Returns the expected indentation for the case statement
           * @param {ASTNode} node node to examine
           * @param {int} [providedSwitchIndent] indent for switch statement
           * @returns {int} indent size
           */
          function expectedCaseIndent(node, providedSwitchIndent) {
              const switchNode = (node.type === "SwitchStatement") ? node : node.parent;
              const switchIndent = typeof providedSwitchIndent === "undefined"
                  ? getNodeIndent(switchNode).goodChar
                  : providedSwitchIndent;
              let caseIndent;
  
              if (caseIndentStore[switchNode.loc.start.line]) {
                  return caseIndentStore[switchNode.loc.start.line];
              }
  
              if (switchNode.cases.length > 0 && options.SwitchCase === 0) {
                  caseIndent = switchIndent;
              } else {
                  caseIndent = switchIndent + (indentSize * options.SwitchCase);
              }
  
              caseIndentStore[switchNode.loc.start.line] = caseIndent;
              return caseIndent;
  
          }
  
          /**
           * Checks wether a return statement is wrapped in ()
           * @param {ASTNode} node node to examine
           * @returns {boolean} the result
           */
          function isWrappedInParenthesis(node) {
              const regex = /^return\s*?\(\s*?\);*?/u;
  
              const statementWithoutArgument = sourceCode.getText(node).replace(
                  sourceCode.getText(node.argument), ""
              );
  
              return regex.test(statementWithoutArgument);
          }
  
          return {
              Program(node) {
                  if (node.body.length > 0) {
  
                      // Root nodes should have no indent
                      checkNodesIndent(node.body, getNodeIndent(node).goodChar);
                  }
              },
  
              ClassBody: blockIndentationCheck,
  
              BlockStatement: blockIndentationCheck,
  
              WhileStatement: blockLessNodes,
  
              ForStatement: blockLessNodes,
  
              ForInStatement: blockLessNodes,
  
              ForOfStatement: blockLessNodes,
  
              DoWhileStatement: blockLessNodes,
  
              IfStatement(node) {
                  if (node.consequent.type !== "BlockStatement" && node.consequent.loc.start.line > node.loc.start.line) {
                      blockIndentationCheck(node);
                  }
              },
  
              VariableDeclaration(node) {
                  if (node.declarations[node.declarations.length - 1].loc.start.line > node.declarations[0].loc.start.line) {
                      checkIndentInVariableDeclarations(node);
                  }
              },
  
              ObjectExpression(node) {
                  checkIndentInArrayOrObjectBlock(node);
              },
  
              ArrayExpression(node) {
                  checkIndentInArrayOrObjectBlock(node);
              },
  
              MemberExpression(node) {
  
                  if (typeof options.MemberExpression === "undefined") {
                      return;
                  }
  
                  if (isSingleLineNode(node)) {
                      return;
                  }
  
                  /*
                   * The typical layout of variable declarations and assignments
                   * alter the expectation of correct indentation. Skip them.
                   * TODO: Add appropriate configuration options for variable
                   * declarations and assignments.
                   */
                  if (getParentNodeByType(node, "VariableDeclarator", ["FunctionExpression", "ArrowFunctionExpression"])) {
                      return;
                  }
  
                  if (getParentNodeByType(node, "AssignmentExpression", ["FunctionExpression"])) {
                      return;
                  }
  
                  const propertyIndent = getNodeIndent(node).goodChar + indentSize * options.MemberExpression;
  
                  const checkNodes = [node.property];
  
                  const dot = sourceCode.getTokenBefore(node.property);
  
                  if (dot.type === "Punctuator" && dot.value === ".") {
                      checkNodes.push(dot);
                  }
  
                  checkNodesIndent(checkNodes, propertyIndent);
              },
  
              SwitchStatement(node) {
  
                  // Switch is not a 'BlockStatement'
                  const switchIndent = getNodeIndent(node).goodChar;
                  const caseIndent = expectedCaseIndent(node, switchIndent);
  
                  checkNodesIndent(node.cases, caseIndent);
  
  
                  checkLastNodeLineIndent(node, switchIndent);
              },
  
              SwitchCase(node) {
  
                  // Skip inline cases
                  if (isSingleLineNode(node)) {
                      return;
                  }
                  const caseIndent = expectedCaseIndent(node);
  
                  checkNodesIndent(node.consequent, caseIndent + indentSize);
              },
  
              FunctionDeclaration(node) {
                  if (isSingleLineNode(node)) {
                      return;
                  }
                  if (options.FunctionDeclaration.parameters === "first" && node.params.length) {
                      checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
                  } else if (options.FunctionDeclaration.parameters !== null) {
                      checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionDeclaration.parameters);
                  }
              },
  
              FunctionExpression(node) {
                  if (isSingleLineNode(node)) {
                      return;
                  }
                  if (options.FunctionExpression.parameters === "first" && node.params.length) {
                      checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
                  } else if (options.FunctionExpression.parameters !== null) {
                      checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionExpression.parameters);
                  }
              },
  
              ReturnStatement(node) {
                  if (isSingleLineNode(node)) {
                      return;
                  }
  
                  const firstLineIndent = getNodeIndent(node).goodChar;
  
                  // in case if return statement is wrapped in parenthesis
                  if (isWrappedInParenthesis(node)) {
                      checkLastReturnStatementLineIndent(node, firstLineIndent);
                  } else {
                      checkNodeIndent(node, firstLineIndent);
                  }
              },
  
              CallExpression(node) {
                  if (isSingleLineNode(node)) {
                      return;
                  }
                  if (options.CallExpression.arguments === "first" && node.arguments.length) {
                      checkNodesIndent(node.arguments.slice(1), node.arguments[0].loc.start.column);
                  } else if (options.CallExpression.arguments !== null) {
                      checkNodesIndent(node.arguments, getNodeIndent(node).goodChar + indentSize * options.CallExpression.arguments);
                  }
              }
  
          };
  
      }
  };
  