<commit_before>package org.jetbrains.plugins.scala.testingSupport.test.ui

import com.intellij.codeInsight.TestFrameworks
import com.intellij.execution.TestStateStorage
import com.intellij.execution.lineMarker.{ExecutorAction, RunLineMarkerContributor}
import com.intellij.execution.testframework.TestIconMapper
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo.Magnitude
import com.intellij.icons.AllIcons.RunConfigurations.TestState
import com.intellij.openapi.project.Project
import com.intellij.psi._
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.testIntegration.TestRunLineMarkerProvider

import javax.swing.Icon
import org.jetbrains.plugins.scala.ScalaBundle
import org.jetbrains.plugins.scala.extensions._
import org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes
import org.jetbrains.plugins.scala.lang.psi.api.ScalaPsiElement
import org.jetbrains.plugins.scala.lang.psi.api.expr.ScReferenceExpression
import org.jetbrains.plugins.scala.lang.psi.api.statements.ScFunctionDefinition
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.{ScClass, ScObject, ScTrait, ScTypeDefinition}
import org.jetbrains.plugins.scala.macroAnnotations.Measure
import org.jetbrains.plugins.scala.testingSupport.test.AbstractTestFramework
import org.jetbrains.plugins.scala.testingSupport.test.munit.{MUnitTestFramework, MUnitTestLocationsFinder, MUnitUtils}
import org.jetbrains.plugins.scala.testingSupport.test.scalatest.{ScalaTestTestFramework, ScalaTestTestLocationsFinder}
import org.jetbrains.plugins.scala.testingSupport.test.specs2.Specs2TestFramework
import org.jetbrains.plugins.scala.testingSupport.test.utest.UTestTestFramework

import scala.jdk.CollectionConverters.IterableHasAsScala

// TODO: split providers by test frameworks, if some logic should be reused, just move to some base/utility classes
class ScalaTestRunLineMarkerProvider extends TestRunLineMarkerProvider {

  /**
   * for scalatest location hint format see:
   *  - [[org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestReporter]]
   *  - [[org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestReporterWithLocation]]
   *  - [[org.jetbrains.plugins.scala.testingSupport.locationProvider.ScalaTestLocationProvider]]
   *
   * for uTest  location hint format see
   *  - [[org.jetbrains.plugins.scala.testingSupport.uTest.UTestReporter]]
   */
  override def getInfo(element: PsiElement): RunLineMarkerContributor.Info =
    element match {
      case leaf: LeafPsiElement if leaf.getElementType == ScalaTokenTypes.tIDENTIFIER =>
        infoForLeafElement(leaf).orNull
      case _                                                                          =>
        null
    }

  // REMINDER from codeInsight.daemon.LineMarkerInfo:
  // LineMarker is supposed to be registered for leaf elements only!
  private def infoForLeafElement(leaf: LeafPsiElement): Option[RunLineMarkerContributor.Info] = {
    val parent = leaf.getParent
    parent match {
      case o: ScObject =>
        infoForScalaTestRefSpec(o) match {
          case info@Some(_) => info
          case _ => infoForClass(o)
        }
      case clazz: PsiClass => infoForClass(clazz)
      case method: PsiMethod => infoForMethod(method)
      case ref: ScReferenceExpression => infoForTestMethodRef(ref)
      case _ => None
    }
  }

  private def infoForClass(clazz: PsiClass): Option[RunLineMarkerContributor.Info] = {
    val framework = TestFrameworks.detectFramework(clazz)
    if (framework == null || !framework.isTestClass(clazz)) return None

    val url = framework match {
      case _: ScalaTestTestFramework =>
        clazz match {
          case _: ScTrait | _: ScObject => None
          case _ => Some(s"scalatest://TopOfClass:${clazz.qualifiedName}TestName:${clazz.name}")
        }
      //FIXME: why the hell uTest url is reported with scalatest prefix? (see UTestReporter)
      case _: UTestTestFramework  => Some(s"scalatest://TopOfClass:${clazz.qualifiedName}TestName:${clazz.qualifiedName}") // TODO: check maybe it should be clazz.name
      case _: Specs2TestFramework => Some(s"") // TODO: spec2 runner does not report location for class currently
      case _                      => Some(s"java:suite://${clazz.qualifiedName}")
    }
    url.map(buildLineInfo(_, clazz.getProject, isClass = true))
  }

