ExtraWarts

Build Status Maven Central

Some extra WartRemover warts that aren't available out of the box.

Versions

ExtraWarts version WartRemover version Scala version sbt version Supported
1.0.3 2.2.1 2.11.11, 2.12.3 0.13.x, 1.0.x
0.3.0 2.1.1 2.11.8, 2.12.2 0.13.x No

Usage

  1. Setup WartRemover.

  2. Add the following to your plugins.sbt:

    addSbtPlugin("org.danielnixon" % "sbt-extrawarts" % "1.0.3")
  3. Add the following to your build.sbt:

    wartremoverWarnings ++= Seq(
      ExtraWart.EnumerationPartial,
      ExtraWart.FutureObject,
      ExtraWart.GenMapLikePartial,
      ExtraWart.GenTraversableLikeOps,
      ExtraWart.GenTraversableOnceOps,
      ExtraWart.ScalaGlobalExecutionContext,
      ExtraWart.StringOpsPartial,
      ExtraWart.ThrowablePartial,
      ExtraWart.TraversableOnceOps,
      ExtraWart.UnsafeContains)

Warts

EnumerationPartial

scala.Enumeration#withName is disabled because is will throw a NoSuchElementException if there is no value matching the specified name. You can wrap it in an implicit that might look like this:

implicit class EnumerationWrapper[A <: Enumeration](val enum: A) extends AnyVal {
  @SuppressWarnings(Array("org.danielnixon.extrawarts.EnumerationPartial"))
  def withNameOpt(s: String): Option[A#Value] = {
    catching[A#Value](classOf[NoSuchElementException]) opt enum.withName(s)
  }
}

FutureObject

scala.concurrent.Future has a reduce method that can throw NoSuchElementException if the collection is empty. Use Future#fold instead.

GenMapLikePartial

scala.collection.GenMapLike has an apply method that can throw NoSuchElementException if there is no mapping for the given key. Use GenMapLike#get instead.

GenTraversableLikeOps

WartRemover's TraversableOps wart only applies to scala.collection.Traversable. The GenTraversableLikeOps wart extends it to everything that implements scala.collection.GenTraversableLike.

scala.collection.GenTraversableLike has:

  • head,
  • tail,
  • init and
  • last methods,

all of which will throw if the list is empty. The program should be refactored to use:

  • GenTraversableLike#headOption,
  • GenTraversableLike#drop(1),
  • GenTraversableLike#dropRight(1) and
  • GenTraversableLike#lastOption respectively,

to explicitly handle both populated and empty GenTraversableLikes.

ScalaGlobalExecutionContext

Scala's global execution context scala.concurrent.ExecutionContext#global is disabled. Declare a dependency on an ExecutionContext instead. See MUST NOT hardcode the thread-pool / execution context.

StringOpsPartial

scala.collection.immutable.StringOps has

  • toBoolean,
  • toByte,
  • toShort,
  • toInt,
  • toLong,
  • toFloat and
  • toDouble methods,

all of which will throw NumberFormatException (or IllegalArgumentException in the case of toBoolean) if the string cannot be parsed.

You can hide these unsafe StringOps methods with an implicit class that might look something like this:

implicit class StringWrapper(val value: String) extends AnyVal {
  import scala.util.control.Exception.catching

  @SuppressWarnings(Array("org.danielnixon.extrawarts.StringOpsPartial"))
  def toIntOpt: Option[Int] = catching[Int](classOf[NumberFormatException]) opt value.toInt
}

ThrowablePartial

java.lang.Throwable#getCause is disabled because it can return null. You can wrap it in an implicit that might look like this:

implicit class ThrowableWrapper(val t: Throwable) extends AnyVal {
  @SuppressWarnings(Array("org.danielnixon.extrawarts.ThrowablePartial"))
  def cause: Option[Throwable] = Option(t.getCause)
}

TraversableOnceOps

scala.collection.TraversableOnce has a reduceLeft method that will throw if the collection is empty. Use TraversableOnce#reduceLeftOption or TraversableOnce#foldLeft instead.

scala.collection.TraversableOnce has

  • max,
  • min,
  • maxBy and
  • minBy methods,

all of which will throw UnsupportedOperationException if the collection is empty. You can wrap these unsafe methods in an implicit class that might look something like this:

implicit class TraversableOnceWrapper[A](val traversable: TraversableOnce[A]) extends AnyVal {
  @SuppressWarnings(Array("org.danielnixon.extrawarts.TraversableOnceOps"))
  def maxOpt[B >: A](implicit cmp: Ordering[B]): Option[A] = {
    if (traversable.isEmpty) None else Some(traversable.max(cmp))
  }
}

UnsafeContains

scala.collection.SeqLike#contains is based on universal equality (seq.contains(elem) is equivalent to seq.exists(_ == elem)) so it is disabled.

If you use Scalaz, you can replace it with something like this:

import scalaz._
import Scalaz._

implicit class SeqLikeWrapper[A](val seq: SeqLike[A, _]) extends AnyVal {
  def containsSafe(elem: A)(implicit ev: Equal[A]): Boolean = {
    seq.exists(x => elem === x)
  }
}

If you use Cats, you can replace it with something like this:

import cats.Eq
import cats.implicits._

implicit class SeqLikeWrapper[A](val seq: SeqLike[A, _]) extends AnyVal {
  def containsSafe(elem: A)(implicit ev: Eq[A]): Boolean = {
    seq.exists(x => elem === x)
  }
}

Or simply (if you're willing to tolerate the universal equality):

implicit class SeqLikeWrapper[A](val seq: SeqLike[A, _]) extends AnyVal {
  @SuppressWarnings(Array("org.danielnixon.extrawarts.UnsafeContains"))
  def containsSafe(elem: A): Boolean = seq.contains(elem)
}

See also