<commit_before>package org.jetbrains.plugins.scala
package codeInspection.typeChecking

import org.jetbrains.plugins.scala.codeInspection.{AbstractFix, AbstractInspection}
import TypeCheckCanBeMatchInspection.inspectionId
import TypeCheckCanBeMatchInspection.inspectionName
import com.intellij.codeInspection.{ProblemDescriptor, ProblemHighlightType, ProblemsHolder}
import com.intellij.psi.PsiElement
import org.jetbrains.plugins.scala.lang.psi.api.expr._
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.plugins.scala.extensions.ElementText
import com.intellij.openapi.project.Project
import com.intellij.psi.tree.IElementType
import org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes
import org.jetbrains.plugins.scala.lang.psi.impl.ScalaPsiElementFactory
import org.jetbrains.plugins.scala.lang.psi.api.ScalaRecursiveElementVisitor
import org.jetbrains.plugins.scala.lang.psi.{ScalaPsiElement, ScalaPsiUtil}
import org.jetbrains.plugins.scala.lang.psi.api.statements.ScPatternDefinition
import org.jetbrains.plugins.scala.lang.refactoring.namesSuggester.NameSuggester
import scala.Some
import scala.collection.mutable
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.ScNamedElement
import org.jetbrains.plugins.scala.lang.psi.api.base.patterns.{ScPattern, ScBindingPattern}
import org.jetbrains.plugins.scala.lang.psi.api.base.ScReferenceElement
import org.jetbrains.plugins.scala.lang.psi.impl.toplevel.synthetic.{SyntheticNamedElement, ScSyntheticFunction}
import java.util.Comparator
import org.jetbrains.plugins.scala.lang.psi.api.statements.params.ScParameter
import com.intellij.codeInsight.PsiEquivalenceUtil
import scala.annotation.tailrec
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.plugins.scala.lang.refactoring.rename.GroupInplaceRenamer
import org.jetbrains.plugins.scala.lang.refactoring.util.ScalaVariableValidator
import TypeCheckToMatchUtil._
import scala.annotation.tailrec
import extensions.inWriteAction

/**
 * Nikolay.Tropin
 * 5/6/13
 */
object TypeCheckCanBeMatchInspection {
  val inspectionId = "TypeCheckCanBeMatch"
  val inspectionName = "Type check can be replaced by pattern matching"
}

class TypeCheckCanBeMatchInspection extends AbstractInspection(inspectionId, inspectionName){

  def actionFor(holder: ProblemsHolder): PartialFunction[PsiElement, Any] = {
    case call: ScGenericCall if isIsInstOfCall(call) =>
      for {
        ifStmt <- Option(PsiTreeUtil.getParentOfType(call, classOf[ScIfStmt]))
        condition <- ifStmt.condition
        iioCall <- findIsInstanceOfCalls(condition, onlyFirst = true)
        if iioCall == call
        if typeCheckIsUsedEnough(ifStmt, call)
      } {
        val fix = new TypeCheckCanBeMatchQuickFix(call, ifStmt)
        holder.registerProblem(call, inspectionId, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fix)
      }
  }

  private def typeCheckIsUsedEnough (ifStmt: ScIfStmt, isInstOf: ScGenericCall): Boolean = {
    val chainSize = listOfIfAndIsInstOf(ifStmt, isInstOf, onlyFirst = true).size
    val typeCastsNumber = findAsInstOfCalls(ifStmt.condition, isInstOf).size + findAsInstOfCalls(ifStmt.thenBranch, isInstOf).size
    chainSize > 1 || typeCastsNumber > 0
  }
}

class TypeCheckCanBeMatchQuickFix(isInstOfUnderFix: ScGenericCall, ifStmt: ScIfStmt) extends AbstractFix(inspectionName, isInstOfUnderFix) {
  def doApplyFix(project: Project, descriptor: ProblemDescriptor) {
    if (!ifStmt.isValid || !isInstOfUnderFix.isValid) return
    val (matchStmtOption, renameData) = buildMatchStmt(ifStmt, isInstOfUnderFix, onlyFirst = true)
    for (matchStmt <- matchStmtOption) {
      val newMatch = inWriteAction {
        ifStmt.replaceExpression(matchStmt, removeParenthesis = true).asInstanceOf[ScMatchStmt]
      }
      if (!ApplicationManager.getApplication.isUnitTestMode) {
        val renamer = new GroupInplaceRenamer(newMatch)
        setElementsForRename(newMatch, renamer, renameData)
        renamer.startRenaming()
      }
    }
  }
}

object TypeCheckToMatchUtil {
  type RenameData = collection.mutable.ArrayBuffer[(Int, Seq[String])]

  def buildMatchStmt(ifStmt: ScIfStmt, isInstOfUnderFix: ScGenericCall, onlyFirst: Boolean): (Option[ScMatchStmt], RenameData) = {
    baseExpr(isInstOfUnderFix) match {
      case Some(expr: ScExpression) =>
        val matchedExprText = expr.getText
        val (caseClausesText, renameData) = buildCaseClausesText(ifStmt, isInstOfUnderFix, onlyFirst)
        val matchStmtText = s"$matchedExprText match { \n " + caseClausesText + "}"
        val matchStmt = ScalaPsiElementFactory.createExpressionFromText(matchStmtText, ifStmt.getManager).asInstanceOf[ScMatchStmt]
        (Some(matchStmt), renameData)
      case _ => (None, null)
    }
  }

  private def buildCaseClauseText(ifStmt: ScIfStmt, isInstOf: ScGenericCall, caseClauseIndex: Int, renameData: RenameData): Option[String] = {
    var definedName: Option[String] = None
    var definition: Option[ScPatternDefinition] = None

    //method for finding and saving named type cast
    def checkAndStoreNameAndDef(asInstOfCall: ScGenericCall): Boolean = {
      ScalaPsiUtil.getContextOfType(asInstOfCall, strict = true, classOf[ScPatternDefinition]) match {
        case patternDef: ScPatternDefinition =>
          val bindings = patternDef.bindings
          //pattern consist of one assignment of asInstanceOf call
          if (bindings.size == 1 && patternDef.expr.get == asInstOfCall) {
            definition match {
              //store first occurence of pattern definition and name
              case Some(oldDef) if oldDef.getTextOffset < patternDef.getTextOffset => true
              case _ =>
                definedName = Some(bindings(0).getName)
                definition = Some(patternDef)
                true
            }
          } else false
        case null => false
      }
    }

    for {
      args <- isInstOf.typeArgs
      condition <- ifStmt.condition

    } yield {
      val scType = args.typeArgs(0).calcType
      val typeName = scType.presentableText
      val asInstOfInBody = findAsInstOfCalls(ifStmt.thenBranch, isInstOf)
      val guardCond = guardCondition(condition, isInstOf)
      val asInstOfInGuard = findAsInstOfCalls(guardCond, isInstOf)
      val asInstOfEverywhere = asInstOfInBody ++ asInstOfInGuard

      if (asInstOfInBody.count(checkAndStoreNameAndDef(_)) == 0) {
        //no usage of asInstanceOf
        if (asInstOfEverywhere.size == 0) {
          buildCaseClauseText("_ : " + typeName, guardCond, ifStmt.thenBranch)
        }
        //no named usage
        else {
          val suggestedNames: Array[String] = NameSuggester.suggestNames(asInstOfEverywhere(0),
            new ScalaVariableValidator(null, ifStmt.getProject, ifStmt, false, ifStmt.getParent, ifStmt.getParent))
          val name = suggestedNames(0)
          asInstOfEverywhere.foreach { c =>
            val newExpr = ScalaPsiElementFactory.createExpressionFromText(name, ifStmt.getManager)
            inWriteAction {
              c.replaceExpression(newExpr, removeParenthesis = true)
            }
          }

          renameData += ((caseClauseIndex, suggestedNames.toSeq))
          buildCaseClauseText(s"$name : $typeName", guardCond, ifStmt.thenBranch)
        }
      }
      //have named usage, use this name in case clause pattern definition
      else {
        //deleting unnecessary val declaration
        val patternDef = definition.get
        inWriteAction {
          patternDef.delete()
        }
        val name = definedName.get
        val newExpr = ScalaPsiElementFactory.createExpressionFromText(name, ifStmt.getManager)
        inWriteAction {
          asInstOfEverywhere.foreach(_.replaceExpression(newExpr, removeParenthesis = true))
        }
        buildCaseClauseText(s"$name : $typeName", guardCond, ifStmt.thenBranch)
      }
    }
  }