  // REMINDER: we shouldn't do any long resolve or some other heavy computation,
  // because we can't block UI thread, we already have a lag in a context menu on a single method (right click),
  // not talking about dozens of methods which should be resolved here
  @Measure
  private def infoForMethod(method: PsiMethod): Option[RunLineMarkerContributor.Info] = {
    val clazz: PsiClass = PsiTreeUtil.getTopmostParentOfType(method, classOf[PsiClass])

    if (clazz == null)
      return None

    val framework = TestFrameworks.detectFramework(clazz)

    if (framework == null)
      return None

    (framework, method) match {
      case (_: ScalaTestTestFramework, f: ScFunctionDefinition) =>
        infoForScalaTestRefSpec(f)
      case _ if framework.isTestMethod(method) =>
        Some(buildLineInfo(
          s"java:test://${clazz.qualifiedName}.${method.getName}",
          method.getProject,
          isClass = false
        ))
      case _ => None
    }
  }

  private def infoForTestMethodRef(ref: ScReferenceExpression): Option[RunLineMarkerContributor.Info] = {
    val definition = PsiTreeUtil.getParentOfType(ref, classOf[ScTypeDefinition])
    if (definition == null)
      return None

    // NOT: for MUnit more than one framework is applicable: MUnit and JUnit, and JUnit is detected first =(
    val frameworks = TestFrameworks.detectApplicableFrameworks(definition).asScala
    val scalaFramework = frameworks.filterByType[AbstractTestFramework].headOption

    scalaFramework.flatMap {
      case _: ScalaTestTestFramework => infoForScalaTestMethodRef(ref, definition)
      case _: MUnitTestFramework     => infoForMUnitMethodRef(ref, definition)
      case _                         => None
    }
  }

  private def infoForScalaTestRefSpec(functionOrObject: ScalaPsiElement): Option[RunLineMarkerContributor.Info] = {
    val definition = PsiTreeUtil.getParentOfType(functionOrObject, classOf[ScClass])

    if (definition == null)
      return None

    val frameworks = TestFrameworks.detectApplicableFrameworks(definition).asScala
    val scalaFramework = frameworks.filterByType[AbstractTestFramework].headOption

    scalaFramework.flatMap {
      case _: ScalaTestTestFramework =>
        functionOrObject match {
          case f: ScFunctionDefinition => infoForScalaTestRefSpecFunctionDefinition(f, definition)
          case o: ScObject => infoForScalaTestRefSpecObject(o, definition)
          case _ => None
        }
      case _ => None
    }
  }

  private def infoForScalaTestMethodRef(ref: ScReferenceExpression, definition: ScTypeDefinition): Option[RunLineMarkerContributor.Info] = {
    val locations = ScalaTestTestLocationsFinder.calculateTestLocations(definition)
    if (locations.contains(ref)) {
      val url = scalaTestLineUrl(ref, definition, testName = "")
      val info = buildLineInfo(url, ref.getProject, isClass = false)
      Some(info)
    }
    else None
  }

  private def infoForScalaTestRefSpecFunctionDefinition(functionDefinition: ScFunctionDefinition, definition: ScTypeDefinition): Option[RunLineMarkerContributor.Info] = {
    val locations = ScalaTestTestLocationsFinder.calculateTestLocations(definition)
    if (locations.contains(functionDefinition)) {
      val url = scalaTestLineUrl(functionDefinition, definition, testName = "")
      val info = buildLineInfo(url, functionDefinition.getProject, isClass = false)
      Some(info)
    }
    else None
  }

