WartRemover is a flexible Scala code linting tool.
Add the following to your project/plugins.sbt
:
addSbtPlugin("org.wartremover" % "sbt-wartremover" % "1.2.1")
NOTE: sbt-wartremover
requires sbt version 0.13.5+.
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, use:
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.wartremover.warts.Var", "org.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, in Maven builds, and as 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.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")
Deprecated, use StringPlusAny.
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 objects should be used instead.
Scala's Any
type provides an ==
method which is not type-safe. Using this method allows obviously incorrect code like 5 == "5"
to compile. A better version which forbids equality checks across types (which always fail) is easily defined:
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
implicit final class AnyOps[A](self: A) {
def ===(other: A): Boolean = self == other
}
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()
Value of a final val
is inlined and can cause inconsistency during incremental compilation (see sbt/sbt/issues/1543 ).
file 1:
object c {
// Won't compile: final val is disabled
final val v = 1
}
file 2:
println(c.v)
Implicit conversions weaken type safety and always can be replaced by explicit conversions.
// Won't compile: implicit conversion is disabled
implicit def int2Array(i: Int) = Array.fill(i)("madness")
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.collection.JavaConversions
. This can make code difficult to understand and read about. The explicit conversions provided by scala.collection.JavaConverters
should be used 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
Descendants of a sealed type must be final or sealed. Otherwise this type can be extended in another file through its descendant.
file 1:
// Won't compile: Descendants of a sealed type must be final or sealed
sealed trait t
class c extends t
file 2:
class d extends c
Deprecated, use TraversableOps.
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.
Method overloading may lead to confusion and usually can be avoided.
// Won't compile: Overloading is disabled
class c {
def equals(x: Int) = {}
}
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))
Scala's String
interface provides a +
method that converts the operand to a String
via its toString
method. As mentioned in the documentation for the ToString
wart, this method is unreliable and brittle.
// Won't compile: Implicit conversion to string is disabled
"foo" + {}
{} + "bar"
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 objects. toString
should be explicitly overridden wherever used.
case object Foo { override val toString = "Foo" }
scala.collection.Traversable
has:
head
,tail
,init
,last
,reduce
,reduceLeft
andreduceRight
methods,
all of which will throw if the collection is empty. The program should be refactored to use:
headOption
,drop(1)
,dropRight(1)
,lastOption
,reduceOption
orfold
,reduceLeftOption
orfoldLeft
andreduceRightOption
orfoldRight
respectively,
to explicitly handle empty collections.
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
- TryPartial
- Var
Mutation breaks equational reasoning.
// Won't compile: var is disabled
var x = 100
while
loop usually indicates low-level code. If performance is not an issue, it can be replaced.
// Won't compile: while is disabled
while(i < 10) {
i += 1
...
}
A wart rule has to be an object that 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.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.