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.unsafeTo turn on all available errors (some have false positives), use:
wartremoverErrors ++= Warts.allSimilarly, to just issue warnings instead of errors for all built-in warts, you can use:
wartremoverWarnings ++= Warts.all // or Warts.unsafeYou 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.allTo 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 = nullFinally, 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] = scalaMapDescendants 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 cDeprecated, 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
falseNothing 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.emptynull is a special value that inhabits all reference types. It breaks
type safety.
// Won't compile: null is disabled
val s: String = nullScala 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,reduceLeftandreduceRightmethods,
all of which will throw if the collection is empty. The program should be refactored to use:
headOption,drop(1),dropRight(1),lastOption,reduceOptionorfold,reduceLeftOptionorfoldLeftandreduceRightOptionorfoldRightrespectively,
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 = 100while 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.