  private def buildDefaultCaseClauseText(body: Option[ScExpression]): Option[String] =  {
    Some(buildCaseClauseText("_ ", None, body))
  }

  private def buildCaseClauseText(patternText: String, guardCondition: Option[ScExpression], body: Option[ScExpression]): String = {
    val builder = new StringBuilder
    builder.append("case ").append(patternText)
    guardCondition.map(cond => builder.append(" if " + cond.getText))
    builder.append(" =>")
    body match {
      case Some(block: ScBlock) =>
        for (elem <- block.children) {
          val elementType: IElementType = elem.getNode.getElementType
          if (elementType != ScalaTokenTypes.tLBRACE && elementType != ScalaTokenTypes.tRBRACE)
            builder.append(elem.getText)
        }
      case Some(expr: ScExpression) => builder.append(expr.getText)
      case None =>
    }
    if (!builder.last.isWhitespace) builder.append("\n")
    builder.toString()
  }

  def listOfIfAndIsInstOf(currentIfStmt: ScIfStmt, currentCall: ScGenericCall, onlyFirst: Boolean): List[(ScIfStmt, ScGenericCall)] = {
    for (currentBase <- baseExpr(currentCall)) {
      currentIfStmt.elseBranch match {
        case Some(nextIfStmt: ScIfStmt) =>
          for {
            nextCond <- nextIfStmt.condition
            nextCall <- findIsInstanceOfCalls(nextCond, onlyFirst)
            nextBase <- baseExpr(nextCall)
            if equiv(currentBase, nextBase)
          } {
            return (currentIfStmt, currentCall) :: listOfIfAndIsInstOf(nextIfStmt, nextCall, onlyFirst)
          }
          return (currentIfStmt, currentCall) :: Nil
        case _ => return (currentIfStmt, currentCall) :: Nil
      }
    }
    Nil
  }

  private def buildCaseClausesText(ifStmt: ScIfStmt, isInstOfUnderFix: ScGenericCall, onlyFirst: Boolean): (String, RenameData) = {

    val builder = new StringBuilder
    val (ifStmts, isInstOf) = listOfIfAndIsInstOf(ifStmt, isInstOfUnderFix, onlyFirst).unzip

    val renameData = new RenameData()
    for {
      index <- 0 until ifStmts.size
      text <- buildCaseClauseText(ifStmts(index), isInstOf(index), index, renameData)
    } {
      builder.append(text)
    }

    if (ifStmts != Nil) {
      val lastElse = ifStmts.last.elseBranch
      val defaultText: Option[String] = buildDefaultCaseClauseText(lastElse)
      defaultText.foreach(builder.append(_))
    }

    (builder.toString(), renameData)
  }

  @tailrec
  def findIsInstanceOfCalls(condition: ScExpression, onlyFirst: Boolean): List[ScGenericCall] = {
    if (onlyFirst) {
      condition match {
        case call: ScGenericCall if isIsInstOfCall(call) => List(call)
        case infixExpr: ScInfixExpr if infixExpr.operation.refName == "&&" => findIsInstanceOfCalls(infixExpr.lOp, onlyFirst)
        case parenth: ScParenthesisedExpr => findIsInstanceOfCalls(parenth.expr.getOrElse(null), onlyFirst)
        case _ => Nil
      }
    }
    else {
      separateConditions(condition).
              filter(isIsInstOfCall(_)).
              map(_.asInstanceOf[ScGenericCall])
    }
  }

