daily pastebin goal
20%
SHARE
TWEET

Untitled

a guest Dec 13th, 2017 54 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import sbt._
  2. import Keys._
  3.  
  4. package sbt {
  5.   object Access {
  6.     def compilerReporter = sbt.Keys.compilerReporter
  7.   }
  8. }
  9.  
  10. package autoimport {
  11.  
  12.   import java.io.FileWriter
  13.  
  14.   import autoimport.AutoImportFixerKeys.DeleteLine
  15.   import sbt.plugins.JvmPlugin
  16.   import xsbti.{Severity, Position, Problem}
  17.  
  18.   object AutoImportFixerKeys {
  19.     sealed trait Fix
  20.     case class DeleteLine(file: File, lineNo: Int, column: Int, lineContent: String) extends Fix
  21.     val collectFixes = taskKey[Seq[DeleteLine]]("Collects fixes that could be applied automatically")
  22.     val applyFixes = taskKey[Seq[DeleteLine]]("Apply fixes to files")
  23.   }
  24.  
  25.   object AutoImportFixer extends AutoPlugin {
  26.     val AutoImports = config("auto-imports")
  27.     val AutoImportsTest = config("auto-imports-test")
  28.  
  29.     class MyReporter(delegate: Option[xsbti.Reporter]) extends xsbti.Reporter {
  30.       var allProblems = Vector.empty[Problem]
  31.  
  32.       def hasWarnings: Boolean = delegate.map(_.hasWarnings).getOrElse(false)
  33.       def comment(pos: Position, msg: String): Unit = delegate.foreach(_.comment(pos, msg))
  34.       def log(p: Problem): Unit =
  35.         if (p.severity == Severity.Warn && p.message.startsWith("Unused import")) {
  36.           val pos = p.position
  37.           println(s"Found unused import at: $pos ${pos.offset().get()} '.pos.lineContent()'")
  38.           allProblems :+= p
  39.         } else delegate.foreach(_.log(p))
  40.  
  41.       def problems(): Array[Problem] = delegate.map(_.problems).getOrElse(Array.empty[Problem])
  42.       def hasErrors: Boolean = delegate.map(_.hasErrors).getOrElse(false)
  43.       def printSummary(): Unit = delegate.foreach(_.printSummary())
  44.       def reset(): Unit = {
  45.         allProblems = Vector.empty
  46.         delegate.foreach(_.reset())
  47.       }
  48.     }
  49.  
  50.     override def trigger: PluginTrigger = allRequirements
  51.     override def requires: Plugins = JvmPlugin
  52.  
  53.     override def projectSettings: Seq[Def.Setting[_]] =
  54.       inConfig(Compile)(settings(Compile)) ++ inConfig(Test)(settings(Test)) ++ Seq(
  55.         AutoImportFixerKeys.applyFixes := {
  56.           (AutoImportFixerKeys.applyFixes in Compile).value ++ (AutoImportFixerKeys.applyFixes in Test).value
  57.         }
  58.       )
  59.     /*forConfig(Compile, Compile, Defaults.compileSettings) ++
  60.       forConfig(Test, Test, Defaults.testSettings)*/
  61.  
  62.     def forConfig(config: Configuration, oldConfig: Configuration, defaultSettings: Seq[Setting[_]]) =
  63.       inConfig(config)(defaultSettings ++ settings(oldConfig))
  64.  
  65.     def settings(oldConfig: Configuration): Seq[Setting[_]] = Seq(
  66.       AutoImportFixerKeys.collectFixes := {
  67.         val rep0 = (Access.compilerReporter in compile).value
  68.         val _ = compile.value
  69.         rep0 match {
  70.           case rep: MyReporter =>
  71.             println(s"Got ${rep.allProblems.size} problems")
  72.             rep.allProblems
  73.               .filterNot { p =>
  74.                 val line = p.position.lineContent
  75.                 line.contains("language.existentials") // it seems to be a bug in 2.11 that those are needed more often than in 2.12, keep them for now
  76.                 // ||
  77.                 //line.contains("import language") ||
  78.                 //line.endsWith("collection.immutable")
  79.               }
  80.               .map { p =>
  81.                 DeleteLine(p.position.sourceFile.get, p.position.line.get, p.position.pointer.get, p.position.lineContent)
  82.               }
  83.           case _ => Nil
  84.         }
  85.       },
  86.       AutoImportFixerKeys.applyFixes := {
  87.         val fixes = AutoImportFixerKeys.collectFixes.value
  88.         fixes.groupBy(_.file.getCanonicalPath).foreach {
  89.           case (file, fixes) =>
  90.             val newFile = java.io.File.createTempFile("out", ".scala")
  91.             val out = new FileWriter(newFile)
  92.  
  93.             val lines = fixes.groupBy(_.lineNo)
  94.  
  95.             scala.io.Source.fromFile(file, "utf8").getLines().zipWithIndex.foreach {
  96.               case (line, idx) =>
  97.                 def deleteAt(column: Int): Unit = {
  98.                   val prefix = line.take(column - 1)
  99.                   val rest = line.drop(column - 1)
  100.  
  101.                   def find(char: Char): Int = {
  102.                     val res = rest.indexOf(char)
  103.                     if (res == -1) Int.MaxValue
  104.                     else res
  105.                   }
  106.  
  107.                   val nextComma = find(',')
  108.                   val nextClosingBrace  = find('}') - 1
  109.                   require(nextComma < Int.MaxValue || nextClosingBrace < Int.MaxValue)
  110.  
  111.                   val end = math.min(nextComma, nextClosingBrace)
  112.  
  113.                   val realPrefix =
  114.                     if (end == nextClosingBrace) prefix.reverse.dropWhile(_ != ',').drop(1).reverse
  115.                     else prefix
  116.                   val suffix = rest.drop(end + 1)
  117.  
  118.                   out.write(realPrefix)
  119.                   out.write(suffix)
  120.                   out.write('\n')
  121.                 }
  122.  
  123.                 lines.get(idx + 1) match {
  124.                   case None =>
  125.                     out.write(line)
  126.                     out.write('\n')
  127.                   case Some(Seq(single)) =>
  128.                     if (single.lineContent contains ",") deleteAt(single.column)
  129.                     else () // drop line completely
  130.                   case Some(several) =>
  131.                     val occs = line.count(_ == ',')
  132.                     if (occs == several.size - 1) () // delete all
  133.                     else {
  134.                       // delete just the first and depend on recompilation to find others
  135.                       deleteAt(several.head.column)
  136.                     }
  137.                 }
  138.             }
  139.             out.close()
  140.             newFile.renameTo(new File(file))
  141.         }
  142.         fixes
  143.       },
  144.       // this has the bad side-effect that MyReporter is now responsible for all error reporting
  145.       // currently, you will have to enable / disable this line manually to get either compilation
  146.       // with normal error reporting or import cleanup functionality
  147.       Access.compilerReporter in compile := new MyReporter(Some((Access.compilerReporter in compile).value)),
  148.       compileInputs in compile := {
  149.         val inputs = (compileInputs in compile in oldConfig).value
  150.         inputs.withOptions(inputs.options.withScalacOptions(inputs.options.scalacOptions ++ Seq("-Ywarn-unused-import", "-Ywarn-dead-code", "-Ywarn-unused")))
  151.       })
  152.   }
  153. }
RAW Paste Data
Top