-Ywarn-unused-import triggering on play routes file
Asked Answered
T

5

53

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.

Theobald answered 24/5, 2016 at 12:4 Comment(7)
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.Conferral
@Conferral I"m using scala 2.11.8, here you can find an example project, just needs to be compiled.Theobald
any update on the subject ?Appetizing
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.Conferral
@Conferral 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.Theobald
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?Cooper
Unfortunately I haven't.Theobald
F
8

I've come up with a working solution for Scala 2.13.7 (no need for any plugin) for Play 2.8.11. Take a look on those examples and adjust to your needs:

scalacOptions ++= Seq(
    "-Wconf:cat=unused-imports&site=.*views.html.*:s", // Silence import warnings in Play html files
    "-Wconf:cat=unused-imports&site=<empty>:s", // Silence import warnings on Play `routes` files
    "-Wconf:cat=unused-imports&site=router:s", // Silence import warnings on Play `routes` files
    "-Wconf:cat=unused-imports&site=v1:s", // Silence import warnings on Play `v1.routes` files
    "-Wconf:cat=unused-imports&site=v2:s", // Silence import warnings on Play `v2.routes` files
    "-Wconf:cat=unused-imports&site=views.v1:s", // Silence import warnings on Play `views.v1.routes` files
    "-Wconf:cat=deprecation&site=controllers\\.v1.*&origin=scala.util.Either.right:s", // Silence deprecations in generated Controller classes
    "-Wconf:cat=deprecation&site=.*v1.Routes.*&origin=scala.util.Either.right:s"
  ) // Silence deprecations in generated Controller classes

If you want to understand more, take a look on this documentation and add verbose information on the compiler messages output

scalacOptions += "-Wconf:any:wv",

Pro tip: fail compilation of unused only within CI

scalacOptions ++= {
  // Mark unused errors as info for local development (due to -Werror usage)
  if (insideCI.value) Seq.empty else Seq("-Wconf:cat=unused:i")
},
Flatling answered 20/12, 2021 at 6:49 Comment(1)
In my case, I used the followings to silence html and routes related warnings: "-Wconf:cat=unused-imports&src=.*routes.*:s" and "-Wconf:cat=unused-imports&src=.*html.*:s"Gynecologist
Y
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.

Yasukoyataghan answered 9/10, 2018 at 11:6 Comment(5)
This looks promising, I will try it out in the next days!Theobald
This just suppresses all warnings for meLaaspere
@Laaspere Thanks for the feedback, I've updated the answer to correct the regex.Yasukoyataghan
@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.Laaspere
@Laaspere 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?Yasukoyataghan
P
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.

Perishing answered 17/1, 2017 at 0:12 Comment(0)
L
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.

Laaspere answered 31/10, 2018 at 21:1 Comment(0)
E
0

Had this problem too. Basically Scala 2.12.13 or 2.13.x incorporates the Silencer code into their -Wconf compiler flag see here.

Example:

scalacOptions += "-Wconf:cat=unused-imports:s"

It is possible to restrict this rule to certain source file eg routes (see doc) but I had no luck when I tried.

For previous versions of Scala, then you should use the Silencer plugin with:

scalacOptions += "-P:silencer:pathFilters=views;routes",
Eam answered 19/10, 2021 at 15:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.