  def findAsInstOfCalls(body: Option[ScExpression], isInstOfCall: ScGenericCall): Seq[ScGenericCall] = {
    def isAsInstOfCall(genCall: ScGenericCall) = {
      genCall.referencedExpr match {
        case ref: ScReferenceExpression if ref.refName == "asInstanceOf" =>
          ref.resolve() match {
            case synth: SyntheticNamedElement => true
            case _ => false
          }
        case _ => false
      }
    }

    def equalTypes(firstCall: ScGenericCall, secondCall: ScGenericCall): Boolean = {
      val option = for {
        firstArgs <- firstCall.typeArgs
        secondArgs <- secondCall.typeArgs
      } yield {
        val firstType = firstArgs.typeArgs(0).calcType
        val secondType = secondArgs.typeArgs(0).calcType
        firstType.equiv(secondType)
      }
      option.getOrElse(false)
    }

    val result = collection.mutable.ArrayBuffer[ScGenericCall]()
    val visitor = new ScalaRecursiveElementVisitor() {
      override def visitGenericCallExpression(call: ScGenericCall) {
        for {
          base1 <- baseExpr(isInstOfCall)
          base2 <- baseExpr(call)
          if isAsInstOfCall(call)
          if equalTypes(call, isInstOfCall)
          if equiv(base1, base2)
        } {
          result += call
        }
        super.visitGenericCallExpression(call)
      }
    }

    for (expr <- body) expr.accept(visitor)
    result
  }

  def setElementsForRename(matchStmt: ScMatchStmt, renamer: GroupInplaceRenamer, renameData: RenameData) {
    val caseClauses = matchStmt.caseClauses.toList

    for {
      (index, suggestedNames) <- renameData
      caseClause = caseClauses(index)
      name = suggestedNames(0)
    } {
      val primary = mutable.ArrayBuffer[ScNamedElement]()
      val dependents = mutable.SortedSet()(Ordering.by[ScalaPsiElement, Int](_.getTextOffset))

      val patternVisitor = new ScalaRecursiveElementVisitor() {
        override def visitPattern(pat: ScPattern) {
          pat match {
            case bp: ScBindingPattern if bp.name == name =>
              primary += bp
            case _ =>
          }
          super.visitPattern(pat)
        }
      }

      val referenceVisitor = new ScalaRecursiveElementVisitor() {
        override def visitReferenceExpression(ref: ScReferenceExpression) {
          for (prim <- primary) {
            if (ref.refName == name && ref.resolve() == prim)
              dependents += ref
          }
          super.visitReferenceExpression(ref)
        }
      }

      caseClause.accept(patternVisitor)
      caseClause.accept(referenceVisitor)
      for (prim <- primary) renamer.addGroup(prim, dependents.toList, suggestedNames)
    }
  }

  def baseExpr(gCall: ScGenericCall): Option[ScExpression] = gCall.referencedExpr.children.toList match {
    case List(expr: ScExpression, ElementText("."), ElementText(_)) => Some(expr)
    case _ => None
  }