  private def infoForScalaTestRefSpecObject(obj: ScObject, definition: ScTypeDefinition): Option[RunLineMarkerContributor.Info] = {
    val locations = ScalaTestTestLocationsFinder.calculateTestLocations(definition)
    if (locations.contains(obj))
      Some(buildLineInfo("", obj.getProject, isClass = false))<commit_msg>Simplify infoForScalaTestReSpec #SCL-20196 Fixed<commit_after>package org.jetbrains.plugins.scala.testingSupport.test.ui

import com.intellij.codeInsight.TestFrameworks
import com.intellij.execution.TestStateStorage
import com.intellij.execution.lineMarker.{ExecutorAction, RunLineMarkerContributor}
import com.intellij.execution.testframework.TestIconMapper
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo.Magnitude
import com.intellij.icons.AllIcons.RunConfigurations.TestState
import com.intellij.openapi.project.Project
import com.intellij.psi._
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.testIntegration.TestRunLineMarkerProvider

import javax.swing.Icon
import org.jetbrains.plugins.scala.ScalaBundle
import org.jetbrains.plugins.scala.extensions._
import org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes
import org.jetbrains.plugins.scala.lang.psi.api.ScalaPsiElement
import org.jetbrains.plugins.scala.lang.psi.api.expr.ScReferenceExpression
import org.jetbrains.plugins.scala.lang.psi.api.statements.ScFunctionDefinition
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.{ScClass, ScObject, ScTrait, ScTypeDefinition}
import org.jetbrains.plugins.scala.macroAnnotations.Measure
import org.jetbrains.plugins.scala.testingSupport.test.AbstractTestFramework
import org.jetbrains.plugins.scala.testingSupport.test.munit.{MUnitTestFramework, MUnitTestLocationsFinder, MUnitUtils}
import org.jetbrains.plugins.scala.testingSupport.test.scalatest.{ScalaTestTestFramework, ScalaTestTestLocationsFinder}
import org.jetbrains.plugins.scala.testingSupport.test.specs2.Specs2TestFramework
import org.jetbrains.plugins.scala.testingSupport.test.utest.UTestTestFramework

import scala.jdk.CollectionConverters.IterableHasAsScala

// TODO: split providers by test frameworks, if some logic should be reused, just move to some base/utility classes
class ScalaTestRunLineMarkerProvider extends TestRunLineMarkerProvider {

  /**
   * for scalatest location hint format see:
   *  - [[org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestReporter]]
   *  - [[org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestReporterWithLocation]]
   *  - [[org.jetbrains.plugins.scala.testingSupport.locationProvider.ScalaTestLocationProvider]]
   *
   * for uTest  location hint format see
   *  - [[org.jetbrains.plugins.scala.testingSupport.uTest.UTestReporter]]
   */
  override def getInfo(element: PsiElement): RunLineMarkerContributor.Info =
    element match {
      case leaf: LeafPsiElement if leaf.getElementType == ScalaTokenTypes.tIDENTIFIER =>
        infoForLeafElement(leaf).orNull
      case _                                                                          =>
        null
    }

  // REMINDER from codeInsight.daemon.LineMarkerInfo:
  // LineMarker is supposed to be registered for leaf elements only!
  private def infoForLeafElement(leaf: LeafPsiElement): Option[RunLineMarkerContributor.Info] = {
    val parent = leaf.getParent
    parent match {
      case o: ScObject =>
        infoForScalaTestRefSpec(o) match {
          case info@Some(_) => info
          case _ => infoForClass(o)
        }
      case clazz: PsiClass => infoForClass(clazz)
      case method: PsiMethod => infoForMethod(method)
      case ref: ScReferenceExpression => infoForTestMethodRef(ref)
      case _ => None
    }
  }

  private def infoForClass(clazz: PsiClass): Option[RunLineMarkerContributor.Info] = {
    val framework = TestFrameworks.detectFramework(clazz)
    if (framework == null || !framework.isTestClass(clazz)) return None

    val url = framework match {
      case _: ScalaTestTestFramework =>
        clazz match {
          case _: ScTrait | _: ScObject => None
          case _ => Some(s"scalatest://TopOfClass:${clazz.qualifiedName}TestName:${clazz.name}")
        }
      //FIXME: why the hell uTest url is reported with scalatest prefix? (see UTestReporter)
      case _: UTestTestFramework  => Some(s"scalatest://TopOfClass:${clazz.qualifiedName}TestName:${clazz.qualifiedName}") // TODO: check maybe it should be clazz.name
      case _: Specs2TestFramework => Some(s"") // TODO: spec2 runner does not report location for class currently
      case _                      => Some(s"java:suite://${clazz.qualifiedName}")
    }
    url.map(buildLineInfo(_, clazz.getProject, isClass = true))
  }

  // REMINDER: we shouldn't do any long resolve or some other heavy computation,
  // because we can't block UI thread, we already have a lag in a context menu on a single method (right click),
  // not talking about dozens of methods which should be resolved here
  @Measure
  private def infoForMethod(method: PsiMethod): Option[RunLineMarkerContributor.Info] = {
    val clazz: PsiClass = PsiTreeUtil.getTopmostParentOfType(method, classOf[PsiClass])

    if (clazz == null)
      return None

    val framework = TestFrameworks.detectFramework(clazz)

    if (framework == null)
      return None

    (framework, method) match {
      case (_: ScalaTestTestFramework, f: ScFunctionDefinition) =>
        infoForScalaTestRefSpec(f)
      case _ if framework.isTestMethod(method) =>
        Some(buildLineInfo(
          s"java:test://${clazz.qualifiedName}.${method.getName}",
          method.getProject,
          isClass = false
        ))
      case _ => None
    }
  }

  private def infoForTestMethodRef(ref: ScReferenceExpression): Option[RunLineMarkerContributor.Info] = {
    val definition = PsiTreeUtil.getParentOfType(ref, classOf[ScTypeDefinition])
    if (definition == null)
      return None

    // NOT: for MUnit more than one framework is applicable: MUnit and JUnit, and JUnit is detected first =(
    val frameworks = TestFrameworks.detectApplicableFrameworks(definition).asScala
    val scalaFramework = frameworks.filterByType[AbstractTestFramework].headOption

    scalaFramework.flatMap {
      case _: ScalaTestTestFramework => infoForScalaTestMethodRef(ref, definition)
      case _: MUnitTestFramework     => infoForMUnitMethodRef(ref, definition)
      case _                         => None
    }
  }

  private def infoForScalaTestRefSpec(functionOrObject: ScalaPsiElement): Option[RunLineMarkerContributor.Info] = {
    val definition = PsiTreeUtil.getParentOfType(functionOrObject, classOf[ScClass])

    if (definition == null)
      return None

    val frameworks = TestFrameworks.detectApplicableFrameworks(definition).asScala
    val scalaFramework = frameworks.findByType[ScalaTestTestFramework]

    scalaFramework.flatMap { _ =>
        functionOrObject match {
          case f: ScFunctionDefinition => infoForScalaTestRefSpecFunctionDefinition(f, definition)
          case o: ScObject => infoForScalaTestRefSpecObject(o, definition)
          case _ => None
        }
    }
  }

  private def infoForScalaTestMethodRef(ref: ScReferenceExpression, definition: ScTypeDefinition): Option[RunLineMarkerContributor.Info] = {
    val locations = ScalaTestTestLocationsFinder.calculateTestLocations(definition)
    if (locations.contains(ref)) {
      val url = scalaTestLineUrl(ref, definition, testName = "")
      val info = buildLineInfo(url, ref.getProject, isClass = false)
      Some(info)
    }
    else None
  }

  private def infoForScalaTestRefSpecFunctionDefinition(functionDefinition: ScFunctionDefinition, definition: ScTypeDefinition): Option[RunLineMarkerContributor.Info] = {
    val locations = ScalaTestTestLocationsFinder.calculateTestLocations(definition)
    if (locations.contains(functionDefinition)) {
      val url = scalaTestLineUrl(functionDefinition, definition, testName = "")
      val info = buildLineInfo(url, functionDefinition.getProject, isClass = false)
      Some(info)
    }
    else None
  }

  private def infoForScalaTestRefSpecObject(obj: ScObject, definition: ScTypeDefinition): Option[RunLineMarkerContributor.Info] = {
    val locations = ScalaTestTestLocationsFinder.calculateTestLocations(definition)
    if (locations.contains(obj))
      Some(buildLineInfo("", obj.getProject, isClass = false))
{'fraction_non_alphanumeric': 0.08929776142287642, 'fraction_numerical': 0.0007973014412756823, 'mean_word_length': 4.023413431916205, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 54, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7326714', 'n_tokens_mistral': 4786, 'n_tokens_neox': 4597, 'n_words': 944}