All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalafix.internal.reflect.ScalafixToolbox.scala Maven / Gradle / Ivy

package scalafix.internal.reflect

import java.io.File
import java.net.URLClassLoader
import java.net.URLDecoder
import java.util.function
import scala.meta.inputs.Input
import scala.reflect.internal.util.AbstractFileClassLoader
import scala.reflect.internal.util.BatchSourceFile
import scala.tools.nsc.Global
import scala.tools.nsc.Settings
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.io.VirtualDirectory
import scala.tools.nsc.reporters.StoreReporter
import scala.{meta => m}
import scalafix.internal.config.LazySemanticdbIndex
import scalafix.internal.config.classloadRule
import scalafix.internal.util.ClassloadRule
import scalafix.rule.Rule
import metaconfig.ConfDecoder
import metaconfig.ConfError
import metaconfig.Configured

object ScalafixToolbox extends ScalafixToolbox
class ScalafixToolbox {
  private val ruleCache =
    new java.util.concurrent.ConcurrentHashMap[Input, Configured[Rule]]()
  private val compilerCache =
    new java.util.concurrent.ConcurrentHashMap[String, RuleCompiler]()
  private val newCompiler: function.Function[String, RuleCompiler] =
    new function.Function[String, RuleCompiler] {
      override def apply(classpath: String) = new RuleCompiler(classpath)
    }

  def getRule(code: Input, index: LazySemanticdbIndex): Configured[Rule] =
    ruleCache.getOrDefault(code, {
      val uncached = getRuleUncached(code, index)
      uncached match {
        case toCache @ Configured.Ok(_) =>
          ruleCache.put(code, toCache)
        case _ =>
      }
      uncached
    })

  def getRuleUncached(
      code: Input,
      index: LazySemanticdbIndex): Configured[Rule] =
    synchronized {
      val cp = RuleCompiler.defaultClasspath + (
        if (index.toolClasspath.isEmpty) ""
        else {
          File.pathSeparator +
            index.toolClasspath.mkString(File.pathSeparator)
        }
      )
      val compiler = compilerCache.computeIfAbsent(cp, newCompiler)
      (
        compiler.compile(code) |@|
          RuleInstrumentation.getRuleFqn(code)
      ).andThen {
        case (classloader, names) =>
          names.foldLeft(Configured.ok(Rule.empty)) {
            case (rule, fqn) =>
              val args = classloadRule(index)
              rule
                .product(ClassloadRule(fqn, args, classloader))
                .map { case (a, b) => a.merge(b) }
          }
      }
    }
}

object RuleCompiler {
  def defaultClasspath: String = {
    getClass.getClassLoader match {
      case u: URLClassLoader =>
        val paths = u.getURLs.toList.map(u => {
          if (u.getProtocol.startsWith("bootstrap")) {
            import java.io._
            import java.nio.file._
            val stream = u.openStream
            val tmp = File.createTempFile("bootstrap-" + u.getPath, ".jar")
            Files.copy(
              stream,
              Paths.get(tmp.getAbsolutePath),
              StandardCopyOption.REPLACE_EXISTING)
            tmp.getAbsolutePath
          } else {
            URLDecoder.decode(u.getPath, "UTF-8")
          }
        })
        paths.mkString(File.pathSeparator)
      case _ => ""
    }
  }
}

class RuleCompiler(
    classpath: String,
    target: AbstractFile = new VirtualDirectory("(memory)", None)) {
  private val settings = new Settings()
  settings.deprecation.value = true // enable detailed deprecation warnings
  settings.unchecked.value = true // enable detailed unchecked warnings
  settings.outputDirs.setSingleOutput(target)
  settings.classpath.value = classpath
  lazy val reporter = new StoreReporter
  private val global = new Global(settings, reporter)
  private val classLoader =
    new AbstractFileClassLoader(target, this.getClass.getClassLoader)

  def compile(input: Input): Configured[ClassLoader] = {
    reporter.reset()
    val run = new global.Run
    val label = input match {
      case Input.File(path, _) => path.toString()
      case Input.VirtualFile(label, _) => label
      case _ => "(input)"
    }
    run.compileSources(
      List(new BatchSourceFile(label, new String(input.chars))))
    val errors = reporter.infos.collect {
      case reporter.Info(pos, msg, reporter.ERROR) =>
        ConfError
          .msg(msg)
          .atPos(
            if (pos.isDefined) m.Position.Range(input, pos.start, pos.end)
            else m.Position.None
          )
          .notOk
    }
    ConfError
      .fromResults(errors.toSeq)
      .map(_.notOk)
      .getOrElse(Configured.Ok(classLoader))
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy