Scalafix rules for Typelevel projects

Scalafix rules for Typelevel projects

Continuous Integration License Discord Maven Central

This is a set of Scalafix rules to provide automated rewrites and linting for code which makes use of Typelevel libraries.

Installation

Follow the instructions to set up Scalafix in your project.

Then you can add the typelevel-scalafix rules to your sbt project using the scalafixDependencies key.

// To add all Scalafix rules
ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix" % "0.1.5"

// To add only cats Scalafix rules
ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats" % "0.1.5"
// To add only cats-effect Scalafix rules
ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-cats-effect" % "0.1.5"
// To add only fs2 Scalafix rules
ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-fs2" % "0.1.5"
// To add only http4s Scalafix rules
ThisBuild / scalafixDependencies += "org.typelevel" %% "typelevel-scalafix-http4s" % "0.1.5"

Usage

Once enabled, you can configure the rules that will run when you use the scalafix command by adding them to your .scalafix.conf file, or run individual rules by providing them as arguments to the scalafix command.

// .scalafix.conf
rules = [
  TypelevelUnusedIO
  TypelevelMapSequence
  TypelevelAs
  TypelevelUnusedShowInterpolator
  TypelevelFs2SyncCompiler
  TypelevelHttp4sLiteralsSyntax
]
// in the sbt shell
> scalafix TypelevelUnusedIO

Rules for cats

TypelevelMapSequence

This rule detects call sequences like .map(f).sequence and .map(f).sequence_, since they can be replaced by .traverse(f) and .traverse_(f) respectively.

Examples

NonEmptyList.one(1).map(Const.apply[Int, String]).sequence /*
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.map(f).sequence can be replaced by .traverse(f) */

TypelevelAs

This rule detects call sequences like .map(_ => ()) and .map(_ => <some literal>), since they can be replaced by .void and .as(<some literal>) respectively.

Examples

List(1, 2, 3).map(_ => ()) /* assert: TypelevelAs.as
^^^^^^^^^^^^^^^^^^^^^^^^^^
.map(_ => ()) can be replaced by .void */

Limitations

At the moment this rule is only applied to applications of map where the argument function returns a literal value.

This is because it's not clear whether any given variable in a Scala program has been evaluated yet.

For example, in the expression .map(_ => someVariable), if someVariable is a lazy val refactoring to use .as could change the behaviour of the program, since as evaluates its argument strictly.

TypelevelUnusedShowInterpolator

This rule detects usages of the cats show interpolator that do not interpolate any variables.

Examples

val foo = show"bar" /* assert: TypelevelUnusedShowInterpolator.unusedShowInterpolator
          ^^^^^^^^^
          This show interpolator contains no interpolated variables. */

Rules for cats-effect

TypelevelUnusedIO

This rule detects discarded IO expressions, since those expressions will not run as part of an IO program unless they are composed into the final result.

Examples

{
  val foo = IO.println("foo")

  foo /*
  ^^^
  This IO expression is not used. */

  IO.println("bar")
}

for {
  _ <- IO.println("foo")
  _ = IO.println("bar") /*
      ^^^^^^^^^^^^^^^^^
      This IO expression is not used. */
} yield "baz"

Limitations

At the moment this rule is most useful in concrete usages of IO, since detecting discarded expressions in a generic context requires access to inferred type information.

This also causes problems with checking for discarded expressions that are using extension methods.

This means that expressions like the following currently won't be detected:

OptionT(IO.some(1)).value

EitherT(IO.println("foo").attempt).value

// .attemptNarrow is provided as a `cats.ApplicativeError` extension method
IO.println("foo").timeout(50.millis).attemptNarrow[TimeoutException]

Rules for fs2

TypelevelFs2SyncCompiler

This rule detects usages of the fs2 Sync compiler which can have surprising semantics with regard to (lack of) interruption (e.g., typelevel/fs2#2371).

Examples

def countChunks[F[_]: Sync, A](stream: Stream[F, A]): F[Long] =
  stream.chunks.as(1L).compile.foldMonoid /*
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  FS2's Sync compiler should be avoided due to its surprising semantics.
  Usually this means a Sync constraint needs to be changed to Concurrent or upgraded to Async. */

Rules for http4s

TypelevelHttp4sLiteralsSyntax

This rule rewrites uses of Uri.unsafeFromString("...") and friends with uri"...".

Conduct

Participants are expected to follow the Scala Code of Conduct while discussing the project on GitHub and any other venues associated with the project.

License

All code in this repository is licensed under the Apache License, Version 2.0. See LICENSE.