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
+ ---
2
7
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 .
6
11
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.
9
20
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 ` .
11
22
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.
15
56
16
57
## Example: Xor to Either
17
58
18
59
The functional programming library [ cats] [ cats ] migrated recently from its ` Xor ` data type to ` Either ` from the standard library.
19
60
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 ` .
21
62
22
63
``` 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)
25
66
```
26
67
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.
30
70
31
71
``` scala
32
72
Replace (Symbol (" _root_.cats.data.Xor." ), q " Either " )
@@ -37,7 +77,10 @@ Replace(Symbol("_root_.cats.data.Xor.Right."), q"Right")
37
77
The ` Symbol(_root_...data.Xor) ` part is the scala.meta symbol referencing the class definition of ` cats.data.Xor ` .
38
78
As we saw in the ` either: Xor[HCursor ` diff above, references to ` Xor ` should become ` Either ` after the rewrite.
39
79
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
+
41
84
``` scala
42
85
Replace (Symbol (" _root_.cats.data.XorT." ), q " EitherT " ,
43
86
additionalImports = List (importer " cats.data.EitherT " )),
@@ -48,20 +91,40 @@ Imports can be removed with the `RemoveGlobalImport` patch
48
91
``` scala
49
92
RemoveGlobalImport (importer " cats.data.Xor " )
50
93
```
94
+
51
95
Nothing happens if the import does not appear in the source file.
52
96
Likewise, it's OK to add the same import twice, scalafix will de-duplicate it.
53
97
54
98
The full ` Xor ` to ` Either ` scalafix rewrite can be found [ here] [ xor2either ] and its accompanying test
55
99
suite [ here] [ xor2either-test ] .
56
100
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.
58
113
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
60
116
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 ` .
62
125
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 .
65
128
66
129
[ xor2either-test ] : https://github.com/scalacenter/scalafix/blob/f61136fad79afcdbb03528ce78c7928afc6eafd6/scalafix-nsc/src/test/resources/syntactic/Xor2Either.source
67
130
[ 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.
70
133
[ ghpages ] : http://github.com/scalacenter/scalafix
71
134
[ prevpost ] : http://scala-lang.org/blog/2016/10/24/scalafix.html
72
135
[ 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
73
138
[ install ] : https://scalacenter.github.io/scalafix/#Installation
74
139
[ 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