pk11 / wartremover

Flexible Scala code linting tool

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

WartRemover

Build Status

WartRemover is a flexible Scala code linting tool.

Usage

WartRemover can be used in the following ways:

  • As a command-line tool
  • As a compiler plugin
  • To derive macros

Command-line

Compile the command-line tool via sbt assembly.

$ ./wartremover -traverser org.brianmckenna.wartremover.warts.Unsafe src/main/scala/wartremover/Plugin.scala
src/main/scala/wartremover/Plugin.scala:15: error: var is disabled
  private[this] var traversers: List[WartTraverser] = List.empty
                    ^

Compiler plugin

Add the following to build.sbt:

resolvers += Resolver.sonatypeRepo("releases")

addCompilerPlugin("org.brianmckenna" %% "wartremover" % "0.7")

scalacOptions in (Compile, compile) += "-P:wartremover:traverser:org.brianmckenna.wartremover.warts.Unsafe"

Macros

You can make any wart into a macro, like so:

scala> import language.experimental.macros
import language.experimental.macros

scala> import org.brianmckenna.wartremover.warts.Unsafe
import org.brianmckenna.wartremover.warts.Unsafe

scala> def safe(expr: Any) = macro Unsafe.asMacro
safe: (expr: Any)Any

scala> safe { null }
<console>:10: error: null is disabled
              safe { null }
                     ^

Warts

Here is a list of built-in warts under the org.brianmckenna.wartremover.warts package.

Any

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")

Any2StringAdd

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

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]

EitherProjectionPartial

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.

IsInstanceOf

isInstanceOf violates parametricity. Refactor so that the type is established statically.

// Won't compile: isInstanceOf is disabled
x.isInstanceOf[String]

NonUnitStatements

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

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

null is a special value that inhabits all reference types. It breaks type safety.

// Won't compile: null is disabled
val s: String = null

OptionPartial

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

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

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

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))

Unsafe

Checks for the following warts:

  • Any
  • Any2StringAdd
  • AsInstanceOf
  • EitherProjectionPartial
  • IsInstanceOf
  • NonUnitStatements
  • Null
  • OptionPartial
  • Product
  • Return
  • Serializable
  • Var

Var

Mutation breaks equational reasoning.

// Won't compile: var is disabled
var x = 100

Writing Wart Rules

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)
      }
    }
  }
}

Reporting Issues

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.

About

Flexible Scala code linting tool


Languages

Language:Scala 68.4%Language:Shell 31.6%