Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import sbt._
- import Keys._
- package sbt {
- object Access {
- def compilerReporter = sbt.Keys.compilerReporter
- }
- }
- package autoimport {
- import java.io.FileWriter
- import autoimport.AutoImportFixerKeys.DeleteLine
- import sbt.plugins.JvmPlugin
- import xsbti.{Severity, Position, Problem}
- object AutoImportFixerKeys {
- sealed trait Fix
- case class DeleteLine(file: File, lineNo: Int, column: Int, lineContent: String) extends Fix
- val collectFixes = taskKey[Seq[DeleteLine]]("Collects fixes that could be applied automatically")
- val applyFixes = taskKey[Seq[DeleteLine]]("Apply fixes to files")
- }
- object AutoImportFixer extends AutoPlugin {
- val AutoImports = config("auto-imports")
- val AutoImportsTest = config("auto-imports-test")
- class MyReporter(delegate: Option[xsbti.Reporter]) extends xsbti.Reporter {
- var allProblems = Vector.empty[Problem]
- def hasWarnings: Boolean = delegate.map(_.hasWarnings).getOrElse(false)
- def comment(pos: Position, msg: String): Unit = delegate.foreach(_.comment(pos, msg))
- def log(p: Problem): Unit =
- if (p.severity == Severity.Warn && p.message.startsWith("Unused import")) {
- val pos = p.position
- println(s"Found unused import at: $pos ${pos.offset().get()} '.pos.lineContent()'")
- allProblems :+= p
- } else delegate.foreach(_.log(p))
- def problems(): Array[Problem] = delegate.map(_.problems).getOrElse(Array.empty[Problem])
- def hasErrors: Boolean = delegate.map(_.hasErrors).getOrElse(false)
- def printSummary(): Unit = delegate.foreach(_.printSummary())
- def reset(): Unit = {
- allProblems = Vector.empty
- delegate.foreach(_.reset())
- }
- }
- override def trigger: PluginTrigger = allRequirements
- override def requires: Plugins = JvmPlugin
- override def projectSettings: Seq[Def.Setting[_]] =
- inConfig(Compile)(settings(Compile)) ++ inConfig(Test)(settings(Test)) ++ Seq(
- AutoImportFixerKeys.applyFixes := {
- (AutoImportFixerKeys.applyFixes in Compile).value ++ (AutoImportFixerKeys.applyFixes in Test).value
- }
- )
- /*forConfig(Compile, Compile, Defaults.compileSettings) ++
- forConfig(Test, Test, Defaults.testSettings)*/
- def forConfig(config: Configuration, oldConfig: Configuration, defaultSettings: Seq[Setting[_]]) =
- inConfig(config)(defaultSettings ++ settings(oldConfig))
- def settings(oldConfig: Configuration): Seq[Setting[_]] = Seq(
- AutoImportFixerKeys.collectFixes := {
- val rep0 = (Access.compilerReporter in compile).value
- val _ = compile.value
- rep0 match {
- case rep: MyReporter =>
- println(s"Got ${rep.allProblems.size} problems")
- rep.allProblems
- .filterNot { p =>
- val line = p.position.lineContent
- 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
- // ||
- //line.contains("import language") ||
- //line.endsWith("collection.immutable")
- }
- .map { p =>
- DeleteLine(p.position.sourceFile.get, p.position.line.get, p.position.pointer.get, p.position.lineContent)
- }
- case _ => Nil
- }
- },
- AutoImportFixerKeys.applyFixes := {
- val fixes = AutoImportFixerKeys.collectFixes.value
- fixes.groupBy(_.file.getCanonicalPath).foreach {
- case (file, fixes) =>
- val newFile = java.io.File.createTempFile("out", ".scala")
- val out = new FileWriter(newFile)
- val lines = fixes.groupBy(_.lineNo)
- scala.io.Source.fromFile(file, "utf8").getLines().zipWithIndex.foreach {
- case (line, idx) =>
- def deleteAt(column: Int): Unit = {
- val prefix = line.take(column - 1)
- val rest = line.drop(column - 1)
- def find(char: Char): Int = {
- val res = rest.indexOf(char)
- if (res == -1) Int.MaxValue
- else res
- }
- val nextComma = find(',')
- val nextClosingBrace = find('}') - 1
- require(nextComma < Int.MaxValue || nextClosingBrace < Int.MaxValue)
- val end = math.min(nextComma, nextClosingBrace)
- val realPrefix =
- if (end == nextClosingBrace) prefix.reverse.dropWhile(_ != ',').drop(1).reverse
- else prefix
- val suffix = rest.drop(end + 1)
- out.write(realPrefix)
- out.write(suffix)
- out.write('\n')
- }
- lines.get(idx + 1) match {
- case None =>
- out.write(line)
- out.write('\n')
- case Some(Seq(single)) =>
- if (single.lineContent contains ",") deleteAt(single.column)
- else () // drop line completely
- case Some(several) =>
- val occs = line.count(_ == ',')
- if (occs == several.size - 1) () // delete all
- else {
- // delete just the first and depend on recompilation to find others
- deleteAt(several.head.column)
- }
- }
- }
- out.close()
- newFile.renameTo(new File(file))
- }
- fixes
- },
- // this has the bad side-effect that MyReporter is now responsible for all error reporting
- // currently, you will have to enable / disable this line manually to get either compilation
- // with normal error reporting or import cleanup functionality
- Access.compilerReporter in compile := new MyReporter(Some((Access.compilerReporter in compile).value)),
- compileInputs in compile := {
- val inputs = (compileInputs in compile in oldConfig).value
- inputs.withOptions(inputs.options.withScalacOptions(inputs.options.scalacOptions ++ Seq("-Ywarn-unused-import", "-Ywarn-dead-code", "-Ywarn-unused")))
- })
- }
- }
Add Comment
Please, Sign In to add comment