  private def guardCondition(condition: ScExpression, isInstOfCall: ScGenericCall): Option[ScExpression] =  {
    val conditions = separateConditions(condition)
    conditions match {
      case Nil => None
      case _ =>
        val guardConditions: List[ScExpression] = conditions.filterNot(equiv(_, isInstOfCall))
        val guardConditionsText: String = guardConditions.map(_.getText).mkString(" && ")
        val guard = ScalaPsiElementFactory.createExpressionFromText(guardConditionsText, condition).asInstanceOf[ScExpression]

        if (guard == null) None
        else Some(guard)
    }<commit_msg>baseExpr() method changed<commit_after>package org.jetbrains.plugins.scala
package codeInspection.typeChecking

import org.jetbrains.plugins.scala.codeInspection.{AbstractFix, AbstractInspection}
import TypeCheckCanBeMatchInspection.inspectionId
import TypeCheckCanBeMatchInspection.inspectionName
import com.intellij.codeInspection.{ProblemDescriptor, ProblemHighlightType, ProblemsHolder}
import com.intellij.psi.PsiElement
import org.jetbrains.plugins.scala.lang.psi.api.expr._
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.plugins.scala.extensions.ElementText
import com.intellij.openapi.project.Project
import com.intellij.psi.tree.IElementType
import org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes
import org.jetbrains.plugins.scala.lang.psi.impl.ScalaPsiElementFactory
import org.jetbrains.plugins.scala.lang.psi.api.ScalaRecursiveElementVisitor
import org.jetbrains.plugins.scala.lang.psi.{ScalaPsiElement, ScalaPsiUtil}
import org.jetbrains.plugins.scala.lang.psi.api.statements.ScPatternDefinition
import org.jetbrains.plugins.scala.lang.refactoring.namesSuggester.NameSuggester
import scala.Some
import scala.collection.mutable
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.ScNamedElement
import org.jetbrains.plugins.scala.lang.psi.api.base.patterns.{ScPattern, ScBindingPattern}
import org.jetbrains.plugins.scala.lang.psi.api.base.ScReferenceElement
import org.jetbrains.plugins.scala.lang.psi.impl.toplevel.synthetic.{SyntheticNamedElement, ScSyntheticFunction}
import java.util.Comparator
import org.jetbrains.plugins.scala.lang.psi.api.statements.params.ScParameter
import com.intellij.codeInsight.PsiEquivalenceUtil
import scala.annotation.tailrec
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.plugins.scala.lang.refactoring.rename.GroupInplaceRenamer
import org.jetbrains.plugins.scala.lang.refactoring.util.ScalaVariableValidator
import TypeCheckToMatchUtil._
import scala.annotation.tailrec
import extensions.inWriteAction

/**
 * Nikolay.Tropin
 * 5/6/13
 */
object TypeCheckCanBeMatchInspection {
  val inspectionId = "TypeCheckCanBeMatch"
  val inspectionName = "Type check can be replaced by pattern matching"
}

class TypeCheckCanBeMatchInspection extends AbstractInspection(inspectionId, inspectionName){

  def actionFor(holder: ProblemsHolder): PartialFunction[PsiElement, Any] = {
    case call: ScGenericCall if isIsInstOfCall(call) =>
      for {
        ifStmt <- Option(PsiTreeUtil.getParentOfType(call, classOf[ScIfStmt]))
        condition <- ifStmt.condition
        iioCall <- findIsInstanceOfCalls(condition, onlyFirst = true)
        if iioCall == call
        if typeCheckIsUsedEnough(ifStmt, call)
      } {
        val fix = new TypeCheckCanBeMatchQuickFix(call, ifStmt)
        holder.registerProblem(call, inspectionId, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, fix)
      }
  }

  private def typeCheckIsUsedEnough (ifStmt: ScIfStmt, isInstOf: ScGenericCall): Boolean = {
    val chainSize = listOfIfAndIsInstOf(ifStmt, isInstOf, onlyFirst = true).size
    val typeCastsNumber = findAsInstOfCalls(ifStmt.condition, isInstOf).size + findAsInstOfCalls(ifStmt.thenBranch, isInstOf).size
    chainSize > 1 || typeCastsNumber > 0
  }
}

class TypeCheckCanBeMatchQuickFix(isInstOfUnderFix: ScGenericCall, ifStmt: ScIfStmt) extends AbstractFix(inspectionName, isInstOfUnderFix) {
  def doApplyFix(project: Project, descriptor: ProblemDescriptor) {
    if (!ifStmt.isValid || !isInstOfUnderFix.isValid) return
    val (matchStmtOption, renameData) = buildMatchStmt(ifStmt, isInstOfUnderFix, onlyFirst = true)
    for (matchStmt <- matchStmtOption) {
      val newMatch = inWriteAction {
        ifStmt.replaceExpression(matchStmt, removeParenthesis = true).asInstanceOf[ScMatchStmt]
      }
      if (!ApplicationManager.getApplication.isUnitTestMode) {
        val renamer = new GroupInplaceRenamer(newMatch)
        setElementsForRename(newMatch, renamer, renameData)
        renamer.startRenaming()
      }
    }
  }
}

object TypeCheckToMatchUtil {
  type RenameData = collection.mutable.ArrayBuffer[(Int, Seq[String])]

  def buildMatchStmt(ifStmt: ScIfStmt, isInstOfUnderFix: ScGenericCall, onlyFirst: Boolean): (Option[ScMatchStmt], RenameData) = {
    baseExpr(isInstOfUnderFix) match {
      case Some(expr: ScExpression) =>
        val matchedExprText = expr.getText
        val (caseClausesText, renameData) = buildCaseClausesText(ifStmt, isInstOfUnderFix, onlyFirst)
        val matchStmtText = s"$matchedExprText match { \n " + caseClausesText + "}"
        val matchStmt = ScalaPsiElementFactory.createExpressionFromText(matchStmtText, ifStmt.getManager).asInstanceOf[ScMatchStmt]
        (Some(matchStmt), renameData)
      case _ => (None, null)
    }
  }

  private def buildCaseClauseText(ifStmt: ScIfStmt, isInstOf: ScGenericCall, caseClauseIndex: Int, renameData: RenameData): Option[String] = {
    var definedName: Option[String] = None
    var definition: Option[ScPatternDefinition] = None

    //method for finding and saving named type cast
    def checkAndStoreNameAndDef(asInstOfCall: ScGenericCall): Boolean = {
      ScalaPsiUtil.getContextOfType(asInstOfCall, strict = true, classOf[ScPatternDefinition]) match {
        case patternDef: ScPatternDefinition =>
          val bindings = patternDef.bindings
          //pattern consist of one assignment of asInstanceOf call
          if (bindings.size == 1 && patternDef.expr.get == asInstOfCall) {
            definition match {
              //store first occurence of pattern definition and name
              case Some(oldDef) if oldDef.getTextOffset < patternDef.getTextOffset => true
              case _ =>
                definedName = Some(bindings(0).getName)
                definition = Some(patternDef)
                true
            }
          } else false
        case null => false
      }
    }

    for {
      args <- isInstOf.typeArgs
      condition <- ifStmt.condition

    } yield {
      val scType = args.typeArgs(0).calcType
      val typeName = scType.presentableText
      val asInstOfInBody = findAsInstOfCalls(ifStmt.thenBranch, isInstOf)
      val guardCond = guardCondition(condition, isInstOf)
      val asInstOfInGuard = findAsInstOfCalls(guardCond, isInstOf)
      val asInstOfEverywhere = asInstOfInBody ++ asInstOfInGuard

      if (asInstOfInBody.count(checkAndStoreNameAndDef(_)) == 0) {
        //no usage of asInstanceOf
        if (asInstOfEverywhere.size == 0) {
          buildCaseClauseText("_ : " + typeName, guardCond, ifStmt.thenBranch)
        }
        //no named usage
        else {
          val suggestedNames: Array[String] = NameSuggester.suggestNames(asInstOfEverywhere(0),
            new ScalaVariableValidator(null, ifStmt.getProject, ifStmt, false, ifStmt.getParent, ifStmt.getParent))
          val name = suggestedNames(0)
          asInstOfEverywhere.foreach { c =>
            val newExpr = ScalaPsiElementFactory.createExpressionFromText(name, ifStmt.getManager)
            inWriteAction {
              c.replaceExpression(newExpr, removeParenthesis = true)
            }
          }

          renameData += ((caseClauseIndex, suggestedNames.toSeq))
          buildCaseClauseText(s"$name : $typeName", guardCond, ifStmt.thenBranch)
        }
      }
      //have named usage, use this name in case clause pattern definition
      else {
        //deleting unnecessary val declaration
        val patternDef = definition.get
        inWriteAction {
          patternDef.delete()
        }
        val name = definedName.get
        val newExpr = ScalaPsiElementFactory.createExpressionFromText(name, ifStmt.getManager)
        inWriteAction {
          asInstOfEverywhere.foreach(_.replaceExpression(newExpr, removeParenthesis = true))
        }
        buildCaseClauseText(s"$name : $typeName", guardCond, ifStmt.thenBranch)
      }
    }
  }

  private def buildDefaultCaseClauseText(body: Option[ScExpression]): Option[String] =  {
    Some(buildCaseClauseText("_ ", None, body))
  }

  private def buildCaseClauseText(patternText: String, guardCondition: Option[ScExpression], body: Option[ScExpression]): String = {
    val builder = new StringBuilder
    builder.append("case ").append(patternText)
    guardCondition.map(cond => builder.append(" if " + cond.getText))
    builder.append(" =>")
    body match {
      case Some(block: ScBlock) =>
        for (elem <- block.children) {
          val elementType: IElementType = elem.getNode.getElementType
          if (elementType != ScalaTokenTypes.tLBRACE && elementType != ScalaTokenTypes.tRBRACE)
            builder.append(elem.getText)
        }
      case Some(expr: ScExpression) => builder.append(expr.getText)
      case None =>
    }
    if (!builder.last.isWhitespace) builder.append("\n")
    builder.toString()
  }

  def listOfIfAndIsInstOf(currentIfStmt: ScIfStmt, currentCall: ScGenericCall, onlyFirst: Boolean): List[(ScIfStmt, ScGenericCall)] = {
    for (currentBase <- baseExpr(currentCall)) {
      currentIfStmt.elseBranch match {
        case Some(nextIfStmt: ScIfStmt) =>
          for {
            nextCond <- nextIfStmt.condition
            nextCall <- findIsInstanceOfCalls(nextCond, onlyFirst)
            nextBase <- baseExpr(nextCall)
            if equiv(currentBase, nextBase)
          } {
            return (currentIfStmt, currentCall) :: listOfIfAndIsInstOf(nextIfStmt, nextCall, onlyFirst)
          }
          return (currentIfStmt, currentCall) :: Nil
        case _ => return (currentIfStmt, currentCall) :: Nil
      }
    }
    Nil
  }

  private def buildCaseClausesText(ifStmt: ScIfStmt, isInstOfUnderFix: ScGenericCall, onlyFirst: Boolean): (String, RenameData) = {

    val builder = new StringBuilder
    val (ifStmts, isInstOf) = listOfIfAndIsInstOf(ifStmt, isInstOfUnderFix, onlyFirst).unzip

    val renameData = new RenameData()
    for {
      index <- 0 until ifStmts.size
      text <- buildCaseClauseText(ifStmts(index), isInstOf(index), index, renameData)
    } {
      builder.append(text)
    }

    if (ifStmts != Nil) {
      val lastElse = ifStmts.last.elseBranch
      val defaultText: Option[String] = buildDefaultCaseClauseText(lastElse)
      defaultText.foreach(builder.append(_))
    }

    (builder.toString(), renameData)
  }

  @tailrec
  def findIsInstanceOfCalls(condition: ScExpression, onlyFirst: Boolean): List[ScGenericCall] = {
    if (onlyFirst) {
      condition match {
        case call: ScGenericCall if isIsInstOfCall(call) => List(call)
        case infixExpr: ScInfixExpr if infixExpr.operation.refName == "&&" => findIsInstanceOfCalls(infixExpr.lOp, onlyFirst)
        case parenth: ScParenthesisedExpr => findIsInstanceOfCalls(parenth.expr.getOrElse(null), onlyFirst)
        case _ => Nil
      }
    }
    else {
      separateConditions(condition).
              filter(isIsInstOfCall(_)).
              map(_.asInstanceOf[ScGenericCall])
    }
  }

  def findAsInstOfCalls(body: Option[ScExpression], isInstOfCall: ScGenericCall): Seq[ScGenericCall] = {
    def isAsInstOfCall(genCall: ScGenericCall) = {
      genCall.referencedExpr match {
        case ref: ScReferenceExpression if ref.refName == "asInstanceOf" =>
          ref.resolve() match {
            case synth: SyntheticNamedElement => true
            case _ => false
          }
        case _ => false
      }
    }

    def equalTypes(firstCall: ScGenericCall, secondCall: ScGenericCall): Boolean = {
      val option = for {
        firstArgs <- firstCall.typeArgs
        secondArgs <- secondCall.typeArgs
      } yield {
        val firstType = firstArgs.typeArgs(0).calcType
        val secondType = secondArgs.typeArgs(0).calcType
        firstType.equiv(secondType)
      }
      option.getOrElse(false)
    }

    val result = collection.mutable.ArrayBuffer[ScGenericCall]()
    val visitor = new ScalaRecursiveElementVisitor() {
      override def visitGenericCallExpression(call: ScGenericCall) {
        for {
          base1 <- baseExpr(isInstOfCall)
          base2 <- baseExpr(call)
          if isAsInstOfCall(call)
          if equalTypes(call, isInstOfCall)
          if equiv(base1, base2)
        } {
          result += call
        }
        super.visitGenericCallExpression(call)
      }
    }

    for (expr <- body) expr.accept(visitor)
    result
  }

  def setElementsForRename(matchStmt: ScMatchStmt, renamer: GroupInplaceRenamer, renameData: RenameData) {
    val caseClauses = matchStmt.caseClauses.toList

    for {
      (index, suggestedNames) <- renameData
      caseClause = caseClauses(index)
      name = suggestedNames(0)
    } {
      val primary = mutable.ArrayBuffer[ScNamedElement]()
      val dependents = mutable.SortedSet()(Ordering.by[ScalaPsiElement, Int](_.getTextOffset))

      val patternVisitor = new ScalaRecursiveElementVisitor() {
        override def visitPattern(pat: ScPattern) {
          pat match {
            case bp: ScBindingPattern if bp.name == name =>
              primary += bp
            case _ =>
          }
          super.visitPattern(pat)
        }
      }

      val referenceVisitor = new ScalaRecursiveElementVisitor() {
        override def visitReferenceExpression(ref: ScReferenceExpression) {
          for (prim <- primary) {
            if (ref.refName == name && ref.resolve() == prim)
              dependents += ref
          }
          super.visitReferenceExpression(ref)
        }
      }

      caseClause.accept(patternVisitor)
      caseClause.accept(referenceVisitor)
      for (prim <- primary) renamer.addGroup(prim, dependents.toList, suggestedNames)
    }
  }

  def baseExpr(gCall: ScGenericCall): Option[ScExpression] = gCall.referencedExpr match {
    case ref: ScReferenceExpression => ref.qualifier
    case _ => None
  }

  private def guardCondition(condition: ScExpression, isInstOfCall: ScGenericCall): Option[ScExpression] =  {
    val conditions = separateConditions(condition)
    conditions match {
      case Nil => None
      case _ =>
        val guardConditions: List[ScExpression] = conditions.filterNot(equiv(_, isInstOfCall))
        val guardConditionsText: String = guardConditions.map(_.getText).mkString(" && ")
        val guard = ScalaPsiElementFactory.createExpressionFromText(guardConditionsText, condition).asInstanceOf[ScExpression]

        if (guard == null) None
        else Some(guard)
    }
{'fraction_non_alphanumeric': 0.09311839140232275, 'fraction_numerical': 0.0014560582423296933, 'mean_word_length': 3.289368029739777, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 65, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '424641', 'n_tokens_mistral': 8555, 'n_tokens_neox': 8445, 'n_words': 1644}