"use strict";

const { getLast } = require("../../common/util");
const {
  builders: { hardline },
} = require("../../document");
const pathNeedsParens = require("../needs-parens");
const {
  getLeftSidePathName,
  hasNakedLeftSide,
  isJsxNode,
  isTheOnlyJsxElementInMarkdown,
  hasComment,
  CommentCheckFlags,
  isNextLineEmpty,
} = require("../utils");
const { shouldPrintParamsWithoutParens } = require("./function");

/**
 * @typedef {import("../../document").Doc} Doc
 * @typedef {import("../../common/fast-path")} FastPath
 */

function printStatementSequence(path, options, print, property) {
  const node = path.getValue();
  const parts = [];
  const isClassBody = node.type === "ClassBody";
  const lastStatement = getLast(
    node[property].filter(({ type }) => type !== "EmptyStatement")
  );

  path.each((path, index, statements) => {
    const node = path.getValue();

    // Skip printing EmptyStatement nodes to avoid leaving stray
    // semicolons lying around.
    if (node.type === "EmptyStatement") {
      return;
    }

    const printed = print(path);

    // in no-semi mode, prepend statement with semicolon if it might break ASI
    // don't prepend the only JSX element in a program with semicolon
    if (
      !options.semi &&
      !isClassBody &&
      !isTheOnlyJsxElementInMarkdown(options, path) &&
      statementNeedsASIProtection(path, options)
    ) {
      if (hasComment(node, CommentCheckFlags.Leading)) {
        parts.push(print(path, { needsSemi: true }));
      } else {
        parts.push(";", printed);
      }
    } else {
      parts.push(printed);
    }

    if (!options.semi && isClassBody) {
      if (classPropMayCauseASIProblems(node)) {
        parts.push(";");
      } else if (
        node.type === "ClassProperty" ||
        node.type === "PropertyDefinition"
      ) {
        // `ClassBody` don't allow `EmptyStatement`,
        // so we can use `statements` to get next node
        if (classChildNeedsASIProtection(statements[index + 1])) {
          parts.push(";");
        }
      }
    }

    if (node !== lastStatement) {
      parts.push(hardline);

      if (isNextLineEmpty(node, options)) {
        parts.push(hardline);
      }
    }
  }, property);

  return parts;
}

function statementNeedsASIProtection(path, options) {
  const node = path.getNode();

  if (node.type !== "ExpressionStatement") {
    return false;
  }

  return path.call(
    (childPath) => expressionNeedsASIProtection(childPath, options),
    "expression"
  );
}

function expressionNeedsASIProtection(path, options) {
  const node = path.getValue();

  const maybeASIProblem =
    pathNeedsParens(path, options) ||
    node.type === "ParenthesizedExpression" ||
    node.type === "TypeCastExpression" ||
    (node.type === "ArrowFunctionExpression" &&
      !shouldPrintParamsWithoutParens(path, options)) ||
    node.type === "ArrayExpression" ||
    node.type === "ArrayPattern" ||
    (node.type === "UnaryExpression" &&
      node.prefix &&
      (node.operator === "+" || node.operator === "-")) ||
    node.type === "TemplateLiteral" ||
    node.type === "TemplateElement" ||
    isJsxNode(node) ||
    (node.type === "BindExpression" && !node.object) ||
    node.type === "RegExpLiteral" ||
    (node.type === "Literal" && node.pattern) ||
    (node.type === "Literal" && node.regex);

  if (maybeASIProblem) {
    return true;
  }

  if (!hasNakedLeftSide(node)) {
    return false;
  }

  return path.call(
    (childPath) => expressionNeedsASIProtection(childPath, options),
    ...getLeftSidePathName(path, node)
  );
}

function printBody(path, options, print) {
  return printStatementSequence(path, options, print, "body");
}

function printSwitchCaseConsequent(path, options, print) {
  return printStatementSequence(path, options, print, "consequent");
}

/**
 * @returns {boolean}
 */
function classPropMayCauseASIProblems(node) {
  if (node.type !== "ClassProperty" && node.type !== "PropertyDefinition") {
    return false;
  }

  const name = node.key && node.key.name;

  // this isn't actually possible yet with most parsers available today
  // so isn't properly tested yet.
  if (
    (name === "static" || name === "get" || name === "set") &&
    !node.value &&
    !node.typeAnnotation
  ) {
    return true;
  }

  return false;
}

/**
 * @returns {boolean}
 */
function classChildNeedsASIProtection(node) {
  if (!node) {
    return;
  }

  if (
    node.static ||
    node.accessibility // TypeScript
  ) {
    return false;
  }

  if (!node.computed) {
    const name = node.key && node.key.name;
    if (name === "in" || name === "instanceof") {
      return true;
    }
  }
  switch (node.type) {
    case "ClassProperty":
    case "PropertyDefinition":
    case "TSAbstractClassProperty":
      return node.computed;
    case "MethodDefinition": // Flow
    case "TSAbstractMethodDefinition": // TypeScript
    case "ClassMethod":
    case "ClassPrivateMethod": {
      // Babel
      const isAsync = node.value ? node.value.async : node.async;
      const isGenerator = node.value ? node.value.generator : node.generator;
      if (isAsync || node.kind === "get" || node.kind === "set") {
        return false;
      }
      if (node.computed || isGenerator) {
        return true;
      }
      return false;
    }
    case "TSIndexSignature":
      return true;
  }

  /* istanbul ignore next */
  return false;
}

module.exports = {
  printBody,
  printSwitchCaseConsequent,
};

@@ -12,58 +12,8 @@
 %22;%0A%0A
-const %7B getLast %7D = require(%22../../common/util%22);%0A
 cons
@@ -678,22 +678,26 @@
  getLast
-(%0A    
+Statement(
 node%5Bpro
@@ -706,59 +706,8 @@
 rty%5D
-.filter((%7B type %7D) =%3E type !== %22EmptyStatement%22)%0A  
 );%0A%0A
@@ -2143,24 +2143,238 @@
 n parts;%0A%7D%0A%0A
+function getLastStatement(statements) %7B%0A  for (let i = statements.length - 1; i %3E= 0; i--) %7B%0A    const statement = statements%5Bi%5D;%0A    if (statement.type !== %22EmptyStatement%22) %7B%0A      return statement;%0A    %7D%0A  %7D%0A%7D%0A%0A
 function sta

Use getLastStatement function in printStatementSequence (#10066)