Skip to content

Commit 631ebf1

Browse files
committed
Expand on details
1 parent c958340 commit 631ebf1

File tree

1 file changed

+89
-22
lines changed

1 file changed

+89
-22
lines changed
Lines changed: 89 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,72 @@
1-
# Refactor with scalafix
1+
---
2+
layout: blog
3+
post-type: blog
4+
by: Ólafur Páll Geirsson
5+
title: "Refactor with scalafix v0.3"
6+
---
27

3-
We are happy to announce the release of [scalafix v0.3][scalafix].
4-
This release introduces a new `Rewrite` and `Patch` API powered by the [scala.meta semantic API][meta-1.6].
5-
We are excited about this release because we believe it enables a number promising tooling applications.
8+
I am happy to announce the release of [scalafix v0.3][scalafix].
9+
This release leverages the new scala.meta semantic API to provide a re-designed `Rewrite` and `Patch` API for refactorings.
10+
Let me explain what that means word by word.
611

7-
Scala.meta recently had its first release of its semantic API; the semantic API provides operations to query information from the compiler.
8-
This has enabled rewrites in scalafix to query for the so-called "symbol" of a name that appears in a Scala source file.
12+
## scala.meta semantic API
13+
14+
Scala.meta recently announced the [first release of its semantic API][meta-1.6].
15+
This release is the product of close collaboration for the past several months between
16+
[@xeno-by](https://twitter.com/xeno_by) at Twitter and myself at the Scala Center.
17+
The semantic API provides operations to query information from the compiler.
18+
19+
The first version of the scala.meta semantic API makes it possible to query for the "symbol" of a name that appears in a Scala source file.
920
A symbol is a unique identifier of a definition such as a class, val, def or trait.
10-
This ability to query for symbols opens possibilities for a great number of refactorings including imports management and identifier renaming.
21+
For example, the symbol of `println` from the standard library is `_root_.scala.Predef.println(Ljava/lang/Object;)V`.
1122

12-
The long-term mission of scalafix is to help automate the migration of deprecated Scala 2.x features to Dotty.
13-
However, scalafix can be used for more than migrating between Scala versions; it can also be used for ad-hoc library and application migrations.
14-
To demonstrate what rewrites can be implemented with scalafix v0.3, let's step through an example of a real usecase.
23+
Symbols are created by "scalahost", a compiler plugin.
24+
Scalahost emits symbols of a source file in the form of a "semantic database".
25+
The semantic database can be persisted to files on disk and loaded for later analysis.
26+
Semantic databases from different compilation units, potentially produced by different
27+
versions of the Scala compiler, can be merged.
28+
This opens possibilities for large-scale code analysis.
29+
30+
The introduction of the scala.meta semantic API is a game changer for scalafix.
31+
The ability to query for symbols open possibilities for many scalafix rewrites.
32+
33+
## Rewrite: meta.Tree => Seq[Patch]
34+
35+
A scalafix `Patch` is a small operation that can produce a diff on a Scala source file.
36+
A patch can either be a "token patch" or a "tree patch".
37+
38+
Token patches are low-level but give full control of how small details are handled in a source files, for example formatting and comments
39+
Example token patches are `Remove(token)` and `AddLeft(token, toAdd: String)`, which removes or prepends a string to `token`, respectively.
40+
41+
Tree patches are high-level and allow rewrite author to declaratively explain what operation to perform.
42+
An example tree patch is `AddGlobalImport(importer)`, which adds a new import to the top of a file if it does not exist.
43+
Observe that `AddGlobalImport` does not worry about token-level details such as whether the user groups imports by prefix (e.g., `import a.{b, c}`).
44+
45+
Tree and token patches build a small algebra of operations that can be composed to build complex refactorings.
46+
A challenge with composing patches is to figure out what to do on conflicts.
47+
For example, what happens when one patch renames a token while the other patch removes the same token?
48+
The current strategy in scalafix is to try and resolve as many conflicts as possible on the tree patch level.
49+
Once we we only have token patches, it is harder to resolve conflicts.
50+
Unsolvable conflicts on the token level abort the refactoring.
51+
In the future, we hope to support more advanced conflict resolution strategies.
52+
53+
In a nutshell, a scalafix `Rewrite` is a `scala.meta.Tree => Seq[Patch]` function.
54+
The tree is backed by the scala.meta semantic API, so the rewrite is able to query for compiler information such as symbols.
55+
To demonstrate how rewrites are implemented with scalafix v0.3, let's step through an example use-case.
1556

1657
## Example: Xor to Either
1758

1859
The functional programming library [cats][cats] migrated recently from its `Xor` data type to `Either` from the standard library.
1960
It requires a few mechanical steps to migrate code to use `Either` instead of `Xor`.
20-
For example, below is a diff that's taken from [circe][xor-cire]'s migration to `Either`.
61+
For example, below is a diff that's taken from [circe][xor-circe]'s migration to `Either`.
2162

2263
```diff
23-
- final def either: Xor[HCursor, HCursor] = if (succeeded) Xor.right(any) else Xor.left(any)
24-
+ final def either: Either[HCursor, HCursor] = if (succeeded) Right(any) else Left(any)
64+
-final def either: Xor[HCursor, HCursor] = if (succeeded) Xor.right(any) else Xor.left(any)
65+
+final def either: Either[HCursor, HCursor] = if (succeeded) Right(any) else Left(any)
2566
```
2667

27-
Scalafix rewrites are composed of so-called "patches".
28-
A patch describes a single refactoring operation.
29-
`Replace` is one patch that can be used to rename identifiers
68+
For this particular rewrite, we are able to get away with only using tree patches.
69+
`Replace` is one tree patch that can be used to replace usage of a reference such as a type, term or a static method.
3070

3171
```scala
3272
Replace(Symbol("_root_.cats.data.Xor."), q"Either")
@@ -37,7 +77,10 @@ Replace(Symbol("_root_.cats.data.Xor.Right."), q"Right")
3777
The `Symbol(_root_...data.Xor)` part is the scala.meta symbol referencing the class definition of `cats.data.Xor`.
3878
As we saw in the `either: Xor[HCursor` diff above, references to `Xor` should become `Either` after the rewrite.
3979

40-
To introduce new imports on rename, it's possible to pass in `additionalImports`
80+
Symbols are normalized by default, so the `cats.data.Xor.Right` replace patch will handle the `Xor.Right` type, `Xor.Right` companion object as well as the `Right.apply` constructor method.
81+
82+
To introduce new imports, it's possible to pass in `additionalImports`
83+
4184
```scala
4285
Replace(Symbol("_root_.cats.data.XorT."), q"EitherT",
4386
additionalImports = List(importer"cats.data.EitherT")),
@@ -48,20 +91,40 @@ Imports can be removed with the `RemoveGlobalImport` patch
4891
```scala
4992
RemoveGlobalImport(importer"cats.data.Xor")
5093
```
94+
5195
Nothing happens if the import does not appear in the source file.
5296
Likewise, it's OK to add the same import twice, scalafix will de-duplicate it.
5397

5498
The full `Xor` to `Either` scalafix rewrite can be found [here][xor2either] and its accompanying test
5599
suite [here][xor2either-test].
56100

57-
To learn more about how to start with scalafix, visit the [installation docs][install]!
101+
## Try it out!
102+
You can use scalafix both as a library and a tool.
103+
104+
The recommended way to use scalafix as a library is with the [`sbt-scalahost`][sbt-scalahost] plugin.
105+
By using scalafix as a library, you have full control of how, where and when to run rewrites.
106+
107+
The recommended way to use scalafix as tool is the [`sbt-scalafix`][sbt-scalafix] plugin.
108+
It's possible define custom tree patches in the [`.scalafix.conf` configuration file][config-patches].
109+
110+
The long-term mission of scalafix is to help automate the migration of deprecated Scala 2.x features to Dotty.
111+
However, as I have hopefully demonstrated in this post, scalafix can be used for more than just migrating between Scala versions.
112+
Scalafix can also be used for ad-hoc library and application migrations.
58113

59-
## Cross-building
114+
I am excited to see what applications the community can build with scala.meta and scalafix.
115+
Some promising ideas that have floated around include
60116

61-
TODO: Shane
117+
- parse and run rewrites from `@deprecated` warning messages. This would enable library authors
118+
to provide exectuable migration guides.
119+
- use scalafix to cross-build against multiple similar APIs. For example, a
120+
library author could write against scalaz and rewrite to cats.
121+
- build a code search web-interface with "jump to definition" functionality
122+
powered by the scala.meta's semantic database.
123+
- replace usage of the infamous `any2StringAdd` in favor of string interpolators or explicit `.toString`.
124+
- replace usages of `scala.Seq`, which can be mutable, in favor of `scala.collection.immutable.Seq`.
62125

63-
## Custom rewrites
64-
You can write your own custom rewrites.
126+
If this sounds exciting to you, join us!
127+
I am happy to answer any question in the [scalafix gitter][gitter] channel.
65128

66129
[xor2either-test]: https://github.com/scalacenter/scalafix/blob/f61136fad79afcdbb03528ce78c7928afc6eafd6/scalafix-nsc/src/test/resources/syntactic/Xor2Either.source
67130
[xor2either]: https://github.com/scalacenter/scalafix/blob/f61136fad79afcdbb03528ce78c7928afc6eafd6/core/src/main/scala/scalafix/rewrite/Xor2Either.scala
@@ -70,5 +133,9 @@ You can write your own custom rewrites.
70133
[ghpages]: http://github.com/scalacenter/scalafix
71134
[prevpost]: http://scala-lang.org/blog/2016/10/24/scalafix.html
72135
[scalafix]: https://scalacenter.github.io/scalafix/#0.3.0
136+
[sbt-scalafix]: https://scalacenter.github.io/scalafix/#sbt-scalafix
137+
[sbt-scalahost]: https://scalacenter.github.io/scalafix/#sbt-scalahost
73138
[install]: https://scalacenter.github.io/scalafix/#Installation
74139
[meta-1.6]: https://github.com/scalameta/scalameta/blob/master/changelog/1.6.0.md#semantic-api
140+
[config-patches]: https://scalacenter.github.io/scalafix/#patches
141+
[gitter]: https://gitter.im/scalacenter/scalafix

0 commit comments

Comments
 (0)