# Life with fatal warnings

Scala programmers are well known for their desire to pass as much work to the compiler as possible. The compiler, in turn, gladly takes care of programs' correctness thanks to many built-in linting rules. It can emit warnings whenever some code construct is suspicious and is likely to be a mistake, and it is a commonly accepted good practice to treat those warnings as errors. This post discusses how you can achieve this without going crazy because of constant compilation errors.

# Making warnings fatal

The easiest way to make every warning seen is to make them fail the build. If you are lucky enough to be able to use Scala 2.13.2 or later, add -Wconf:any:error to your scalac options. For older Scala versions -Xfatal-warnings works. The list of the checks that the Scala compiler can perform is well documented on the Scala Compiler Options (opens new window) page, and if you use sbt as a build tool there are plugins that configure scalac for you, such as sbt-tpolecat (opens new window).

The stricter your compiler settings are, however, the higher the chances that they will get in the way of the normal development process. For example, even though you don't want to have unused variables, you very often have some just because the code that uses them is yet to be written. Or you comment out a line of code to see if a test covers some corner case, but instead, you learn that now there is an unused import that fails the compilation.

# Making warnings non-fatal

Tired of fighting with the compiler, you may decide that you want different compiler options for the CI and the local development. The former should be strict to ensure that no bad code gets into production, while the latter can be more relaxed. With sbt you can achieve this by checking the insideCI value:

scalacOptions ++= {
    if (insideCI.value) Seq("-Wconf:any:error")
    else                Seq("-Wconf:any:warning")

Now the compiler will keep telling you about potential problems in the code, but it will not fail the build, so you will still be able to run the tests or the application itself.

# Making warnings shown

The solution of keeping warnings as warnings has one shortcoming: you will only see them for the files that have been re-compiled in the current incremental compilation run. If missed, you will not know about a problem until it fails the CI build.

Moreover, sbt 1.4.0-M1 adds experimental cached compilation support, where the compiler can reuse compilation results from another machine. Regarding scalac warnings, it means that you may miss some of them because someone else has already compiled this code and there was no need to re-run the compilation on your machine.

There is a printWarnings sbt task that can show all compilation warnings, but you need to remember to run it, and since it calls compile first you may see some warnings twice. It would be nice if you just always saw the warnings regardless of the incremental compilation details, and here we come to sbt-rewarn (opens new window).

Add the following line to your plugins.sbt (either of a particular project, or the global one at ~/.sbt/1.0/plugins/plugins.sbt)

addSbtPlugin("com.timushev.sbt" % "sbt-rewarn" % "0.1.0")

and sbt will start showing scalac warnings whenever you run tasks like compile or test, even if there are no changes in sources:

sbt:demo> compile
[info] Compiling 1 Scala source to ...
// ...
// here a warning has been printed but you did not pay attention
// ...

// without sbt-rewarn
sbt:demo> test
[info] + DemoSpec
[info]   + an important test
[info] Ran 1 test in 500 ms: 1 succeeded, 0 ignored, 0 failed
[success] Total time: 1 s, completed Jul 25, 2020 5:00:00 PM

// with sbt-rewarn
sbt:demo> test
[warn] /demo/A.scala:2:15: private val a in object A is never used
[warn]   private val a: Int = 1
[warn]               ^
[warn] one warning found
[info] + DemoSpec
[info]   + an important test
[info] Ran 1 test in 500 ms: 1 succeeded, 0 ignored, 0 failed
[success] Total time: 1 s, completed Jul 25, 2020 5:00:00 PM

# Summary

To get the most out of the built-int Scala compiler linting options: