scalafix - a Scala rewrite tool


0.3.4

Join the chat at https://gitter.im/scalacenter/scalafix

Scalafix is a Scala code rewriting tool and library. This effort follows the Scala Center Advisory Board proposal: Clarification of Scala to Dotty migration path. The long-term goal of the project is to automate migration from Scala 2.x to Dotty, a next-generation Scala compiler.

The name "scalafix" is inspired by gofix, a similar tool used to migrate code written in the Go programming language.

Installation


Scalafix can used either as a tool or a library. Integrations are currently limited to the command-line, but it may be possible to add IDE integrations in the future.

sbt-scalafix

The sbt-plugin is the recommended integration for semantic rewrites.
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.3.4")

scalafix-core

Scalafix can be used as a library to run custom rewrites.
libraryDependencies += "ch.epfl.scala" % "scalafix-core" % "0.3.4" cross CrossVersion.full

By using scalafix as a library, you have full control of how/when/where rewrites are written.

When writing custom rewrites, you should decide what API you need. There currently available APIs are:

Scalafix as a library is still under heavy development. Feedback is very welcome. Be prepared for breaking changes.

scalafix-cli

The recommended way to install the scalafix command-line interface is with coursier.
// coursier
coursier bootstrap ch.epfl.scala:scalafix-fatcli_2.11.11:0.3.4 -f --main scalafix.cli.Cli -o scalafix
./scalafix --help

// homebrew
brew install --HEAD olafurpg/scalafmt/scalafix
scalafix --help

// wget
wget -O scalafix https://github.com/scalacenter/scalafix/blob/master/scalafix?raw=true
./scalafix --help

The fatcli module depends on the scalahost-nsc compiler plugin fatjar, making it unnecessary to pass in the --scalahost-nsc-plugin-path flag.

Once the scalafix cli is installed, consult the help page for further usage instructions

scalafix 0.3.4+34-f2501799
Usage: scalafix [options]
  --usage  
        Print usage and exit
  --help | -h  
        Print help message and exit
  --config | -c  <.scalafix.conf OR imports.organize=false>
        Scalafix configuration, either a file path or a hocon string
  --classpath  <entry1.jar:entry2.jar>
        java.io.File.pathSeparator separated list of jar files or directories
        containing classfiles and `semanticdb` files. The `semanticdb`
        files are emitted by the scalahost-nsc compiler plugin and
        are necessary for the semantic API to function. The
        classfiles + jar files are necessary forruntime compilation
        of quasiquotes when extracting symbols (that is,
        `q"scala.Predef".symbol`).
  --sourcepath  <File2.scala:File1.scala:src/main/scala>
        java.io.File.pathSeparator separated list of Scala source files OR
        directories containing Scala source files.
  --scalahost-nsc-plugin-path  <$HOME/.ivy2/cache/.../scalahost-nsc_2.11.8.jar>
        File path to the scalahost-nsc compiler plugin fatjar, the same path
        that is passed in `-Xplugin:/scalahost.jar`.
        (optional) skip this option by using the "scalafix-fatcli"
        module instead of "scalafix-cli."
  --rewrites  <ProcedureSyntax OR
               file:LocalFile.scala OR
               scala:full.Name OR
               https://gist.com/.../Rewrite.scala>
        Rewrite rules to run.
        NOTE. rewrite.rules = [ .. ] from --config will also run.
  --files | -f  <File1.scala File2.scala>
        Files to fix. Runs on all *.scala files if given a directory.
  --in-place | -i  
        If true, writes changes to files instead of printing to stdout.
  --out-from  </shared/>
        Regex that is passed as first argument to
        fileToFix.replaceAll(outFrom, outTo).
  --out-to  </custom/>
        Replacement string that is passed as second argument to
        fileToFix.replaceAll(outFrom, outTo)
  --single-thread  
        If true, run on single thread. If false (default), use all available cores.
  --debug  
        If true, prints out debug information.
  --no-sys-exit  
        If true, does not sys.exit at the end. Useful for example in sbt-scalafix.

Examples:
  $ scalafix --rewrites ProcedureSyntax Code.scala # print fixed file to stdout
  $ cat .scalafix.conf
  rewrites = [ProcedureSyntax]
  $ scalafix Code.scala # Same as --rewrites ProcedureSyntax
  $ scalafix -i --rewrites ProcedureSyntax Code.scala # write fixed file in-place

Exit status codes:
 Ok=0
 UnexpectedError=1
 ParseError=2
 ScalafixError=3

cbt

There is an experimental cbt plugin for scalafix.

Pre-release

Our CI infrastructure publishes a pre-release version on every merge into master. These pre-releases make it possible to try out new scalafix features that have not yet been officially released.
// sbt
resolvers += Resolver.bintrayRepo("scalameta", "maven") // necessary to resolve pre-release
libraryDependencies += "ch.epfl.scala" % "scalafix-core" % "0.3.4+34-f2501799" cross CrossVersion.full
libraryDependencies += "ch.epfl.scala" % "scalafix-fatcli" % "0.3.4+34-f2501799" cross CrossVersion.full
libraryDependencies += "ch.epfl.scala" % "scalafix-testkit" % "0.3.4+34-f2501799" % Test cross CrossVersion.full
// cli
coursier bootstrap ch.epfl.scala:scalafix-fatcli_2.11.11:0.3.4+34-f2501799 -r bintray:scalameta/maven -f --main scalafix.cli.Cli -o scalafix

Configuration


Scalafix reads configuration from a file using HOCON syntax. I recommend you put a file .scalafix.conf into the root directory of your project.

rewrites

You can either run a pre-made that comes with scalafix or a custom rewrite that you write yourself.

Pre-made rewrites

Available rewrites are listed in Rewrites.
rewrites = [ExplicitImplicit] # No rewrites are run if empty.

Custom rewrites

rewrites = [
  "file:readme/MyRewrite.scala" // from local file
  // from url
  "https://gist.githubusercontent.com/olafurpg/fc6f43a695ac996bd02000f45ed02e63/raw/ff0a1a8c71936bb3d06f804274dc042131fd8392/ExampleRewrite.scala"
  // from fully qualified name on classpath
  "scala:scalafix.rewrite.ExplicitImplicit"
]

imports

NOTE. This feature is new and may still have bugs that cause rewritten code to not compile. Please report back you bump into issues.

Scalafix can organize imports. If you use import patches such as Add/RemoveGlobalImport, you need to allow scalafix to organize you imports. To minimize the diff from import patches, it's best to run scalafix once with organize imports enabled and no zero patches.

// Group and sort imports
imports.organize = true
// Example relative import: `import collection.immutable`
// Expanded: `import scala.collection.immutable`
imports.expandRelative = true
// Removes global imports that -Ywarn-unused-import complains about
// Requires sbt-scalafix/ScalafixMirror
imports.removeUnused = true
// Groups are separated by a blank line, example:
// import com.a
// import com.b
//
// import scala.collection.immutable._
imports.groups = [
  "com.mycompany.*"
  "scala\\..*"
]

patches

For simple use-cases, it's possible to write custom rewrites directly .scalafix.conf.
patches.removeGlobalImports = [
  "scala.collection.mutable" # scala.meta.Importee
]
patches.addGlobalImports = [
  "scala.collection.immutable"
]
patches.replacements = [
  {
    from = "_root_.scala.Seq." // scala.meta.Symbol
    to = "Seq"
    additionalImports = [ "scala.collection.immutable.Seq" ]
  }
]
// Helper to see which symbols appear in your source files
debug.printSymbols = true
For more advanced use-cases, I recommend you use scalafix-core as a library.

All options

The following is the list of all available options along with their default values.
patches.removeGlobalImports = []
patches.addGlobalImports = []
patches.replacements = []
reporter.outStream = java.io.PrintStream@295950b
reporter.minSeverity = "[34minfo[0m"
reporter.filter.".*"
reporter.includeLoggerName = false
parser = scala.meta.internal.parsers.ScalametaParser$$anon$202@c8a29a0
debug.printSymbols = false
fatalWarnings = true
rewrite = empty
dialect = Scala211
imports.organize = false
imports.alwaysUsed = []
imports.expandRelative = false
imports.groups = [
  "scala.language.*"
  "(scala|scala\..*)$"
  "(java|java\..*)$"
]
imports.removeUnused = false
imports.groupByPrefix = false
imports.spaceAroundCurlyBrace = false

Rewrites


Scalafix comes with a few rewrites out-of-the-box. These rewrites have been chosen to meet the long-term goal of scalafix to clarify the Scala to Dotty migration path. To create custom rewrites, see scalafix-core.

ExplicitImplicit

Dotty requires implicit vals and defs to explicitly annotate return types. The ExplicitImplicit rewrite inserts the inferred type from the compiler for implicit definitions that are missing an explicit return type. For example,
// before
implicit val tt = liftedType
// after
implicit val tt: TypedType[R] = liftedType

ProcedureSyntax

"Procedure syntax" is not supported in Dotty. Methods that use procedure syntax should use regular method syntax instead. For example,
// before: procedure syntax
def main(args: Seq[String]) {
  println("Hello world!")
}
// after: regular syntax
def main(args: Seq[String]): Unit = {
  println("Hello world!")
}

VolatileLazyVal

Adds a @volatile annotation to lazy vals. The @volatile annotation is needed to maintain thread-safe behaviour of lazy vals in Dotty.

// before
lazy val x = ...
// after
@volatile lazy val x = ...

With @volatile, Dotty uses a deadlock free scheme that is comparable-if-not-faster than the scheme used in scalac.

ExplicitUnit

Adds an explicit Unit return type to def declarations without a return type

// before
trait A {
  def doSomething
}
// after
trait A {
  def doSomething: Unit
}

Such members already have a return type of Unit and sometimes this is unexpected. Adding an explicit return type makes it more obvious.

Planned rewrites...

See here

scalafix-testkit


Use scalafix-testkit for a rapid edit/run/debug cycle while implementing semantic scalafix rewrites.

Note. Scalafix-testkit is a new module under active development and is currently only available as a pre-release version. To depend on scalafix-testkit

resolvers += Resolver.bintrayRepo("scalameta", "maven")
libraryDependencies += "ch.epfl.scala" % "scalafix-testkit" % "0.3.4+34-f2501799" % Test cross CrossVersion.full

Tests are written in .source text files in some directory of your project. A good place to put the tests is under src/test/resources so that tests re-run on file save when using ~test.

Unit tests are written in the following format:

<<< This is a test name
object OriginalCode
>>>
object ExpectedCode
<<< This is another test file
object OriginalCode
>>>
object ExpectedCode

To setup a test suite with scalafix-testkit:

package scalafix.tests

import scalafix.testkit._

class SemanticTests
    extends SemanticRewriteSuite( /* optionally pass in custom classpath */ ) {
  // directory containing .source files
  val testDir = "scalafix-tests/src/test/resources"
  DiffTest.fromFile(new java.io.File(testDir)).foreach(runDiffTest)
}

Define .scalafix.conf configuration at the top of a .source file:

$ cat MyTest.source
rewrites = [ProcedureSyntax]
<<< basic main function
object Main { def main(args: Seq[String]) { println(args) } }
>>>
object Main { def main(args: Seq[String]): Unit = { println(args) } }

When a test fails, a failure report shows a diff between the expected and obtained output of the rewrite.

===========
=> Obtained
===========
object Main { def main(args: Seq[String]) { println(args) } }

=======
=> Diff
=======
- object Main { def main(args: Seq[String]) { println(args) } }
+ object Main { def main(args: Seq[String]): Unit = { println(args) } }

To run only a single test, prefix the test name with ONLY

<<< ONLY This test will run
object OriginalCode
>>>
object ExpectedCode
<<< This test is skipped
object OriginalCode
>>>
object ExpectedCode

To skip a single test, prefix the test name with SKIP

<<< SKIP This test will NOT run
object OriginalCode
>>>
object ExpectedCode
<<< This test will run
object OriginalCode
>>>
object ExpectedCode

For more resources on scalafix-testkit, clone the scalafix repo and look at the sources in the scalafix-tests module.

FAQ / Troubleshooting


If you have any questions, don't hesitate to ask on Gitter. Join the chat at https://gitter.im/scalacenter/scalafix

Enclosing tree [2873] does not include tree [2872]

Scalafix requires code to compile with the scalac option -Yrangepos. A macro that emits invalid tree positions is usually the cause of compiler errors triggered by -Yrangepos. Other tools like the presentation compiler (ENSIME) or scala.meta semantic api also require -Yrangepos to work properly.

sbt.Init$RuntimeUndefined: References to undefined settings at runtime.

You might be using an old version of sbt. sbt-scalafix requires sbt 0.13.13 or higher.

(root/compile:compileIncremental) java.lang.reflect.InvocationTargetException

You might have a typo in .scalafix.conf, run last root/compile:compileIncremental to see the full stack trace.

Changelog


0.3.4

0.3.3

0.3.2

See merged PRs.

0.3.1

0.3.0

0.2.1

0.2.0

0.1.0