|
| 1 | +@import Main._ |
| 2 | +@import scalafix.Readme._ |
| 3 | +@import scalafix.rewrite._ |
| 4 | +@import scalafix.{Versions => V} |
| 5 | +@import scalafix.cli.Cli |
| 6 | + |
| 7 | +@sect{Creating your own rewrite} |
| 8 | + @p |
| 9 | + It is possible to implement custom rewrites with Scalafix. |
| 10 | + Depending on what your rewrite does, it may be a lot of work or very |
| 11 | + little work. Don't hestitate to get an estimate on @gitter for |
| 12 | + how complicated it would be to implement your rewrite. |
| 13 | + |
| 14 | + @sect{Before you begin} |
| 15 | + Before you dive right into the code of your rewrite, it might be |
| 16 | + good to answer the following questions first. |
| 17 | + |
| 18 | + @sect{What diff do you want to make?} |
| 19 | + Scalafix is a tool to automatically produce diffs. |
| 20 | + Before implementing a rewrite, it's good to manually migrate/refactor a |
| 21 | + few examples first. Manually refactoring code is helpful |
| 22 | + to estimate how complicated the rewrite is. |
| 23 | + |
| 24 | + @sect{Is the expected output unambiguous?} |
| 25 | + Does the rewrite require manual intervention or do you always know what |
| 26 | + output the rewrite should produce? Scalafix currently does not yet support |
| 27 | + interactive refactoring. However, Scalafix has support for configuration, |
| 28 | + which makes it possible to leave some choice to the user on how the rewrite |
| 29 | + should behave. |
| 30 | + |
| 31 | + @sect{Who will use your rewrite?} |
| 32 | + The target audience/users of your rewrite can impact the implementation the |
| 33 | + rewrite. If you are the only end-user of the rewrite, then you can maybe |
| 34 | + take shortcuts and worry less about rare corner cases that may be easier to fix |
| 35 | + manually. If your rewrite is intended to be used by the entire Scala |
| 36 | + community, then you might want to be more careful with corner cases. |
| 37 | + |
| 38 | + @sect{What code will your rewrite fix?} |
| 39 | + Is your rewrite specific to a particular codebase? Or is the rewrite intended |
| 40 | + to be used on codebases that you don't have access to? If your rewrite is |
| 41 | + specific to one codebase, then it's easier to validate if your rewrite |
| 42 | + is ready. You may not even need tests, since your codebase is your only test. |
| 43 | + If your rewrite is intended to be used in any random codebase, you may |
| 44 | + want to have tests and put more effort into handling corner cases. |
| 45 | + In general, the smaller the target domain of your rewrite, the easier it |
| 46 | + is to implement a rewrite. |
| 47 | + |
| 48 | + @sect{How often will your rewrite run?} |
| 49 | + Are you writing a one-off migration script or will your rewrite run on |
| 50 | + every pull request? A rewrite that runs on every pull request should ideally |
| 51 | + have some unit tests and be documented so that other people can help maintain |
| 52 | + the rewrite. |
| 53 | + |
| 54 | + @sect{scalacenter/scalafix.g8} |
| 55 | + @p |
| 56 | + Run the following commands to generate a skeleton project |
| 57 | + |
| 58 | + @hl.scala |
| 59 | + // by convention, --rewrite= should match the GitHub repo name. |
| 60 | + // this makes it possible for users to run `scalafix github:org/reponame/v1.0` |
| 61 | + sbt new scalacenter/scalafix.g8 --rewrite "reponame" --version=v1.0 |
| 62 | + cd reponame/scalafix |
| 63 | + sbt tests/test |
| 64 | + @p |
| 65 | + Note that the @code{scalafix} directory is a self-contained sbt build |
| 66 | + and can be put into the root directory of your repo. |
| 67 | + The tests are written using @sect.ref{scalafix-testkit}. |
| 68 | + |
| 69 | + @sect{Example rewrites} |
| 70 | + The Scalafix repository contains several example rewrites and tests, |
| 71 | + see @lnk("here", "https://github.com/scalacenter/scalafix/tree/master/scalafix-core/src/main/scala/scalafix/rewrite"). |
| 72 | + These examples may serve as inspiration for your rewrite. |
| 73 | + |
| 74 | + @sect{Vocabulary} |
| 75 | + The following sections explain useful vocabulary when working with Scalafix. |
| 76 | + |
| 77 | + @sect{Rewrite} |
| 78 | + A rewrite is a small program/function that can produce diffs. |
| 79 | + To implement a rewrite, you extend the |
| 80 | + @lnk("Rewrite", "https://github.com/scalacenter/scalafix/blob/master/scalafix-core/src/main/scala/scalafix/rewrite/Rewrite.scala") |
| 81 | + class. |
| 82 | + To run a rewrite, users execute @code{scalafix --rewrites MyRewrite}. |
| 83 | + Multiple rewrites can be composed into a single rewrite. |
| 84 | + For example, the migration for Dotty may involve @sect.ref{ProcedureSyntax}, |
| 85 | + @sect.ref{ExplicitUnit}, @sect.ref{DottyVarArgPattern}, @sect.ref{ExplicitReturnTypes} |
| 86 | + and a few other rewrites. It is possible to combine all of those rewrites |
| 87 | + into a single @code{Dotty} rewrite so users can run |
| 88 | + @code{scalafix --rewrites Dotty}. |
| 89 | + |
| 90 | + @sect{RewriteCtx} |
| 91 | + A rewrite context contains data structures and utilities to rewrite a single |
| 92 | + source file. For example, the rewrite context contains the parsed @sect.ref{Tree}, |
| 93 | + @sect.ref{Tokens}, lookup tables for matching parentheses and more. |
| 94 | + |
| 95 | + @sect{Patch} |
| 96 | + A "Patch" is a data structure that describes how to produce a diff. |
| 97 | + Two patches can combined into a single patch with the @code{+} operator. |
| 98 | + A patch can also be empty. Patches can either be low-level "token patches", |
| 99 | + that operate on the token level or high-level "tree patches" that operate |
| 100 | + on parsed abstract syntax tree nodes. The public API for patch |
| 101 | + operations is available in PatchOps.scala |
| 102 | + |
| 103 | + @hl.ref(wd/"scalafix-core"/"src"/"main"/"scala"/"scalafix"/"patch"/"PatchOps.scala", start = "class SyntacticPatch") |
| 104 | + |
| 105 | + Some things are typically easier to do on the token level and other |
| 106 | + things are easier to do on the tree level. |
| 107 | + The Patch API is constantly evolving and we regularly add more |
| 108 | + utility methods to accomplish common tasks. |
| 109 | + If you experience that it's difficult to implement something that |
| 110 | + seems simple then don't hesitate to ask on @gitter. |
| 111 | + |
| 112 | + @sect{Scalameta} |
| 113 | + Scalafix uses @lnk("Scalameta", "http://scalameta.org/") to implement |
| 114 | + rewrites. |
| 115 | + Scalameta is a clean-room implementation of a metaprogramming toolkit for Scala. |
| 116 | + This means it's not necessary to have experience with Scala compiler internals |
| 117 | + to implement Scalafix rewrites. |
| 118 | + In fact, Scalafix doesn't even depend on the Scala compiler. |
| 119 | + Since Scalafix is not tied so a single compiler, this means that Scalafix |
| 120 | + rewrites in theory can work with any Scala compiler, including @dotty and |
| 121 | + IntelliJ Scala Plugin. |
| 122 | + |
| 123 | + @sect{Scalahost} |
| 124 | + Scalahost is a compiler plugin for Scala 2.x in the @sect.ref{Scalameta} project |
| 125 | + that collects information to build a @sect.ref{Mirror}. |
| 126 | + For more information about Scalahost, see |
| 127 | + the @lnk("Scalameta documentation", "http://scalameta.org/tutorial/#Scalahost"). |
| 128 | + |
| 129 | + @sect{Token} |
| 130 | + A token is for example an identifier @code{println}, a delimiter @code{[} @code{)}, |
| 131 | + or a whitespace character like space or newline. |
| 132 | + In the context of Scalafix, a @code{Token} means the data structure |
| 133 | + @code{scala.meta.Token}. |
| 134 | + See @lnk("Scalameta tutorial", "http://scalameta.org/tutorial/#Tokens") |
| 135 | + for more details. |
| 136 | + See @lnk("Wikipedia", "https://en.wikipedia.org/wiki/Lexical_analysis#Token") |
| 137 | + for a more general definition. |
| 138 | + |
| 139 | + @sect{Tokens} |
| 140 | + @code{Tokens} is a list of @sect.ref{Token}. |
| 141 | + See @lnk("Scalameta tutorial", "http://scalameta.org/tutorial/#Tokens") |
| 142 | + |
| 143 | + @sect{Tree} |
| 144 | + A @code{Tree} is a parsed abstract syntax tree. |
| 145 | + In the context of Scalafix, a @code{Tree} means the data structure |
| 146 | + @code{scala.meta.Tree}. |
| 147 | + See @lnk("Scalameta tutorial", "http://scalameta.org/tutorial/#Trees") |
| 148 | + for more details. |
| 149 | + See @lnk("Wikipedia", "https://en.wikipedia.org/wiki/Abstract_syntax_tree") |
| 150 | + for a more general definition. |
| 151 | + |
| 152 | + @sect{Syntactic} |
| 153 | + A @sect.ref{Rewrite} is "syntactic" when it does not require information |
| 154 | + from type-checking such as resolved names (@code{println} => @code{scala.Predef.println}), |
| 155 | + types or terms, or inferred implicit arguments. |
| 156 | + A syntactic rewrite can use @sect.ref{Tokens} and @sect.ref{Tree}, but |
| 157 | + not @sect.ref{Mirror}. |
| 158 | + |
| 159 | + @sect{Semantic} |
| 160 | + A @sect.ref{Rewrite} is "semantic" if it requires information from the compiler |
| 161 | + such as types, symbols and reported compiler messages. |
| 162 | + A semantic rewrite can use a @sect.ref{Mirror}. |
| 163 | + |
| 164 | + @sect{Mirror} |
| 165 | + A mirror is a Scalameta concept that encapsulates a compilation context, providing |
| 166 | + capabilities to perform semantic operations for @sect.ref{Semantic} rewrites. |
| 167 | + To learn more about mirrors and its associated concepts (Symbol, |
| 168 | + Denotation, ...), see the |
| 169 | + @lnk("Scalameta tutorial", "http://scalameta.org/tutorial/#Mirror"). |
| 170 | + |
| 171 | + @scalatex.Testkit() |
| 172 | + |
| 173 | + @sect{Sharing your rewrite} |
| 174 | + @p |
| 175 | + You have implemented a rewrite, you have tests, it works, |
| 176 | + and now you want to share it with the world. Congrats! |
| 177 | + There are several ways to share a rewrite if the rewrite is contained in |
| 178 | + a single file and uses no external dependencies, |
| 179 | + @ul |
| 180 | + @li |
| 181 | + If you used @sect.ref{scalacenter/scalafix.g8} to build your project, |
| 182 | + push your rewrite to github and tell users to run |
| 183 | + @code{scalafix github:org/$reponame/$version}. |
| 184 | + @li |
| 185 | + otherwise, tell users to use the @sect.ref{http:} protocol, |
| 186 | + @code{scalafix --rewrites https://gist....} where the url |
| 187 | + points to the plaintext contents of your rewrite. |
| 188 | + |
| 189 | + If your rewrite uses a custom library, then it's a bit tricky |
| 190 | + to share it. See @issue(201) for more updates. |
0 commit comments