WartRemover is a flexible Scala code linting tool.
Add the following to your project/plugins.sbt
:
addSbtPlugin("org.brianmckenna" % "sbt-wartremover" % "0.14")
Now, you can proceed to configure the linter in your build.sbt
. By default, all errors and warnings are turned off. To turn on all checks that are currently considered stable, use:
wartremoverErrors ++= Warts.unsafe
To turn on all available errors (some have false positives), use:
wartremoverErrors ++= Warts.all
Similarly, to just issue warnings instead of errors for all built-in warts, you can use:
wartremoverWarnings ++= Warts.all // or Warts.unsafe
You can also use scopes, e.g. to turn on all warts only for compilation (and not for the tests nor the sbt console
), use:
wartremoverErrors in (Compile, compile) ++= Warts.all
To choose warts more selectively, use any of the following:
wartremoverErrors ++= Warts.allBut(Wart.Any, Wart.Nothing, Wart.Serializable)
wartremoverWarnings += Wart.Nothing
wartremoverWarnings ++= Seq(Wart.Any, Wart.Serializable)
To exclude a file from all checks:
wartremoverExcluded += baseDirectory.value / "src" / "main" / "scala" / "SomeFile.scala"
To exclude a specific piece of code from one or more checks use the SuppressWarnings
annotation:
@SuppressWarnings(Array("org.brianmckenna.wartremover.warts.Var", "org.brianmckenna.wartremover.warts.Null"))
var foo = null
Finally, if you want to add your custom WartTraverser
, provide its classpath first:
wartremoverClasspaths += "some-url"
wartremoverErrors += Wart.custom("org.your.custom.WartTraverser")
See also other ways of using WartRemover for information on how to use it as a command-line tool, a macro or a compiler plugin, while providing all the scalac
options manually.
- Note - the WartRemover SBT plugin sets scalac options - make sure you're not overwriting those by having a
scalacOptions := ...
setting in your SBT settings. UsescalacOptions ++= ...
instead.
Here is a list of built-in warts under the
org.brianmckenna.wartremover.warts
package.
Any is the top type; it is the supertype of every other type. The Scala compiler loves to infer Any as a generic type but that is almost always incorrect. Explicit type arguments should be used instead.
// Won't compile: Inferred type containing Any
val any = List(1, true, "three")
Scala has an implicit which converts anything to a String
if the
right hand side of +
is a String
.
// Won't compile: Scala inserted an any2stringadd call
println({} + "test")
asInstanceOf
is unsafe in isolation and violates parametricity when guarded by isInstanceOf
. Refactor so that the desired type is proven statically.
// Won't compile: asInstanceOf is disabled
x.asInstanceOf[String]
Scala allows methods to have default arguments, which make it hard to use methods as functions.
// Won't compile: Function has default arguments
def x(y: Int = 0)
scala.util.Either.LeftProjection
and scala.util.Either.RightProjection
have a get
method which will throw if the value doesn't match the
projection. The program should be refactored to use scala.util.Either.LeftProjection#toOption
and scala.util.Either.RightProjection#toOption
to explicitly handle both
the Some
and None
cases.
Scala's Enumeration
can cause performance problems due to its reliance on reflection. Additionally the lack of exhaustive match checks and partial methods can lead to runtime errors. Instead of Enumeration
, a sealed abstract class
extended by case object
s should be used instead.
Scala has trouble correctly resolving implicits when some of them lack explicit result types. To avoid this all implicits should have explicit type ascriptions.
Scala's case classes provide a useful implementation of logicless data types. Extending a case class can break this functionality in surprising ways. This can be avoided by always making them final.
// Won't compile: case classes must be final
case class Foo()
isInstanceOf
violates parametricity. Refactor so that the type is established statically.
// Won't compile: isInstanceOf is disabled
x.isInstanceOf[String]
The standard library provides implicits conversions to and from Java types in scala.util.JavaConversions
. This can make code difficult to understand and read about. The explicit conversions provided by scala.collection.JavaConverters
instead.
// Won't compile: scala.collection.JavaConversions is disabled
import scala.collection.JavaConversions._
val scalaMap: Map[String, String] = Map()
val javaMap: java.util.Map[String, String] = scalaMap
scala.collection.immutable.List
has:
head
,tail
,init
,last
,reduce
,reduceLeft
andreduceRight
methods,
all of which will throw if the list is empty. The program should be refactored to use:
List#headOption
,List#drop(1)
,List#dropRight(1)
,List#lastOption
,List#reduceOption
orList#fold
,List#reduceLeftOption
orList#foldLeft
andList#reduceRightOption
orList#foldRight
respectively,
to explicitly handle both the populated and empty List
.
The standard library provides mutable collections. Mutation breaks equational reasoning.
// Won't compile: scala.collection.mutable package is disabled
import scala.collection.mutable.ListBuffer
val mutList = ListBuffer()
Sometimes an additional power of Monad
is not needed, and
Applicative
is enough. This issues a warning in such cases
(not an error, since using a Monad
instance might still be a conscious decision)
scala> for {
| x <- List(1,2,3)
| y <- List(2,3,4)
| } yield x * y
<console>:19: warning: No need for Monad here (Applicative should suffice).
> "If the extra power provided by Monad isn’t needed, it’s usually a good idea to use Applicative instead."
Typeclassopedia (http://www.haskell.org/haskellwiki/Typeclassopedia)
Apart from a cleaner code, using Applicatives instead of Monads can in general case result in a more parallel code.
For more context, please refer to the aforementioned Typeclassopedia, http://comonad.com/reader/2012/abstracting-with-applicatives/, or http://www.serpentine.com/blog/2008/02/06/the-basics-of-applicative-functors-put-to-practical-work/
x <- List(1,2,3)
^
res0: List[Int] = List(2, 3, 4, 4, 6, 8, 6, 9, 12)
scala> for {
| x <- List(1,2,3)
| y <- x to 3
| } yield x * y
res1: List[Int] = List(1, 2, 3, 4, 6, 9)
Scala allows statements to return any type. Statements should only
return Unit
(this ensures that they're really intended to be
statements).
// Won't compile: Statements must return Unit
10
false
Nothing is a special bottom type; it is a subtype of every other type. The Scala compiler loves to infer Nothing as a generic type but that is almost always incorrect. Explicit type arguments should be used instead.
// Won't compile: Inferred type containing Nothing
val nothing = ???
val nothingList = List.empty
null
is a special value that inhabits all reference types. It breaks
type safety.
// Won't compile: null is disabled
val s: String = null
Scala inserts an implicit conversion from Option
to Iterable
. This can hide bugs and creates surprising situations like Some(1) zip Some(2)
returning an Iterable[(Int, Int)]
.
scala.Option
has a get
method which will throw if the value is
None
. The program should be refactored to use scala.Option#fold
to
explicitly handle both the Some
and None
cases.
Product is a type common to many structures; it is the supertype of case classes and tuples. The Scala compiler loves to infer Product as a generic type but that is almost always incorrect. Explicit type arguments should be used instead.
// Won't compile: Inferred type containing Product
val any = List((1, 2, 3), (1, 2))
return
breaks referential transparency. Refactor to terminate computations in a safe way.
// Won't compile: return is disabled
def foo(n:Int): Int = return n + 1
def foo(ns: List[Int]): Any = ns.map(n => return n + 1)
Serializable is a type common to many structures. The Scala compiler loves to infer Serializable as a generic type but that is almost always incorrect. Explicit type arguments should be used instead.
// Won't compile: Inferred type containing Serializable
val any = List((1, 2, 3), (1, 2))
throw
implies partiality. Encode exceptions/errors as return
values instead using Either
.
Scala creates a toString
method automatically for all classes. Since toString
is based on the class name, any rename can potentially introduce bugs. This is especially pernicious for case object
s. toString
should be explicitly overridden wherever used.
case object Foo { override val toString = "Foo" }
scala.util.Try
has a get
method which will throw if the value is a
Failure
. The program should be refactored to use scala.util.Try#map
and scala.util.Try#getOrElse
to
explicitly handle both the Success
and Failure
cases.
Checks for the following warts:
- Any
- Any2StringAdd
- AsInstanceOf
- EitherProjectionPartial
- IsInstanceOf
- ListOps
- NonUnitStatements
- Null
- OptionPartial
- Product
- Return
- Serializable
- Throw
- Var
Mutation breaks equational reasoning.
// Won't compile: var is disabled
var x = 100
A wart rule has to be an object which extends WartTraverser
. The
object only needs an apply
method which takes a WartUniverse
and
returns a WartUniverse#universe#Traverser
.
The WartUniverse
has error
and warning
methods which both take
(WartUniverse#universe#Position, String)
. They are side-effecting
methods for adding errors and warnings.
Most traversers will want a super.traverse
call to be able to
recursively continue.
import org.brianmckenna.wartremover.{WartTraverser, WartUniverse}
object Unimplemented extends WartTraverser {
def apply(u: WartUniverse): u.Traverser = {
import u.universe._
import scala.reflect.NameTransformer
val notImplementedName: TermName = NameTransformer.encode("???")
val notImplemented: Symbol = typeOf[Predef.type].member(notImplementedName)
require(notImplemented != NoSymbol)
new Traverser {
override def traverse(tree: Tree) {
tree match {
case rt: RefTree if rt.symbol == notImplemented =>
u.error(tree.pos, "There was something left unimplemented")
case _ =>
}
super.traverse(tree)
}
}
}
}
It's very useful to get the tree expanded by the Scala compiler,
rather than the original source. Adding the -Xprint:typer
flag to
the Scala compiler will show code like the following:
// println("Hello world")
package $line4 {
object $read extends scala.AnyRef {
def <init>(): $line4.$read.type = {
$read.super.<init>();
()
};
object $iw extends scala.AnyRef {
def <init>(): type = {
$iw.super.<init>();
()
};
object $iw extends scala.AnyRef {
def <init>(): type = {
$iw.super.<init>();
()
};
private[this] val res1: Unit = scala.this.Predef.println("Hello world");
<stable> <accessor> def res1: Unit = $iw.this.res1
}
}
}
}
Adding the generated code to an issue is very useful for debugging.