51

I want to be able to use -Xfatal-warnings and -Ywarn-unused-import, the problem is that the compiler is triggering an error on the file which contains the play routes for my application:

[error] /path/to/app/conf/routes: Unused import
[error] /path/to/app/conf/routes: Unused import
[error] /path/to/app/conf/routes:1: Unused import
[error] GET        /document/:id        my.app.controllers.MyController.getById(id: Int)

same goes for other routes.

Is it possible maybe to tell scalac to ignore a file?

Scala version is 2.11.8.

Ende Neu
  • 14,921
  • 5
  • 50
  • 67
  • What scala version? I contributed a fix for the warning related to implicit search, some time ago. I would try a sample project if you can do it. There's no mechanism to ignore a file, except that in 2.11 you can supply a reporter that does whatever it wants. – som-snytt Jul 18 '16 at 05:34
  • @som-snytt I"m using scala `2.11.8`, [here you can find an example project](https://github.com/EndeNeu/wron-warn-example), just needs to be compiled. – Ende Neu Jul 18 '16 at 07:51
  • any update on the subject ? – Louis F. Aug 03 '16 at 09:28
  • The templates do indeed inject extra imports at route compile. It might be possible to exclude the route classes from scalac task and then use custom task to compile; or run a formatter that cleans up the imports; or make the route compiler smarter. I haven't tried yet. – som-snytt Aug 31 '16 at 06:46
  • @som-snytt I actually saw some imported being generated in the `Routes.scala` file but the compiler points directly to the `routes` file and I'm not sure why or if it matters. Regarding the formatter, scalariform doesn't support import optimization, maybe can be set in Intellij. Regarding scalac I don't even know were to start, googling didn't help either. – Ende Neu Aug 31 '16 at 18:38
  • I've the exact same issue, Scala 2.11.8, Play 2.5.10. I ended up commenting out `warn-unused-import`. Did you ever find a solution to this? – Abhijit Sarkar Jan 02 '17 at 10:39
  • Unfortunately I haven't. – Ende Neu Jan 02 '17 at 10:49

3 Answers3

5

I just encountered the same problem with Scala 2.12 and Play 2.6 (which you're probably now using).

A Scala compiler plugin called Silencer sorts it out: https://github.com/ghik/silencer

Add the following dependency into build.sbt:

val silencerVersion = "1.2.1"

libraryDependencies ++= Seq(
    compilerPlugin("com.github.ghik" %% "silencer-plugin" % silencerVersion),
    "com.github.ghik" %% "silencer-lib" % silencerVersion % Provided
)

Then add (also in build.sbt):

scalacOptions += "-P:silencer:globalFilters=Unused import"

The text after the globalFilters= is a list of regex matches for compiler warnings to silence, can be comma-separated. You might need to tweak the regex for your own case, but I found the above example worked fine.

It does mean that it silences any "Unused import" warnings, but if you're in the habit of auto-formatting your code (ctrl+alt+L in Intellij), including tidying up unused imports, then it shouldn't cause a problem.

Matt Stephens
  • 51
  • 1
  • 2
  • This looks promising, I will try it out in the next days! – Ende Neu Oct 11 '18 at 12:06
  • This just suppresses all warnings for me – Pavel Oct 31 '18 at 18:40
  • @Pavel Thanks for the feedback, I've updated the answer to correct the regex. – Matt Stephens Nov 01 '18 at 10:58
  • @MattStephens won't it suppress all "Unused import" warnings now? Anyway for me silencer-plugin with any globalFilters key just suppresses all warnings (I tried different regexps, different keys, different code), for sure there is a bug in silencer. – Pavel Nov 01 '18 at 13:01
  • @Pavel yep that's correct, as I noted in the last paragraph. How much of a problem that might be probably depends on personal preference & whether your team habitually auto-tidies their imports. Can't really comment on silencer, as it worked fine over here; changing the regex I could switch it from working to not working, and back. Different Scala/JVM/sbt versions perhaps? – Matt Stephens Nov 01 '18 at 19:46
4

A horrible "solution" could be to remove those unused imports after the routes are generated but before the compile task runs. Here's a sketch:

lazy val optimizeRoutesImports = taskKey[Unit]("Remove unused imports from generated routes sources.")

optimizeRoutesImports := {

  def removeUnusedImports(targetFiles: (File) => PathFinder, linesToRemove: Set[String], linesToReplace: Map[String, String]) = {
    val files = targetFiles(crossTarget.value).get
    files foreach { file =>
      val lines = sbt.IO.readLines(file)
      val updatedLines = lines map { line =>
        linesToReplace.getOrElse(line, line)
      } filterNot { line =>
        linesToRemove.contains(line.trim)
      }
      sbt.IO.writeLines(file, updatedLines, append = false)
    }
  }

  removeUnusedImports(
    _ / "routes" / "main" / "controllers" / "ReverseRoutes.scala",
    Set("import ReverseRouteContext.empty"),
    Map(
      "import play.api.mvc.{ QueryStringBindable, PathBindable, Call, JavascriptLiteral }" ->
        "import play.api.mvc.{ QueryStringBindable, PathBindable, Call }",
      "import play.core.routing.{ HandlerDef, ReverseRouteContext, queryString, dynamicString }" ->
        "import play.core.routing.{ ReverseRouteContext, queryString, dynamicString }"
    )
  )

  removeUnusedImports(
    _ / "routes" / "main" / "controllers" / "javascript" / "JavaScriptReverseRoutes.scala",
    Set(
      "import play.core.routing.{ HandlerDef, ReverseRouteContext, queryString, dynamicString }",
      "import ReverseRouteContext.empty"
    ),
    Map(
      "import play.api.mvc.{ QueryStringBindable, PathBindable, Call, JavascriptLiteral }" ->
        "import play.api.mvc.{ QueryStringBindable, PathBindable }"
    )
  )

  removeUnusedImports(
    _ / "routes" / "main" / "router" / "Routes.scala",
    Set("import play.core.j._"),
    Map())
}

You'll then want to sort out the task dependencies:

// Our optimize routes imports task depends on the routes task.
optimizeRoutesImports := (optimizeRoutesImports dependsOn (play.sbt.routes.RoutesKeys.routes in Compile)).value

// And compilation depends on the unused routes having been removed.
compile := ((compile in Compile) dependsOn optimizeRoutesImports).value

You'll also likely need to set TwirlKeys.templateImports to a conservative list before enabling -Ywarn-unused-import. Something like this, depending on what types are used in your views:

TwirlKeys.templateImports := Seq("play.api.mvc._", "play.api.i18n.Messages", "controllers.routes")

I also had to knock unused TemplateMagic imports out of Twirl templates (YMMV):

  removeUnusedImports(
    _ / "twirl" ** "*.template.scala",
    Set("import play.twirl.api.TemplateMagic._"),
    Map())

If you do that, make sure the task dependencies are set up appropriately.

This works for me. Scala 2.11.8, Play 2.5.10, both -Xfatal-warnings and -Ywarn-unused-import enabled. It's hideous, but it works.

danielnixon
  • 3,845
  • 1
  • 22
  • 37
0

Here's another option (possibly inferior to that of danielnixon)

I added the following to build.sbt:

import CustomGenerator._

import play.sbt.routes.RoutesKeys
RoutesKeys.routesImport := Seq.empty
routesGenerator := ModifiedInjectedRoutesGenerator

Then added this to project/CustomGenerator.scala (always top level project/):

object CustomGenerator {
  object ModifiedInjectedRoutesGenerator extends play.routes.compiler.RoutesGenerator {
    import play.routes.compiler._
    import play.routes.compiler.RoutesCompiler.RoutesCompilerTask

    def generate(task: RoutesCompilerTask, namespace: Option[String], rules: List[Rule]): Seq[(String, String)] = {
      play.routes.compiler.InjectedRoutesGenerator.generate(task, namespace, rules) map { case(key, value) =>
        var v = value
        if(key.endsWith("/ReverseRoutes.scala")) {
          v = v.replace("import ReverseRouteContext.empty", "implicit val empty = ReverseRouteContext(Map())")
          v = v.replace("import play.core.routing.{ HandlerDef, ReverseRouteContext, queryString, dynamicString }", "import play.core.routing.{ ReverseRouteContext, queryString }")
          v = v.replace("import play.api.mvc.{ QueryStringBindable, PathBindable, Call, JavascriptLiteral }", "import play.api.mvc.{ QueryStringBindable, Call }")
        }
        if(key.endsWith("migrations/ReverseRoutes.scala")) {
          v = v.replace("import play.api.mvc.{ QueryStringBindable, Call }", "import play.api.mvc.{ Call }")
          v = v.replace("import play.core.routing.{ ReverseRouteContext, queryString }", "import play.core.routing.{ ReverseRouteContext }")
        }
        if(key.endsWith("/JavaScriptReverseRoutes.scala")) {
          v = v.replace("import ReverseRouteContext.empty", "")
          v = v.replace("import play.api.mvc.{ QueryStringBindable, PathBindable, Call, JavascriptLiteral }", "import play.api.mvc.{ QueryStringBindable, JavascriptLiteral }")
          v = v.replace("import play.core.routing.{ HandlerDef, ReverseRouteContext, queryString, dynamicString }", "")
        }
        if(key.endsWith("migrations/javascript/JavaScriptReverseRoutes.scala")) {
          v = v.replace("import play.api.mvc.{ QueryStringBindable, JavascriptLiteral }", "")
        }
        if(key.endsWith("/Routes.scala")) {
          v = v.replace("import play.core.routing.HandlerInvokerFactory._", "")
          v = v.replace("import play.core.j._", "")
          v = v.replace("import ReverseRouteContext.empty", "implicit val empty = ReverseRouteContext(Map())")
        }
        (key, v)
      }
    }

    def id: String = "injected+"
  }
}

Play sbt plugin generates routes code (can be seen under target/scala-2.11/routes). This code snippet removes or inlines all unused imports. You might need to tailor it for your routes.

Pavel
  • 307
  • 4
  • 16