Skip to content
This repository has been archived by the owner on Dec 22, 2021. It is now read-only.

Commit

Permalink
Merge pull request #352 from julienrf/new-rewrite-rules
Browse files Browse the repository at this point in the history
Create two distinct scalafix rules targeting 2.12 and 2.13
  • Loading branch information
julienrf authored Jan 22, 2018
2 parents 883115e + 7c4580b commit 73edfbb
Show file tree
Hide file tree
Showing 26 changed files with 331 additions and 15 deletions.
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,34 @@ To use it, add the [scalafix](https://scalacenter.github.io/scalafix/) sbt plugi
to your build, as explained in
[its documentation](https://scalacenter.github.io/scalafix/#Installation).

Then run the following sbt task on your project:
Two situations are supported: (1) migrating a 2.12 code base to a 2.12 code base that
uses the collection strawman as a library (instead of the standard collections), and
(2) migrating a 2.12 code base to 2.13 code base.

The migration tool is not exhaustive and we will continue to improve
it over time. If you encounter a use case that’s not supported, please
report it as described in the
[contributing documentation](CONTRIBUTING.md#migration-tool).

#### Migrating a 2.12 code base to a 2.12 code base that uses the collection strawman as a library

Run the following sbt task on your project:

~~~
> scalafix github:scala/collection-strawman/v0
> scalafix https://github.com/scala/collection-strawman/raw/master/scalafix/2.12/rules/src/main/scala/fix/Collectionstrawman_v0.scala
~~~

In essence, the migration tool changes the imports in your source code
so that the strawman definitions are imported. It also rewrites
expressions that use an API that is different in the strawman.

The migration tool is not exhaustive and we will continue to improve
it over time. If you encounter a use case that’s not supported, please
report it as described in the
[contributing documentation](CONTRIBUTING.md#migration-tool).
#### Migrating a 2.12 code base to 2.13 code base

Run the following sbt task on your project:

~~~
> scalafix https://github.com/scala/collection-strawman/raw/master/scalafix/2.13/rules/src/main/scala/fix/Collectionstrawman_v0.scala
~~~

### Additional Operations

Expand Down
12 changes: 10 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import org.scalajs.sbtplugin.cross.CrossProject
val dotty = settingKey[String]("dotty version")
dotty in ThisBuild := "0.6.0-RC1"

val collectionsScalaVersionSettings = Seq(
scalaVersion := "2.13.0-M2",
crossScalaVersions := scalaVersion.value :: "2.12.4" :: dotty.value :: Nil
)

val commonSettings = Seq(
organization := "ch.epfl.scala",
version := "0.9.0-SNAPSHOT",
scalaVersion := "2.13.0-M2",
crossScalaVersions := scalaVersion.value :: "2.12.4" :: dotty.value :: Nil,
scalaVersion := "2.12.4",
scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-language:higherKinds"/*, "-opt:l:classpath"*/),
scalacOptions ++= {
if (!isDotty.value)
Expand Down Expand Up @@ -74,6 +78,7 @@ def crossProj(id: String, base: File) =
val collections =
crossProj("collections", file("collections"))
.settings(
collectionsScalaVersionSettings,
name := "collection-strawman",
scalacOptions += "-Yno-imports"
)
Expand All @@ -85,6 +90,7 @@ val `collections-contrib` =
crossProj("collections-contrib", file("collections-contrib"))
.dependsOn(collections)
.settings(
collectionsScalaVersionSettings,
name := "collections-contrib"
)

Expand Down Expand Up @@ -112,6 +118,7 @@ val junit = project.in(file("test") / "junit")
.dependsOn(collectionsJVM)
.settings(commonSettings ++ disablePublishing)
.settings(
collectionsScalaVersionSettings,
fork in Test := true,
javaOptions in Test += "-Xss1M",
libraryDependencies ++= Seq(
Expand All @@ -129,6 +136,7 @@ val scalacheck = project.in(file("test") / "scalacheck")
// Dotty 0.3.0-RC1 crashes when trying to compile this project
.settings(disableDotty)
.settings(
collectionsScalaVersionSettings,
fork in Test := true,
javaOptions in Test += "-Xss1M",
libraryDependencies ++= Seq(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
rule = "scala:fix.Collectionstrawman_v0"
*/
package fix

object Collectionstrawman_v0_Stream {
Stream(1, 2, 3)
1 #:: 2 #:: 3 #:: Stream.Empty
val isEmpty: Stream[_] => Boolean = {
case Stream.Empty => true
case x #:: xs => false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
rule = "scala:fix.Collectionstrawman_v0"
*/
package fix

object Collectionstrawman_v0_Traversable {
def foo(xs: Traversable[(Int, String)], ys: List[Int]): Unit = {
xs.to[List]
xs.to[Set]
xs.toIterator
ys.iterator
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
rule = "scala:fix.Collectionstrawman_v0"
*/
package fix

import scala.language.postfixOps
object Collectionstrawman_v0_Tuple2Zipped {
def zipped(xs: List[Int], ys: List[Int]): Unit = {
(xs, ys).zipped
(xs,ys).zipped
((xs, ys) zipped)
(((xs) , (ys)).zipped)
(xs, // foo
ys).zipped
/* a */(/* b */ xs /* c */, /* d */ ys /* e */)/* f */./* g */zipped/* h */
(coll(1), coll(2)).zipped
(List(1, 2, 3), Stream.from(1)).zipped
}
def coll(x: Int): List[Int] = ???
}

object Collectionstrawman_v0_Tuple3Zipped {
def zipped(xs: List[Int], ys: List[Int], zs: List[Int]): Unit = {
(xs, ys, zs).zipped
(xs,ys,zs).zipped
((xs, ys, zs) zipped)
(((xs) , (ys) , (zs)).zipped)
(xs, // foo
ys, // bar
zs).zipped
/* a */(/* b */ xs /* c */, /* d */ ys /* e */, /* f */ zs /* g */)/* h */./* i */zipped/* j */
(coll(1), coll(2), coll(3)).zipped
(List(1, 2, 3), Set(1, 2, 3), Stream.from(1)).zipped
}
def coll(x: Int): List[Int] = ???
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
rule = "scala:fix.Collectionstrawman_v0"
*/
package fix

import scala.collection.mutable

class Collectionstrawman_v0_copyToBuffer(xs: List[Int], b: mutable.Buffer[Int]) {

xs.copyToBuffer(b)
(xs ++ xs).copyToBuffer(b)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package fix

object Collectionstrawman_v0_Stream {
LazyList(1, 2, 3)
1 #:: 2 #:: 3 #:: LazyList.Empty
val isEmpty: LazyList[_] => Boolean = {
case LazyList.Empty => true
case x #:: xs => false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package fix

object Collectionstrawman_v0_Traversable {
def foo(xs: Iterable[(Int, String)], ys: List[Int]): Unit = {
xs.to(List)
xs.to(Set)
xs.iterator()
ys.iterator()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package fix

import scala.language.postfixOps
object Collectionstrawman_v0_Tuple2Zipped {
def zipped(xs: List[Int], ys: List[Int]): Unit = {
xs.lazyZip(ys)
xs.lazyZip(ys)
(xs.lazyZip(ys) )
((xs).lazyZip((ys)))
xs.lazyZip(// foo
ys)
/* a *//* b */ xs /* c */.lazyZip(/* d */ ys /* e */)/* f *//* g *//* h */
coll(1).lazyZip(coll(2))
List(1, 2, 3).lazyZip(LazyList.from(1))
}
def coll(x: Int): List[Int] = ???
}

object Collectionstrawman_v0_Tuple3Zipped {
def zipped(xs: List[Int], ys: List[Int], zs: List[Int]): Unit = {
xs.lazyZip(ys).lazyZip(zs)
xs.lazyZip(ys).lazyZip(zs)
(xs.lazyZip(ys).lazyZip(zs) )
((xs).lazyZip((ys)).lazyZip((zs)))
xs.lazyZip(// foo
ys).lazyZip(// bar
zs)
/* a *//* b */ xs /* c */.lazyZip(/* d */ ys /* e */).lazyZip(/* f */ zs /* g */)/* h *//* i *//* j */
coll(1).lazyZip(coll(2)).lazyZip(coll(3))
List(1, 2, 3).lazyZip(Set(1, 2, 3)).lazyZip(LazyList.from(1))
}
def coll(x: Int): List[Int] = ???
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package fix

import scala.collection.mutable

class Collectionstrawman_v0_copyToBuffer(xs: List[Int], b: mutable.Buffer[Int]) {

b ++= xs
b ++= xs ++ xs

}
105 changes: 105 additions & 0 deletions scalafix/2.13/rules/src/main/scala/fix/Collectionstrawman_v0.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package fix

import scalafix._
import scalafix.syntax._
import scalafix.util._
import scala.meta._

case class Collectionstrawman_v0(index: SemanticdbIndex)
extends SemanticRule(index, "Collectionstrawman_v0") {

def replaceSymbols(ctx: RuleCtx): Patch = {
ctx.replaceSymbols(
"scala.Stream" -> "scala.LazyList",
"scala.collection.immutable.Stream" -> "scala.collection.immutable.LazyList",
"scala.Traversable" -> "scala.Iterable",
"scala.collection.Traversable" -> "scala.collection.Iterable",
"scala.TraversableOnce" -> "scala.IterableOnce",
"scala.collection.TraversableOnce" -> "scala.collection.IterableOnce"
)
}

val toTpe = SymbolMatcher.normalized(
Symbol("_root_.scala.collection.TraversableLike.to.")
)
val iterator = SymbolMatcher.normalized(
Symbol("_root_.scala.collection.LinearSeqLike.iterator."),
Symbol("_root_.scala.collection.TraversableLike.toIterator.")
)
val tupleZipped = SymbolMatcher.normalized(
Symbol("_root_.scala.runtime.Tuple2Zipped.Ops.zipped."),
Symbol("_root_.scala.runtime.Tuple3Zipped.Ops.zipped.")
)

def replaceToList(ctx: RuleCtx) =
ctx.tree.collect {
case iterator(t: Name) =>
ctx.replaceTree(t, "iterator()")
case toTpe(n: Name) =>
(for {
name <- n.tokens.lastOption
open <- ctx.tokenList.find(name)(t => t.is[Token.LeftBracket])
close <- ctx.matchingParens.close(open.asInstanceOf[Token.LeftBracket])
} yield
ctx.replaceToken(open, "(") +
ctx.replaceToken(close, ")")
).asPatch
}.asPatch

def replaceTupleZipped(ctx: RuleCtx) =
ctx.tree.collect {
case tupleZipped(Term.Select(Term.Tuple(args), name)) =>
val removeTokensPatch =
(for {
zipped <- name.tokens.headOption
closeTuple <- ctx.tokenList.leading(zipped).find(_.is[Token.RightParen])
openTuple <- ctx.matchingParens.open(closeTuple.asInstanceOf[Token.RightParen])
maybeDot = ctx.tokenList.slice(closeTuple, zipped).find(_.is[Token.Dot])
} yield {
ctx.removeToken(openTuple) +
maybeDot.map(ctx.removeToken).asPatch +
ctx.removeToken(zipped)
}).asPatch

def removeSurroundingWhiteSpaces(tk: Token) =
(ctx.tokenList.trailing(tk).takeWhile(_.is[Token.Space]).map(ctx.removeToken) ++
ctx.tokenList.leading(tk).takeWhile(_.is[Token.Space]).map(ctx.removeToken)).asPatch

val commas =
for {
(prev, next) <- args.zip(args.tail)
tokensBetweenArgs = ctx.tokenList.slice(prev.tokens.last, next.tokens.head)
comma <- tokensBetweenArgs.find(_.is[Token.Comma])
} yield comma

val replaceCommasPatch = commas match {
case head :: tail =>
ctx.replaceToken(head, ".lazyZip(") +
removeSurroundingWhiteSpaces(head) ++
tail.map { comma =>
ctx.replaceToken(comma, ").lazyZip(") +
removeSurroundingWhiteSpaces(comma)
}
case _ => Patch.empty
}

removeTokensPatch + replaceCommasPatch
}.asPatch

val copyToBuffer = SymbolMatcher.normalized(
Symbol("_root_.scala.collection.TraversableOnce.copyToBuffer.")
)

def replaceCopyToBuffer(ctx: RuleCtx): Patch =
ctx.tree.collect {
case t @ q"${copyToBuffer(Term.Select(collection, _))}($buffer)" =>
ctx.replaceTree(t, q"$buffer ++= $collection".syntax)
}.asPatch

override def fix(ctx: RuleCtx): Patch = {
replaceToList(ctx) +
replaceSymbols(ctx) +
replaceTupleZipped(ctx) +
replaceCopyToBuffer(ctx)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package fix

import scala.meta._
import scalafix.testkit._
import scalafix._

class Collectionstrawman_Tests
extends SemanticRuleSuite(
SemanticdbIndex.load(Classpath(AbsolutePath(BuildInfo.inputClassdirectory))),
AbsolutePath(BuildInfo.inputSourceroot),
Seq(AbsolutePath(BuildInfo.outputSourceroot))
) {
override def assertNoDiff(a: String, b: String, c: String) = {
super.assertNoDiff(a, b, c)
}
runAllTests()
}
Loading

0 comments on commit 73edfbb

Please sign in to comment.