diff --git a/.travis.yml b/.travis.yml index 03d765ee..80eaa60a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,17 +54,24 @@ matrix: # | oraclejdk8 | 2.13.0-M4 | jvm | | | # | oraclejdk8 | 2.13.0-M4 | js | 0.6.23 | | # | oraclejdk8 | 2.12.6 | jvm | | true | - + before_script: ./checkCLA.sh script: - java -version - admin/build.sh -before_cache: - - find $HOME/.sbt -name "*.lock" | xargs rm - - find $HOME/.ivy2/cache -name "ivydata-*.properties" | xargs rm cache: directories: - - $HOME/.ivy2/cache - - $HOME/.sbt/boot - - $HOME/.sbt/launchers + - "$HOME/.sbt/0.13/dependency" + - "$HOME/.sbt/boot/scala*" + - "$HOME/.sbt/launchers" + - "$HOME/.ivy2/cache" + - "$HOME/.coursier" + +before_cache: +- du -h -d 1 $HOME/.ivy2/cache +- du -h -d 2 $HOME/.sbt/ +- find $HOME/.sbt -name "*.lock" -type f -delete +- find $HOME/.ivy2/cache -name "ivydata-*.properties" -type f -delete +- find $HOME/.ivy2/cache -name "*scalafix*.xml" -type f -delete +- rm -rf $HOME/.ivy2/local diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e68f962..abb2a72f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,8 +4,8 @@ ### Sbt Projects -- `scala-collection-compat` project (in the root directory): implementation of the compatibility library ; -- In directory `scalafix/` there is an independent build containing the implementation of the migration tool. +- `compat` project: implementation of the compatibility library ; +- `scalafix*`: implementation of the migration tool. ## Migration tool @@ -31,16 +31,16 @@ Even better, instead of providing a diff, you can directly add it as a test case 2. Add a file in the `scalafix/input/src/main/scala/fix/` directory with code that uses the standard collections: - + ~~~ scala class toIteratorVsIterator(xs: Iterable[Int]) { xs.toIterator } ~~~ -3. Add a corresponding file in the `scalafix/output/src/main/scala/fix/` directory +3. Add a corresponding file in the `scalafix/output213/src/main/scala/fix/` directory with the same code but using the strawman: - + ~~~ scala import strawman.collection.Iterable @@ -50,8 +50,8 @@ class toIteratorVsIterator(xs: Iterable[Int]) { ~~~ 4. Check that your code example compiles - - run sbt from the `scalafix/` directory - and then run the following tasks `; input/compile ; output/compile`; + - run sbt + and then run the following task `compile`; 5. Commit your changes, push your branch to your fork and create a pull request. @@ -68,10 +68,10 @@ migration tool on the input files and check whether the result matches the expected output files: ~~~ -> tests/test +> scalafixTests/test ~~~ Fix the implementation of the rule (in the -`rules/src/main/scala/fix/Scalacollectioncompat_v0.scala` file) until the +`rules/src/main/scala/fix/NewCollections.scala` file) until the tests are green. You can find more help about the scalafix API in its [documentation](https://scalacenter.github.io/scalafix/docs/rule-authors/setup). diff --git a/README.md b/README.md index 750a6690..74d90f4e 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,3 @@ 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 - -Run the following sbt task on your project: - -~~~ -> scalafix github:scala/scala-collection-compat/NewCollections -~~~ diff --git a/admin/build.sh b/admin/build.sh index bffec523..52a555dd 100755 --- a/admin/build.sh +++ b/admin/build.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e @@ -18,20 +18,30 @@ set -e RELEASE_COMBO=true -if [[ "$TEST_SCALAFIX" == "true" ]]; then - cd scalafix && sbt input/compile output/compile tests/test - exit 0 +if [ "$SCALAJS_VERSION" = "" ]; then + if [[ "$TEST_SCALAFIX" == "true" ]]; then + projectPrefix="scalafixRules" + else + projectPrefix="compat" + fi +else + projectPrefix="compatJS" fi -if [ "$SCALAJS_VERSION" = "" ]; then - projectPrefix="scala-collection-compat" +if [[ "$TEST_SCALAFIX" == "true" ]]; then + crossScalaVersion="noop" + testProjectPrefix="scalafixTests" else - projectPrefix="scala-collection-compatJS" + crossScalaVersion="++$TRAVIS_SCALA_VERSION" + testProjectPrefix="$projectPrefix" fi verPat="[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9-]+)?" tagPat="^v$verPat(#.*)?$" +publishVersion="noop" +publishTask="noop" + if [[ "$TRAVIS_TAG" =~ $tagPat ]]; then tagVer=$(echo $TRAVIS_TAG | sed s/#.*// | sed s/^v//) publishVersion='set every version := "'$tagVer'"' @@ -52,4 +62,4 @@ if [[ "$TRAVIS_TAG" =~ $tagPat ]]; then fi fi -sbt -Dhttps.protocols=TLSv1.2 "++$TRAVIS_SCALA_VERSION" "$publishVersion" "$projectPrefix/clean" "$projectPrefix/test" "$projectPrefix/publishLocal" "$publishTask" +sbt -Dhttps.protocols=TLSv1.2 -sbt-dir=/home/travis/.sbt ";$crossScalaVersion ;$publishVersion ;$projectPrefix/clean ;$testProjectPrefix/test ;$projectPrefix/publishLocal ;$publishTask" diff --git a/admin/encryptEnvVars.sh b/admin/encryptEnvVars.sh index b6256679..1dac942c 100755 --- a/admin/encryptEnvVars.sh +++ b/admin/encryptEnvVars.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash + # # Encrypt sonatype credentials so that they can be # decrypted in trusted builds on Travis CI. diff --git a/admin/genKeyPair.sh b/admin/genKeyPair.sh index 17db3f39..2b731a33 100755 --- a/admin/genKeyPair.sh +++ b/admin/genKeyPair.sh @@ -1,4 +1,5 @@ -#!/bin/bash +#!/usr/bin/env bash + # # Generates a key pair for this repository to sign artifacts. # Encrypt the private key and its passphrase in trusted builds diff --git a/build.sbt b/build.sbt index bf43cc3d..cc7362c6 100644 --- a/build.sbt +++ b/build.sbt @@ -1,16 +1,23 @@ -import sbtcrossproject.{crossProject, CrossType} import ScalaModulePlugin._ +import sbtcrossproject.{crossProject, CrossType} +import _root_.scalafix.Versions.{version => scalafixVersion, scala212 => scalafixScala212} -inThisBuild(Seq( - crossScalaVersions := Seq("2.12.6", "2.13.0-M4", "2.11.12") -)) +lazy val root = project + .in(file(".")) + .settings(dontPublish) + .aggregate( + compatJVM, compatJS, + scalafixRules, scalafixInput, scalafixTests, + scalafixOutput212, scalafixOutput213 + ) + .disablePlugins(ScalafixPlugin) -disablePlugins(JvmPlugin) +// == Core Libraries == -lazy val `scala-collection-compat` = crossProject(JSPlatform, JVMPlatform) +lazy val compat = crossProject(JSPlatform, JVMPlatform) .withoutSuffixFor(JVMPlatform) .crossType(CrossType.Pure) - .in(file(".")) + .in(file("compat")) .settings(scalaModuleSettings) .jvmSettings(scalaModuleSettingsJVM) .settings( @@ -36,6 +43,94 @@ lazy val `scala-collection-compat` = crossProject(JSPlatform, JVMPlatform) fork in Test := false // Scala.js cannot run forked tests ) .jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin)) + .disablePlugins(ScalafixPlugin) -lazy val `scala-collection-compatJVM` = `scala-collection-compat`.jvm -lazy val `scala-collection-compatJS` = `scala-collection-compat`.js +lazy val compatJVM = compat.jvm +lazy val compatJS = compat.js + +lazy val scalafixRules = project + .in(file("scalafix/rules")) + .settings(scalaModuleSettings) + .settings(scalaModuleSettingsJVM) + .settings( + name := "scala-collection-migrations", + scalaVersion := scalafixScala212, + libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % scalafixVersion + ) + +// == Scalafix Test Setup == + +lazy val scalafixInput = project + .in(file("scalafix/input")) + .settings(dontPublish) + .settings( + scalaVersion := scalafixScala212, + scalafixSourceroot := sourceDirectory.in(Compile).value + ) + +lazy val scalafixOutput212 = project + .in(file("scalafix/output212")) + .settings(scalaVersion := scalafixScala212) + .settings(dontPublish) + .dependsOn(compatJVM) + +lazy val scalafixOutput213 = project + .in(file("scalafix/output213")) + .settings(scala213Settings) + .settings(dontPublish) + +lazy val scalafixOutput213Failure = project + .in(file("scalafix/output213-failure")) + .settings(scala213Settings) + .settings(dontPublish) + +lazy val scalafixTests = project + .in(file("scalafix/tests")) + .settings(dontPublish) + .settings( + scalaVersion := scalafixScala212, + libraryDependencies += "ch.epfl.scala" % "scalafix-testkit" % scalafixVersion % Test cross CrossVersion.full, + buildInfoPackage := "fix", + buildInfoKeys := Seq[BuildInfoKey]( + "inputSourceroot" -> + sourceDirectory.in(scalafixInput, Compile).value, + "output212Sourceroot" -> + sourceDirectory.in(scalafixOutput212, Compile).value, + "output213Sourceroot" -> + sourceDirectory.in(scalafixOutput213, Compile).value, + "output213FailureSourceroot" -> + sourceDirectory.in(scalafixOutput213Failure, Compile).value, + "inputClassdirectory" -> + classDirectory.in(scalafixInput, Compile).value + ), + test in Test := (test in Test).dependsOn( + compile in (scalafixOutput212, Compile), + compile in (scalafixOutput213, Compile) + ).value + ) + .dependsOn(scalafixInput, scalafixRules) + .enablePlugins(BuildInfoPlugin) + +lazy val dontPublish = Seq( + publishArtifact := false, + packagedArtifacts := Map.empty, + publish := {}, + publishLocal := {} +) + +lazy val scala212 = "2.12.6" +lazy val scala213 = "2.13.0-M4" + +lazy val scala213Settings = Seq( + resolvers += "scala-pr" at "https://scala-ci.typesafe.com/artifactory/scala-integration/", + scalaVersion := scala213 +) + +// required by sbt-scala-module +inThisBuild(Seq( + crossScalaVersions := Seq(scala212, scala213, "2.11.12"), + commands += Command.command("noop") { state => + println("noop") + state + } +)) diff --git a/src/main/scala-2.11_2.12/scala/collection/compat/BuildFrom.scala b/compat/src/main/scala-2.11_2.12/scala/collection/compat/BuildFrom.scala similarity index 100% rename from src/main/scala-2.11_2.12/scala/collection/compat/BuildFrom.scala rename to compat/src/main/scala-2.11_2.12/scala/collection/compat/BuildFrom.scala diff --git a/src/main/scala-2.11_2.12/scala/collection/compat/CompatImpl.scala b/compat/src/main/scala-2.11_2.12/scala/collection/compat/CompatImpl.scala similarity index 100% rename from src/main/scala-2.11_2.12/scala/collection/compat/CompatImpl.scala rename to compat/src/main/scala-2.11_2.12/scala/collection/compat/CompatImpl.scala diff --git a/src/main/scala-2.11_2.12/scala/collection/compat/Factory.scala b/compat/src/main/scala-2.11_2.12/scala/collection/compat/Factory.scala similarity index 100% rename from src/main/scala-2.11_2.12/scala/collection/compat/Factory.scala rename to compat/src/main/scala-2.11_2.12/scala/collection/compat/Factory.scala diff --git a/src/main/scala-2.11_2.12/scala/collection/compat/immutable/ArraySeq.scala b/compat/src/main/scala-2.11_2.12/scala/collection/compat/immutable/ArraySeq.scala similarity index 100% rename from src/main/scala-2.11_2.12/scala/collection/compat/immutable/ArraySeq.scala rename to compat/src/main/scala-2.11_2.12/scala/collection/compat/immutable/ArraySeq.scala diff --git a/src/main/scala-2.11_2.12/scala/collection/compat/package.scala b/compat/src/main/scala-2.11_2.12/scala/collection/compat/package.scala similarity index 100% rename from src/main/scala-2.11_2.12/scala/collection/compat/package.scala rename to compat/src/main/scala-2.11_2.12/scala/collection/compat/package.scala diff --git a/src/main/scala-2.13/scala/collection/compat/immutable/package.scala b/compat/src/main/scala-2.13/scala/collection/compat/immutable/package.scala similarity index 100% rename from src/main/scala-2.13/scala/collection/compat/immutable/package.scala rename to compat/src/main/scala-2.13/scala/collection/compat/immutable/package.scala diff --git a/src/main/scala-2.13/scala/collection/compat/package.scala b/compat/src/main/scala-2.13/scala/collection/compat/package.scala similarity index 100% rename from src/main/scala-2.13/scala/collection/compat/package.scala rename to compat/src/main/scala-2.13/scala/collection/compat/package.scala diff --git a/src/test/scala/test/scala/collection/ArraySeqTest.scala b/compat/src/test/scala/test/scala/collection/ArraySeqTest.scala similarity index 100% rename from src/test/scala/test/scala/collection/ArraySeqTest.scala rename to compat/src/test/scala/test/scala/collection/ArraySeqTest.scala diff --git a/src/test/scala/test/scala/collection/BuildFromTest.scala b/compat/src/test/scala/test/scala/collection/BuildFromTest.scala similarity index 100% rename from src/test/scala/test/scala/collection/BuildFromTest.scala rename to compat/src/test/scala/test/scala/collection/BuildFromTest.scala diff --git a/src/test/scala/test/scala/collection/CollectionTest.scala b/compat/src/test/scala/test/scala/collection/CollectionTest.scala similarity index 100% rename from src/test/scala/test/scala/collection/CollectionTest.scala rename to compat/src/test/scala/test/scala/collection/CollectionTest.scala diff --git a/src/test/scala/test/scala/collection/FactoryTest.scala b/compat/src/test/scala/test/scala/collection/FactoryTest.scala similarity index 100% rename from src/test/scala/test/scala/collection/FactoryTest.scala rename to compat/src/test/scala/test/scala/collection/FactoryTest.scala diff --git a/src/test/scala/test/scala/collection/StreamTest.scala b/compat/src/test/scala/test/scala/collection/StreamTest.scala similarity index 100% rename from src/test/scala/test/scala/collection/StreamTest.scala rename to compat/src/test/scala/test/scala/collection/StreamTest.scala diff --git a/src/test/scala/test/scala/collection/generic/SortedTest.scala b/compat/src/test/scala/test/scala/collection/generic/SortedTest.scala similarity index 100% rename from src/test/scala/test/scala/collection/generic/SortedTest.scala rename to compat/src/test/scala/test/scala/collection/generic/SortedTest.scala diff --git a/project/plugins.sbt b/project/plugins.sbt index 0670f0ee..dc26cc39 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -5,9 +5,10 @@ else // see https://github.com/scala/sbt-scala-module/issues/35 Seq(addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.9.3")) -val scalaJSVersion = - Option(System.getenv("SCALAJS_VERSION")).filter(_.nonEmpty).getOrElse("0.6.23") +val scalaJSVersion = Option(System.getenv("SCALAJS_VERSION")).filter(_.nonEmpty).getOrElse("0.6.23") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion) -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.4.0") -addSbtPlugin("org.scala-lang.modules" % "sbt-scala-module" % "1.0.14") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion) +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.5.0") +addSbtPlugin("org.scala-lang.modules" % "sbt-scala-module" % "1.0.14") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.5.10") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") diff --git a/scalafix/build.sbt b/scalafix/build.sbt deleted file mode 100644 index 9406fe10..00000000 --- a/scalafix/build.sbt +++ /dev/null @@ -1,52 +0,0 @@ -def scalafixVersion = _root_.scalafix.Versions.version -inScope(Global)( - List( - scalaVersion := _root_.scalafix.Versions.scala212 - ) -) - -lazy val root = project - .in(file(".")) - .aggregate( - rules, input, output, tests - ) - -lazy val rules = project.settings( - libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % scalafixVersion -) - -lazy val input = project - .settings( - scalafixSourceroot := sourceDirectory.in(Compile).value - ) - -lazy val output = project - .settings( - resolvers += "scala-pr" at "https://scala-ci.typesafe.com/artifactory/scala-integration/", - scalaVersion := "2.13.0-M4" - ) - -lazy val outputFailure = project.in(file("output-failure")) - .settings( - resolvers += "scala-pr" at "https://scala-ci.typesafe.com/artifactory/scala-integration/", - scalaVersion := "2.13.0-M4" - ) - -lazy val tests = project - .settings( - libraryDependencies += "ch.epfl.scala" % "scalafix-testkit" % scalafixVersion % Test cross CrossVersion.full, - buildInfoPackage := "fix", - buildInfoKeys := Seq[BuildInfoKey]( - "inputSourceroot" -> - sourceDirectory.in(input, Compile).value, - "outputSourceroot" -> - sourceDirectory.in(output, Compile).value, - "outputFailureSourceroot" -> - sourceDirectory.in(outputFailure, Compile).value, - "inputClassdirectory" -> - classDirectory.in(input, Compile).value - ), - test in Test := (test in Test).dependsOn(compile in (output, Compile)).value - ) - .dependsOn(input, rules) - .enablePlugins(BuildInfoPlugin) diff --git a/scalafix/input/src/main/scala/fix/BreakoutSrc.scala b/scalafix/input/src/main/scala/fix/BreakoutSrc.scala deleted file mode 100644 index 09b7e16a..00000000 --- a/scalafix/input/src/main/scala/fix/BreakoutSrc.scala +++ /dev/null @@ -1,25 +0,0 @@ -/* -rule = "scala:fix.Scalacollectioncompat_newcollections" - */ -package fix - -import scala.collection.breakOut - -object BreakoutSrc { - val xs = List(1, 2, 3) - - xs.collect{ case x => x }(breakOut): Set[Int] - xs.flatMap(x => List(x))(breakOut): collection.SortedSet[Int] - xs.map(_ + 1)(breakOut): Set[Int] - xs.reverseMap(_ + 1)(breakOut): Set[Int] - xs.scanLeft(0)((a, b) => a + b)(breakOut): Set[Int] - xs.union(xs)(breakOut): Set[Int] - xs.updated(0, 1)(breakOut): Set[Int] - xs.zip(xs)(breakOut): Map[Int, Int] - xs.zipAll(xs, 0, 0)(breakOut): Array[(Int, Int)] - - (xs ++ xs)(breakOut): Set[Int] - (1 +: xs)(breakOut): Set[Int] - (xs :+ 1)(breakOut): Set[Int] - (xs ++: xs)(breakOut): Set[Int] -} \ No newline at end of file diff --git a/scalafix/input/src/main/scala/fix/CanBuildFromNegSrc.scala b/scalafix/input/src/main/scala/fix/CanBuildFromNegSrc.scala index f09a0604..8e0109ed 100644 --- a/scalafix/input/src/main/scala/fix/CanBuildFromNegSrc.scala +++ b/scalafix/input/src/main/scala/fix/CanBuildFromNegSrc.scala @@ -1,5 +1,5 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.CrossCompat" */ package fix diff --git a/scalafix/input/src/main/scala/fix/CanBuildFromSrc.scala b/scalafix/input/src/main/scala/fix/CanBuildFromSrc.scala index e3cf22d2..43311424 100644 --- a/scalafix/input/src/main/scala/fix/CanBuildFromSrc.scala +++ b/scalafix/input/src/main/scala/fix/CanBuildFromSrc.scala @@ -1,5 +1,5 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.CrossCompat" */ package fix diff --git a/scalafix/input/src/main/scala/fix/CopyToBufferSrc.scala b/scalafix/input/src/main/scala/fix/CopyToBufferSrc.scala index 1650bc2e..071e0385 100644 --- a/scalafix/input/src/main/scala/fix/CopyToBufferSrc.scala +++ b/scalafix/input/src/main/scala/fix/CopyToBufferSrc.scala @@ -1,13 +1,11 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.CrossCompat" */ package fix import scala.collection.mutable class CopyToBufferSrc(xs: List[Int], b: mutable.Buffer[Int]) { - xs.copyToBuffer(b) (xs ++ xs).copyToBuffer(b) - } diff --git a/scalafix/input/src/main/scala/fix/FoldSrc.scala b/scalafix/input/src/main/scala/fix/FoldSrc.scala index 2d259110..2e5970ac 100644 --- a/scalafix/input/src/main/scala/fix/FoldSrc.scala +++ b/scalafix/input/src/main/scala/fix/FoldSrc.scala @@ -1,5 +1,5 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.CrossCompat" */ package fix diff --git a/scalafix/input/src/main/scala/fix/IterableSrc.scala b/scalafix/input/src/main/scala/fix/IterableSrc.scala index e0e0b200..8735a55b 100644 --- a/scalafix/input/src/main/scala/fix/IterableSrc.scala +++ b/scalafix/input/src/main/scala/fix/IterableSrc.scala @@ -1,8 +1,8 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.NewCollections" */ package fix class IterableSrc(it: Iterable[Int]) { it.sameElements(it) -} \ No newline at end of file +} diff --git a/scalafix/input/src/main/scala/fix/LinearSeqSrc.scala b/scalafix/input/src/main/scala/fix/LinearSeqSrc.scala index cebc82ae..d60f5072 100644 --- a/scalafix/input/src/main/scala/fix/LinearSeqSrc.scala +++ b/scalafix/input/src/main/scala/fix/LinearSeqSrc.scala @@ -1,5 +1,5 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.CrossCompat" */ package fix diff --git a/scalafix/input/src/main/scala/fix/MutSetMapSrc.scala b/scalafix/input/src/main/scala/fix/MutSetMapSrc.scala index e9329ab4..6b111ffb 100644 --- a/scalafix/input/src/main/scala/fix/MutSetMapSrc.scala +++ b/scalafix/input/src/main/scala/fix/MutSetMapSrc.scala @@ -1,5 +1,5 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.CrossCompat" */ package fix @@ -10,4 +10,4 @@ class MutSetMapSrc(map: mutable.Map[Int, Int], set: mutable.Set[Int]) { map + (2 -> 3) (set + 2).size map.updated(1, 3) -} \ No newline at end of file +} diff --git a/scalafix/input/src/main/scala/fix/RetainSrc.scala b/scalafix/input/src/main/scala/fix/RetainSrc.scala index 108e859e..f5bc3040 100644 --- a/scalafix/input/src/main/scala/fix/RetainSrc.scala +++ b/scalafix/input/src/main/scala/fix/RetainSrc.scala @@ -1,5 +1,5 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.NewCollections" */ package fix @@ -9,4 +9,4 @@ class MethodRenames(xs: Map[Int, Int], ys: Set[Int]) { xs.retain((_, _) => true) xs.retain{case (x, y) => true} ys.retain(_ => true) -} \ No newline at end of file +} diff --git a/scalafix/input/src/main/scala/fix/RoughlyMapValuesSrc.scala b/scalafix/input/src/main/scala/fix/RoughlyMapValuesSrc.scala new file mode 100644 index 00000000..5a545ab8 --- /dev/null +++ b/scalafix/input/src/main/scala/fix/RoughlyMapValuesSrc.scala @@ -0,0 +1,8 @@ +/* +rule = "scala:fix.RoughlyMapValues" + */ +package fix + +class RoughlyMapValuesSrc(map: Map[Int, Int]) { + map.mapValues(_ + 1) +} diff --git a/scalafix/input/src/main/scala/fix/StreamSrc.scala b/scalafix/input/src/main/scala/fix/RoughlyStreamToLazyListSrc.scala similarity index 72% rename from scalafix/input/src/main/scala/fix/StreamSrc.scala rename to scalafix/input/src/main/scala/fix/RoughlyStreamToLazyListSrc.scala index 70d2df9e..25e0bbc0 100644 --- a/scalafix/input/src/main/scala/fix/StreamSrc.scala +++ b/scalafix/input/src/main/scala/fix/RoughlyStreamToLazyListSrc.scala @@ -1,9 +1,9 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.RoughlyStreamToLazyList" */ package fix -object StreamSrc { +class RoughlyStreamToLazyListSrc() { val s = Stream(1, 2, 3) s.append(List(4, 5, 6)) 1 #:: 2 #:: 3 #:: Stream.Empty diff --git a/scalafix/input/src/main/scala/fix/SetMapSrc.scala b/scalafix/input/src/main/scala/fix/SetMapSrc.scala index 7f70e202..fd31fa34 100644 --- a/scalafix/input/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/input/src/main/scala/fix/SetMapSrc.scala @@ -1,5 +1,5 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.CrossCompat" */ package fix @@ -8,5 +8,4 @@ class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { map + (2 -> 3, 3 -> 4) (set + (2, 3)).toString set + (2, 3) - 4 - map.mapValues(_ + 1) -} \ No newline at end of file +} diff --git a/scalafix/input/src/main/scala/fix/TestsSrc.scala b/scalafix/input/src/main/scala/fix/TestsSrc.scala index 861757cf..876995b5 100644 --- a/scalafix/input/src/main/scala/fix/TestsSrc.scala +++ b/scalafix/input/src/main/scala/fix/TestsSrc.scala @@ -1,5 +1,5 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.CrossCompat" */ package fix diff --git a/scalafix/input/src/main/scala/fix/TraversableSrc.scala b/scalafix/input/src/main/scala/fix/TraversableSrc.scala index ca2a3c57..8560160e 100644 --- a/scalafix/input/src/main/scala/fix/TraversableSrc.scala +++ b/scalafix/input/src/main/scala/fix/TraversableSrc.scala @@ -1,5 +1,5 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.NewCollections" */ package fix diff --git a/scalafix/input/src/main/scala/fix/TupleNZippedSrc.scala b/scalafix/input/src/main/scala/fix/TupleNZippedSrc.scala index e47ff35e..0390857d 100644 --- a/scalafix/input/src/main/scala/fix/TupleNZippedSrc.scala +++ b/scalafix/input/src/main/scala/fix/TupleNZippedSrc.scala @@ -1,5 +1,5 @@ /* -rule = "scala:fix.Scalacollectioncompat_newcollections" +rule = "scala:fix.NewCollections" */ package fix @@ -14,7 +14,7 @@ object Collectionstrawman_v0_Tuple2Zipped { 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 + (List(1, 2, 3), Array(1)).zipped } def coll(x: Int): List[Int] = ??? } @@ -30,7 +30,7 @@ object Collectionstrawman_v0_Tuple3Zipped { 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 + (List(1, 2, 3), Set(1, 2, 3), Array(1)).zipped } def coll(x: Int): List[Int] = ??? -} \ No newline at end of file +} diff --git a/scalafix/output/src/main/scala/fix/BreakoutSrc.scala b/scalafix/output/src/main/scala/fix/BreakoutSrc.scala deleted file mode 100644 index e9ef5a05..00000000 --- a/scalafix/output/src/main/scala/fix/BreakoutSrc.scala +++ /dev/null @@ -1,24 +0,0 @@ - - - -package fix - - -object BreakoutSrc { - val xs = List(1, 2, 3) - - xs.iterator.collect{ case x => x }.to(implicitly): Set[Int] - xs.iterator.flatMap(x => List(x)).to(implicitly): collection.SortedSet[Int] - xs.iterator.map(_ + 1).to(implicitly): Set[Int] - xs.reverseIterator.map(_ + 1).to(implicitly): Set[Int] - xs.iterator.scanLeft(0)((a, b) => a + b).to(implicitly): Set[Int] - xs.iterator.concat(xs).to(implicitly): Set[Int] - xs.view.updated(0, 1).to(implicitly): Set[Int] - xs.iterator.zip(xs).to(implicitly): Map[Int, Int] - xs.iterator.zipAll(xs, 0, 0).to(implicitly): Array[(Int, Int)] - - (xs.iterator ++ xs).to(implicitly): Set[Int] - (1 +: xs.view).to(implicitly): Set[Int] - (xs.view :+ 1).to(implicitly): Set[Int] - (xs ++: xs.view).to(implicitly): Set[Int] -} \ No newline at end of file diff --git a/scalafix/output/src/main/scala/fix/CanBuildFromSrc.scala b/scalafix/output/src/main/scala/fix/CanBuildFromSrc.scala deleted file mode 100644 index 34021461..00000000 --- a/scalafix/output/src/main/scala/fix/CanBuildFromSrc.scala +++ /dev/null @@ -1,35 +0,0 @@ - - - -package fix - -import scala.language.higherKinds - - -class CanBuildFromSrc() { - - def f[C0, A, C1[_]](c0: C0)(implicit - cbf: collection.Factory[Int, C1[Int]], - cbf2: collection.Factory[String, C1[String]], - cbf3: collection.BuildFrom[C0, A, C1[A]]): C1[Int] = { - - val b = cbf.newBuilder - val b2 = cbf.newBuilder - val b3 = cbf.newBuilder - val b4 = cbf2.newBuilder - val b5 = cbf3.newBuilder(c0) - val b6 = cbf3.newBuilder(c0) - - List.empty[Int].to(cbf) - List.empty[String].to(cbf2) - b.result() - } - - def kind(implicit cbf: collection.BuildFrom[String, Char, String], - cbf2: collection.BuildFrom[String, (Int, Boolean), Map[Int, Boolean]]): Unit = { - - cbf.newBuilder("") - cbf2.newBuilder("") - () - } -} diff --git a/scalafix/output212/src/main/scala/fix/CanBuildFromSrc.scala b/scalafix/output212/src/main/scala/fix/CanBuildFromSrc.scala new file mode 100644 index 00000000..00abbbeb --- /dev/null +++ b/scalafix/output212/src/main/scala/fix/CanBuildFromSrc.scala @@ -0,0 +1,36 @@ + + + +package fix + +import scala.language.higherKinds + +import scala.collection.compat._ + +class CanBuildFromSrc() { + + def f[C0, A, C1[_]](c0: C0)(implicit + cbf: Factory[Int, C1[Int]], + cbf2: Factory[String, C1[String]], + cbf3: BuildFrom[C0, A, C1[A]]): C1[Int] = { + + val b = cbf.newBuilder + val b2 = cbf.newBuilder + val b3 = cbf.newBuilder + val b4 = cbf2.newBuilder + val b5 = cbf3.newBuilder(c0) + val b6 = cbf3.newBuilder(c0) + + cbf.fromSpecific(List.empty[Int]) + cbf2.fromSpecific(List.empty[String]) + b.result() + } + + def kind(implicit cbf: BuildFrom[String, Char, String], + cbf2: BuildFrom[String, (Int, Boolean), Map[Int, Boolean]]): Unit = { + + cbf.newBuilder("") + cbf2.newBuilder("") + () + } +} diff --git a/scalafix/output/src/main/scala/fix/CopyToBufferSrc.scala b/scalafix/output212/src/main/scala/fix/CopyToBufferSrc.scala similarity index 99% rename from scalafix/output/src/main/scala/fix/CopyToBufferSrc.scala rename to scalafix/output212/src/main/scala/fix/CopyToBufferSrc.scala index e254f68c..b3f33c9e 100644 --- a/scalafix/output/src/main/scala/fix/CopyToBufferSrc.scala +++ b/scalafix/output212/src/main/scala/fix/CopyToBufferSrc.scala @@ -1,10 +1,11 @@ + + + package fix import scala.collection.mutable class CopyToBufferSrc(xs: List[Int], b: mutable.Buffer[Int]) { - b ++= xs b ++= xs ++ xs - } diff --git a/scalafix/output/src/main/scala/fix/ExperimentalSrc.scala b/scalafix/output212/src/main/scala/fix/ExperimentalSrc.scala similarity index 100% rename from scalafix/output/src/main/scala/fix/ExperimentalSrc.scala rename to scalafix/output212/src/main/scala/fix/ExperimentalSrc.scala diff --git a/scalafix/output/src/main/scala/fix/FoldSrc.scala b/scalafix/output212/src/main/scala/fix/FoldSrc.scala similarity index 100% rename from scalafix/output/src/main/scala/fix/FoldSrc.scala rename to scalafix/output212/src/main/scala/fix/FoldSrc.scala diff --git a/scalafix/output/src/main/scala/fix/LinearSeqSrc.scala b/scalafix/output212/src/main/scala/fix/LinearSeqSrc.scala similarity index 100% rename from scalafix/output/src/main/scala/fix/LinearSeqSrc.scala rename to scalafix/output212/src/main/scala/fix/LinearSeqSrc.scala diff --git a/scalafix/output/src/main/scala/fix/MutSetMapSrc.scala b/scalafix/output212/src/main/scala/fix/MutSetMapSrc.scala similarity index 100% rename from scalafix/output/src/main/scala/fix/MutSetMapSrc.scala rename to scalafix/output212/src/main/scala/fix/MutSetMapSrc.scala diff --git a/scalafix/output212/src/main/scala/fix/RoughlyMapValuesSrc.scala b/scalafix/output212/src/main/scala/fix/RoughlyMapValuesSrc.scala new file mode 100644 index 00000000..a7b1f620 --- /dev/null +++ b/scalafix/output212/src/main/scala/fix/RoughlyMapValuesSrc.scala @@ -0,0 +1,8 @@ + + + +package fix + +class RoughlyMapValuesSrc(map: Map[Int, Int]) { + map.mapValues(_ + 1).toMap +} diff --git a/scalafix/output/src/main/scala/fix/SetMapSrc.scala b/scalafix/output212/src/main/scala/fix/SetMapSrc.scala similarity index 84% rename from scalafix/output/src/main/scala/fix/SetMapSrc.scala rename to scalafix/output212/src/main/scala/fix/SetMapSrc.scala index 526be356..2aa2afbd 100644 --- a/scalafix/output/src/main/scala/fix/SetMapSrc.scala +++ b/scalafix/output212/src/main/scala/fix/SetMapSrc.scala @@ -8,5 +8,4 @@ class SetMapSrc(set: Set[Int], map: Map[Int, Int]) { map + (2 -> 3) + (3 -> 4) (set + 2 + 3).toString set + 2 + 3 - 4 - map.mapValues(_ + 1).toMap } \ No newline at end of file diff --git a/scalafix/output/src/main/scala/fix/TestsSrc.scala b/scalafix/output212/src/main/scala/fix/TestsSrc.scala similarity index 100% rename from scalafix/output/src/main/scala/fix/TestsSrc.scala rename to scalafix/output212/src/main/scala/fix/TestsSrc.scala diff --git a/scalafix/output-failure/src/main/scala/fix/CanBuildFromNegSrc.scala b/scalafix/output213-failure/src/main/scala/fix/CanBuildFromNegSrc.scala similarity index 100% rename from scalafix/output-failure/src/main/scala/fix/CanBuildFromNegSrc.scala rename to scalafix/output213-failure/src/main/scala/fix/CanBuildFromNegSrc.scala diff --git a/scalafix/output/src/main/scala/fix/IterableSrc.scala b/scalafix/output213/src/main/scala/fix/IterableSrc.scala similarity index 97% rename from scalafix/output/src/main/scala/fix/IterableSrc.scala rename to scalafix/output213/src/main/scala/fix/IterableSrc.scala index cd6b3c9a..cda2811d 100644 --- a/scalafix/output/src/main/scala/fix/IterableSrc.scala +++ b/scalafix/output213/src/main/scala/fix/IterableSrc.scala @@ -5,4 +5,4 @@ package fix class IterableSrc(it: Iterable[Int]) { it.iterator.sameElements(it) -} \ No newline at end of file +} diff --git a/scalafix/output/src/main/scala/fix/RetainSrc.scala b/scalafix/output213/src/main/scala/fix/RetainSrc.scala similarity index 97% rename from scalafix/output/src/main/scala/fix/RetainSrc.scala rename to scalafix/output213/src/main/scala/fix/RetainSrc.scala index c5ed3707..c09eee04 100644 --- a/scalafix/output/src/main/scala/fix/RetainSrc.scala +++ b/scalafix/output213/src/main/scala/fix/RetainSrc.scala @@ -1,3 +1,6 @@ + + + package fix import scala.collection.mutable.{Map, Set} @@ -6,4 +9,4 @@ class MethodRenames(xs: Map[Int, Int], ys: Set[Int]) { xs.filterInPlace{case (_, _) => true} xs.filterInPlace{case (x, y) => true} ys.filterInPlace(_ => true) -} \ No newline at end of file +} diff --git a/scalafix/output/src/main/scala/fix/StreamSrc.scala b/scalafix/output213/src/main/scala/fix/RoughlyStreamToLazyListSrc.scala similarity index 84% rename from scalafix/output/src/main/scala/fix/StreamSrc.scala rename to scalafix/output213/src/main/scala/fix/RoughlyStreamToLazyListSrc.scala index 8f79829f..87ad201e 100644 --- a/scalafix/output/src/main/scala/fix/StreamSrc.scala +++ b/scalafix/output213/src/main/scala/fix/RoughlyStreamToLazyListSrc.scala @@ -1,6 +1,9 @@ + + + package fix -object StreamSrc { +class RoughlyStreamToLazyListSrc() { val s = LazyList(1, 2, 3) s.lazyAppendedAll(List(4, 5, 6)) 1 #:: 2 #:: 3 #:: LazyList.Empty diff --git a/scalafix/output/src/main/scala/fix/TraversableSrc.scala b/scalafix/output213/src/main/scala/fix/TraversableSrc.scala similarity index 98% rename from scalafix/output/src/main/scala/fix/TraversableSrc.scala rename to scalafix/output213/src/main/scala/fix/TraversableSrc.scala index e8232467..096f01d8 100644 --- a/scalafix/output/src/main/scala/fix/TraversableSrc.scala +++ b/scalafix/output213/src/main/scala/fix/TraversableSrc.scala @@ -1,3 +1,6 @@ + + + package fix object TraversableSrc { diff --git a/scalafix/output/src/main/scala/fix/TupleNZippedSrc.scala b/scalafix/output213/src/main/scala/fix/TupleNZippedSrc.scala similarity index 89% rename from scalafix/output/src/main/scala/fix/TupleNZippedSrc.scala rename to scalafix/output213/src/main/scala/fix/TupleNZippedSrc.scala index e43cbc95..8a8e7ff6 100644 --- a/scalafix/output/src/main/scala/fix/TupleNZippedSrc.scala +++ b/scalafix/output213/src/main/scala/fix/TupleNZippedSrc.scala @@ -1,3 +1,6 @@ + + + package fix import scala.language.postfixOps @@ -11,7 +14,7 @@ object Collectionstrawman_v0_Tuple2Zipped { 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)) + List(1, 2, 3).lazyZip(Array(1)) } def coll(x: Int): List[Int] = ??? } @@ -27,7 +30,7 @@ object Collectionstrawman_v0_Tuple3Zipped { 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)) + List(1, 2, 3).lazyZip(Set(1, 2, 3)).lazyZip(Array(1)) } def coll(x: Int): List[Int] = ??? -} \ No newline at end of file +} diff --git a/scalafix/project/build.properties b/scalafix/project/build.properties deleted file mode 100644 index c091b86c..00000000 --- a/scalafix/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version=0.13.16 diff --git a/scalafix/project/plugins.sbt b/scalafix/project/plugins.sbt deleted file mode 100644 index 0fd7a907..00000000 --- a/scalafix/project/plugins.sbt +++ /dev/null @@ -1,3 +0,0 @@ -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.5.10") -addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0") -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.6.1") \ No newline at end of file diff --git a/scalafix/rules/src/main/scala/fix/CanBuildFrom.scala b/scalafix/rules/src/main/scala/fix/CanBuildFrom.scala new file mode 100644 index 00000000..86a09837 --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/CanBuildFrom.scala @@ -0,0 +1,184 @@ +package fix + +import scalafix._ +import scalafix.util._ +import scala.meta._ + +object CanBuildFrom { + def apply(paramss: List[List[Term.Param]], + body: Term, + ctx: RuleCtx, + collectionCanBuildFrom: SymbolMatcher, + nothing: SymbolMatcher)(implicit index: SemanticdbIndex): Patch = { + // CanBuildFrom has def apply() but not CanBuild + def emptyApply(param: Name): Boolean = { + import scala.meta.contrib._ + val matchCbf = SymbolMatcher.exact(ctx.index.symbol(param).get) + body.exists{ + case Term.Apply(Term.Select(matchCbf(_), _), Nil) => true + case Term.Apply(matchCbf(_), Nil) => true + case _ => false + } + } + + paramss.flatten.collect{ + case Term.Param( + List(Mod.Implicit()), + param, + Some( + Type.Apply( + cbf @ collectionCanBuildFrom(_), + List(p1, _, _) + ) + ), + _ + ) if !nothing.matches(p1) && !emptyApply(param) => new CanBuildFrom(param, cbf) + }.map(_.toBuildFrom(body, ctx)).asPatch + } +} + +// example: +// implicit cbf: collection.generic.CanBuildFrom[C0, Int, CC[Int]] +// param: cbf +// cbf : collection.generic.CanBuildFrom +case class CanBuildFrom(param: Name, cbf: Type) { + def toBuildFrom(body: Term, ctx: RuleCtx)(implicit index: SemanticdbIndex): Patch = { + + val matchCbf = SymbolMatcher.exact(ctx.index.symbol(param).get) + + // cbf(x) / cbf.apply(x) => cbf.newBuilder(x) + def replaceNewBuilder(tree: Tree, cbf2: Term, x: Term): Patch = + ctx.replaceTree( + tree, + Term.Apply(Term.Select(cbf2, Term.Name("newBuilder")), List(x)).syntax + ) + + val cbfCalls = + body.collect { + // cbf.apply(x) + case ap @ Term.Apply(sel @ Term.Select(cbf2 @ matchCbf(_), apply), List(x)) => + replaceNewBuilder(ap, cbf2, x) + + // cbf(x) + case ap @ Term.Apply(cbf2 @ matchCbf(_), List(x)) => + replaceNewBuilder(ap, cbf2, x) + }.asPatch + + val parameterType = + ctx.replaceTree(cbf, "BuildFrom") + + parameterType + cbfCalls + } +} + +object CanBuildFromNothing { + def apply(paramss: List[List[Term.Param]], + body: Term, + ctx: RuleCtx, + collectionCanBuildFrom: SymbolMatcher, + nothing: SymbolMatcher, + toTpe: SymbolMatcher)(implicit index: SemanticdbIndex): Patch = { + paramss.flatten.collect{ + case + Term.Param( + List(Mod.Implicit()), + param, + Some( + tpe @ Type.Apply( + collectionCanBuildFrom(_), + List( + nothing(_), + t, + cct @ Type.Apply( + cc, + _ + ) + ) + ) + ), + _ + ) => new CanBuildFromNothing(param, tpe, t, cct, cc, body, ctx, toTpe) + }.map(_.toFactory).asPatch + } +} + +// example: +// implicit cbf: collection.generic.CanBuildFrom[Nothing, Int, CC[Int]] +// +// param: cbf +// tpe : collection.generic.CanBuildFrom[Nothing, Int, CC[Int]] +// cbf : CanBuildFrom +// v : Int +// cct : CC[Int] +// cc : CC +case class CanBuildFromNothing(param: Name, + tpe: Type.Apply, + t: Type, + cct: Type.Apply, + cc: Type, + body: Term, + ctx: RuleCtx, + toTpe: SymbolMatcher) { + def toFactory(implicit index: SemanticdbIndex): Patch = { + val matchCbf = SymbolMatcher.exact(ctx.index.symbol(param).get) + + // cbf() / cbf.apply => cbf.newBuilder + def replaceNewBuilder(tree: Tree, cbf2: Term): Patch = + ctx.replaceTree(tree, Term.Select(cbf2, Term.Name("newBuilder")).syntax) + + // don't patch cbf.apply twice (cbf.apply and cbf.apply()) + val visitedCbfCalls = scala.collection.mutable.Set[Tree]() + + val cbfCalls = + body.collect { + // cbf.apply() + case ap @ Term.Apply(sel @ Term.Select(cbf2 @ matchCbf(_), apply), Nil) => + visitedCbfCalls += sel + replaceNewBuilder(ap, cbf2) + + // cbf.apply + case sel @ Term.Select(cbf2 @ matchCbf(_), ap) if (!visitedCbfCalls.contains(sel)) => + replaceNewBuilder(sel, cbf2) + + // cbf() + case ap @ Term.Apply(cbf2 @ matchCbf(_), Nil) => + replaceNewBuilder(ap, cbf2) + }.asPatch + + + val matchCC = SymbolMatcher.exact(ctx.index.symbol(cc).get) + + // e.to[CC] => cbf.fromSpecific(e) + val toCalls = + body.collect { + case ap @ Term.ApplyType(Term.Select(e, to @ toTpe(_)), List(cc2 @ matchCC(_))) => + + // e.to[CC](*cbf*) extract implicit parameter + val synth = ctx.index.synthetics.find(_.position.end == ap.pos.end).get + val Term.Apply(_, List(implicitCbf)) = synth.text.parse[Term].get + + // This is a bit unsafe + // https://github.com/scalameta/scalameta/issues/1636 + if (implicitCbf.syntax == param.syntax) { + + // .to[CC] + val apToRemove = ap.tokens.slice(e.tokens.end - ap.tokens.start, ap.tokens.size) + + ctx.removeTokens(apToRemove) + + ctx.addLeft(e, implicitCbf.syntax + ".fromSpecific(") + + ctx.addRight(e, ")") + } else Patch.empty + + }.asPatch + + // implicit cbf: collection.generic.CanBuildFrom[Nothing, Int, CC[Int]] => + // implicit cbf: Factory[Int, CC[Int]] + val parameterType = + ctx.replaceTree( + tpe, + Type.Apply(Type.Name("Factory"), List(t, cct)).syntax + ) + + parameterType + cbfCalls + toCalls + } +} diff --git a/scalafix/rules/src/main/scala/fix/CrossCompat.scala b/scalafix/rules/src/main/scala/fix/CrossCompat.scala new file mode 100644 index 00000000..d6d4cd78 --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/CrossCompat.scala @@ -0,0 +1,5 @@ +package fix + +import scalafix._ + +case class CrossCompat(index: SemanticdbIndex) extends SemanticRule(index, "CrossCompat") with Stable212Base diff --git a/scalafix/rules/src/main/scala/fix/Experimental.scala b/scalafix/rules/src/main/scala/fix/Experimental.scala index 0ffd9b11..920618f5 100644 --- a/scalafix/rules/src/main/scala/fix/Experimental.scala +++ b/scalafix/rules/src/main/scala/fix/Experimental.scala @@ -1,57 +1,24 @@ package fix import scalafix._ -import scalafix.syntax._ import scalafix.util._ import scala.meta._ +// 2.12 Cross-Compatible case class Experimental(index: SemanticdbIndex) extends SemanticRule(index, "Experimental") { - // WARNING: TOTAL HACK - // this is only to unblock us until Term.tpe is available: https://github.com/scalameta/scalameta/issues/1212 - // if we have a simple identifier, we can look at his definition at query it's type - // this should be improved in future version of scalameta - object TypeMatcher { - def apply(symbols: Symbol*)(implicit index: SemanticdbIndex): TypeMatcher = - new TypeMatcher(symbols: _*)(index) - } - final class TypeMatcher(symbols: Symbol*)(implicit index: SemanticdbIndex) { - def unapply(tree: Tree): Boolean = { - index.denotation(tree) - .exists(_.names.headOption.exists(n => symbols.exists(_ == n.symbol))) - } - } - - val CollectionMap: TypeMatcher = TypeMatcher( + val CollectionMap = TypeMatcher( Symbol("_root_.scala.collection.immutable.Map#"), Symbol("_root_.scala.collection.mutable.Map#"), Symbol("_root_.scala.Predef.Map#") ) + val CollectionSet = TypeMatcher(Symbol("_root_.scala.collection.Set#")) - val CollectionSet: TypeMatcher = TypeMatcher(Symbol("_root_.scala.collection.Set#")) - - val mapZip = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.IterableLike#zip(Lscala/collection/GenIterable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - ) - - val mapPlus = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.MapLike#`+`(Lscala/Tuple2;)Lscala/collection/Map;.") - ) - - val setPlus = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.SetLike#`+`(Ljava/lang/Object;)Lscala/collection/Set;.") - ) - - val setMinus = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.SetLike#`-`(Ljava/lang/Object;)Lscala/collection/Set;.") - ) - - def startsWithParens(tree: Tree): Boolean = - tree.tokens.headOption.map(_.is[Token.LeftParen]).getOrElse(false) + // == Symbols == + val mapZip = exact("_root_.scala.collection.IterableLike#zip(Lscala/collection/GenIterable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val mapPlus = exact("_root_.scala.collection.MapLike#`+`(Lscala/Tuple2;)Lscala/collection/Map;.") + val setPlus = exact("_root_.scala.collection.SetLike#`+`(Ljava/lang/Object;)Lscala/collection/Set;.") + val setMinus = exact("_root_.scala.collection.SetLike#`-`(Ljava/lang/Object;)Lscala/collection/Set;.") def replaceMapZip(ctx: RuleCtx): Patch = { ctx.tree.collect { @@ -65,7 +32,7 @@ case class Experimental(index: SemanticdbIndex) extends SemanticRule(index, "Exp val col = "_root_.scala.collection." + col0 val callSite = if (startsWithParens(rhs)) { - ctx.addLeft(rhs, col) + ctx.addLeft(rhs, col) } else { ctx.addLeft(rhs, col + "(") + @@ -78,7 +45,7 @@ case class Experimental(index: SemanticdbIndex) extends SemanticRule(index, "Exp ctx.tree.collect { case Term.ApplyInfix(CollectionSet(), op @ setPlus(_), Nil, List(rhs)) => rewriteOp(op, rhs, "+", "Set") - + case Term.ApplyInfix(CollectionSet(), op @ setMinus(_), Nil, List(rhs)) => rewriteOp(op, rhs, "-", "Set") @@ -90,4 +57,4 @@ case class Experimental(index: SemanticdbIndex) extends SemanticRule(index, "Exp override def fix(ctx: RuleCtx): Patch = replaceSetMapPlusMinus(ctx) + replaceMapZip(ctx) -} \ No newline at end of file +} diff --git a/scalafix/rules/src/main/scala/fix/NewCollections.scala b/scalafix/rules/src/main/scala/fix/NewCollections.scala new file mode 100644 index 00000000..9cf99a0d --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/NewCollections.scala @@ -0,0 +1,227 @@ +package fix + +import scalafix._ +import scalafix.util._ +import scala.meta._ + +// Not 2.12 Cross-Compatible +case class NewCollections(index: SemanticdbIndex) extends SemanticRule(index, "NewCollections") with Stable212Base { + // == Symbols == + val iterableSameElement = exact("_root_.scala.collection.IterableLike#sameElements(Lscala/collection/GenIterable;)Z.") + val iterator = normalized("_root_.scala.collection.TraversableLike.toIterator.") + val tupleZipped = normalized( + "_root_.scala.runtime.Tuple2Zipped.Ops.zipped.", + "_root_.scala.runtime.Tuple3Zipped.Ops.zipped." + ) + val retainMap = normalized("_root_.scala.collection.mutable.MapLike.retain.") + val retainSet = normalized("_root_.scala.collection.mutable.SetLike.retain.") + + object Breakout { + implicit class RichSymbol(val symbol: Symbol) { + def exact(tree: Tree)(implicit index: SemanticdbIndex): Boolean = + index.symbol(tree).fold(false)(_ == symbol) + } + + val breakOut = SymbolMatcher.exact(Symbol("_root_.scala.collection.package.breakOut(Lscala/collection/generic/CanBuildFrom;)Lscala/collection/generic/CanBuildFrom;.")) + + // infix operators + val `List ++` = Symbol("_root_.scala.collection.immutable.List#`++`(Lscala/collection/GenTraversableOnce;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val `List +:` = Symbol("_root_.scala.collection.immutable.List#`+:`(Ljava/lang/Object;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val `SeqLike :+` = Symbol("_root_.scala.collection.SeqLike#`:+`(Ljava/lang/Object;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val `TraversableLike ++:` = Symbol("_root_.scala.collection.TraversableLike#`++:`(Lscala/collection/Traversable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + + val operatorsIteratorSymbols = List(`List ++`) + val operatorsViewSymbols = List(`List +:`, `SeqLike :+`, `TraversableLike ++:`) + val operatorsSymbols = operatorsViewSymbols ++ operatorsIteratorSymbols + + val operatorsIterator = SymbolMatcher.exact(operatorsIteratorSymbols: _*) + val operatorsView = SymbolMatcher.exact(operatorsViewSymbols: _*) + val operators = SymbolMatcher.exact(operatorsSymbols: _*) + + // select + val `List.collect` = Symbol("_root_.scala.collection.immutable.List#collect(Lscala/PartialFunction;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val `List.flatMap` = Symbol("_root_.scala.collection.immutable.List#flatMap(Lscala/Function1;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val `List.map` = Symbol("_root_.scala.collection.immutable.List#map(Lscala/Function1;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val `IterableLike.zip` = Symbol("_root_.scala.collection.IterableLike#zip(Lscala/collection/GenIterable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val `IterableLike.zipAll` = Symbol("_root_.scala.collection.IterableLike#zipAll(Lscala/collection/GenIterable;Ljava/lang/Object;Ljava/lang/Object;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val `SeqLike.union` = Symbol("_root_.scala.collection.SeqLike#union(Lscala/collection/GenSeq;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val `SeqLike.updated` = Symbol("_root_.scala.collection.SeqLike#updated(ILjava/lang/Object;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + val `SeqLike.reverseMap` = Symbol("_root_.scala.collection.SeqLike#reverseMap(Lscala/Function1;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") + + val functionsIteratorSymbols = List(`List.collect`, `List.flatMap`, `List.map`, `IterableLike.zip`, `IterableLike.zipAll`, `SeqLike.union`) + val functionsViewSymbols = List(`SeqLike.updated`) + val functionsReverseIteratorSymbols = List(`SeqLike.reverseMap`) + val functionsSymbols = functionsIteratorSymbols ++ functionsViewSymbols ++ functionsReverseIteratorSymbols + + val functionsIterator = SymbolMatcher.exact(functionsIteratorSymbols: _*) + val functionsReverseIterator = SymbolMatcher.exact(functionsReverseIteratorSymbols: _*) + val functionsView = SymbolMatcher.exact(functionsViewSymbols: _*) + val functions = SymbolMatcher.exact(functionsSymbols: _*) + + // special select + + // iterator + val `TraversableLike.scanLeft` = SymbolMatcher.exact(Symbol("_root_.scala.collection.TraversableLike#scanLeft(Ljava/lang/Object;Lscala/Function2;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.")) + + def isLeftAssociative(tree: Tree): Boolean = + tree match { + case Term.Name(value) => value.last != ':' + case _ => false + } + } + + // == Rules == + + def replaceSymbols(ctx: RuleCtx): Patch = { + ctx.replaceSymbols( + "scala.TraversableOnce" -> "scala.IterableOnce", + "scala.collection.TraversableOnce" -> "scala.collection.IterableOnce" + ) + } + + def replaceMutableSet(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case retainSet(n: Name) => + ctx.replaceTree(n, "filterInPlace") + }.asPatch + } + + def replaceMutableMap(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case Term.Apply(Term.Select(_, retainMap(n: Name)), List(_: Term.PartialFunction)) => + ctx.replaceTree(n, "filterInPlace") + + case Term.Apply(Term.Select(_, retainMap(n: Name)), List(_: Term.Function)) => + trailingParens(n, ctx).map { case (open, close) => + ctx.replaceToken(open, "{case ") + + ctx.replaceToken(close, "}") + + ctx.replaceTree(n, "filterInPlace") + }.asPatch + }.asPatch + } + + def replaceToList(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case iterator(t: Name) => + ctx.replaceTree(t, "iterator") + + case t @ toTpe(n: Name) => + trailingBrackets(n, ctx).map { case (open, close) => + ctx.replaceToken(open, "(") + ctx.replaceToken(close, ")") + }.asPatch + }.asPatch + } + + def replaceTupleZipped(ctx: RuleCtx): Patch = { + 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 + } + + def replaceIterableSameElements(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case Term.Apply(Term.Select(lhs, iterableSameElement(_)), List(_)) => + ctx.addRight(lhs, ".iterator") + }.asPatch + } + + def replaceBreakout(ctx: RuleCtx): Patch = { + import Breakout._ + + def fixIt(intermediate: String, lhs: Term, ap: Term, breakout: Tree): Patch = { + ctx.addRight(lhs, "." + intermediate) + + ctx.addRight(ap, ".to") + + ctx.replaceTree(breakout, "implicitly") + } + + ctx.tree.collect { + case i: Importee if breakOut.matches(i) => + ctx.removeImportee(i) + + case Term.Apply(ap @ Term.ApplyInfix(lhs, operators(op), _, List(rhs)), List(breakOut(bo))) => + val subject = + if(isLeftAssociative(op)) lhs + else rhs + + val intermediate = + op match { + case operatorsIterator(_) => "iterator" + case operatorsView(_) => "view" + // since operators(op) matches iterator and view + case _ => throw new Exception("impossible") + } + + fixIt(intermediate, subject, ap, bo) + + case Term.Apply(ap @ Term.Apply(Term.Select(lhs, functions(op)), _), List(breakOut(bo))) => + val intermediate = + op match { + case functionsIterator(_) => "iterator" + case functionsView(_) => "view" + case functionsReverseIterator(_) => "reverseIterator" + // since functions(op) matches iterator, view and reverseIterator + case _ => throw new Exception("impossible") + } + + val replaceUnion = + if (`SeqLike.union`.exact(op)) ctx.replaceTree(op, "concat") + else Patch.empty + + val isReversed = `SeqLike.reverseMap`.exact(op) + val replaceReverseMap = + if (isReversed) ctx.replaceTree(op, "map") + else Patch.empty + + fixIt(intermediate, lhs, ap, bo) + replaceUnion + replaceReverseMap + + case Term.Apply(ap @ Term.Apply(Term.Apply(Term.Select(lhs, `TraversableLike.scanLeft`(op)), _), _), List(breakOut(bo))) => + fixIt("iterator", lhs, ap, bo) + }.asPatch + } + + override def fix(ctx: RuleCtx): Patch = { + super.fix(ctx) + + replaceToList(ctx) + + replaceSymbols(ctx) + + replaceTupleZipped(ctx) + + replaceMutableMap(ctx) + + replaceMutableSet(ctx) + + replaceBreakout(ctx) + + replaceIterableSameElements(ctx) + } +} diff --git a/scalafix/rules/src/main/scala/fix/RoughlyMapValues.scala b/scalafix/rules/src/main/scala/fix/RoughlyMapValues.scala new file mode 100644 index 00000000..07838667 --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/RoughlyMapValues.scala @@ -0,0 +1,24 @@ +package fix + +import scalafix._ +import scalafix.util._ +import scala.meta._ + + +/* 2.12 Cross-Compatible + * + * This rules is marked unstable since Map.mapValues was lazy + */ +case class RoughlyMapValues(index: SemanticdbIndex) extends SemanticRule(index, "RoughlyMapValues") { + val mapMapValues = + SymbolMatcher.exact( + Symbol("_root_.scala.collection.immutable.MapLike#mapValues(Lscala/Function1;)Lscala/collection/immutable/Map;.") + ) + + override def fix(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case ap @ Term.Apply(Term.Select(_, mapMapValues(_)), List(_)) => + ctx.addRight(ap, ".toMap") + }.asPatch + } +} diff --git a/scalafix/rules/src/main/scala/fix/RoughlyStreamToLazyListSrc.scala b/scalafix/rules/src/main/scala/fix/RoughlyStreamToLazyListSrc.scala new file mode 100644 index 00000000..90a04b11 --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/RoughlyStreamToLazyListSrc.scala @@ -0,0 +1,35 @@ +package fix + +import scalafix._ +import scalafix.util._ +import scala.meta._ + +/* Not 2.12 Cross-Compatible + * + * This rules is marked unstable since Stream is not strictly equivalent to LazyList. + * LazyList has a lazy head but not Stream + */ +case class RoughlyStreamToLazyList(index: SemanticdbIndex) extends SemanticRule(index, "RoughlyStreamToLazyList") { + + val streamAppend = SymbolMatcher.normalized( + Symbol("_root_.scala.collection.immutable.Stream.append.") + ) + + def replaceStreamAppend(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case streamAppend(t: Name) => + ctx.replaceTree(t, "lazyAppendedAll") + }.asPatch + } + + def replaceSymbols(ctx: RuleCtx): Patch = { + ctx.replaceSymbols( + "scala.Stream" -> "scala.LazyList", + "scala.collection.immutable.Stream" -> "scala.collection.immutable.LazyList" + ) + } + + override def fix(ctx: RuleCtx): Patch = { + replaceStreamAppend(ctx) + replaceSymbols(ctx) + } +} diff --git a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala b/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala deleted file mode 100644 index 3c3662a0..00000000 --- a/scalafix/rules/src/main/scala/fix/Scalacollectioncompat_newcollections.scala +++ /dev/null @@ -1,605 +0,0 @@ -package fix - -import scalafix._ -import scalafix.syntax._ -import scalafix.util._ -import scala.meta._ - -case class Scalacollectioncompat_newcollections(index: SemanticdbIndex) - extends SemanticRule(index, "Scalacollectioncompat_newcollections") { - - // Two rules triggers the same rewrite TraversableLike.to and CanBuildFrom - // we keep track of what is handled in CanBuildFrom and guard against TraversableLike.to - val handledTo = scala.collection.mutable.Set[Tree]() - - def trailingParens(tree: Tree, ctx: RuleCtx): Option[(Token.LeftParen, Token.RightParen)] = - for { - end <- tree.tokens.lastOption - open <- ctx.tokenList.find(end)(_.is[Token.LeftParen]).map(_.asInstanceOf[Token.LeftParen]) - close <- ctx.matchingParens.close(open) - } yield (open, close) - - def trailingBrackets(tree: Tree, ctx: RuleCtx): Option[(Token.LeftBracket, Token.RightBracket)] = - for { - end <- tree.tokens.lastOption - open <- ctx.tokenList.find(end)(_.is[Token.LeftBracket]).map(_.asInstanceOf[Token.LeftBracket]) - close <- ctx.matchingParens.close(open) - } yield (open, close) - - def replaceSymbols(ctx: RuleCtx): Patch = { - ctx.replaceSymbols( - "scala.collection.LinearSeq" -> "scala.collection.immutable.List", - "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 collectionCanBuildFrom = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.generic.CanBuildFrom#") - ) - - val collectionCanBuildFromImport = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.generic.CanBuildFrom.;_root_.scala.collection.generic.CanBuildFrom#") - ) - - val nothing = - SymbolMatcher.exact( - Symbol("_root_.scala.Nothing#") - ) - - val toTpe = SymbolMatcher.normalized( - Symbol("_root_.scala.collection.TraversableLike.to.") - ) - val iterator = SymbolMatcher.normalized( - Symbol("_root_.scala.collection.TraversableLike.toIterator.") - ) - val tupleZipped = SymbolMatcher.normalized( - Symbol("_root_.scala.runtime.Tuple2Zipped.Ops.zipped."), - Symbol("_root_.scala.runtime.Tuple3Zipped.Ops.zipped.") - ) - val setPlus2 = SymbolMatcher.exact( - Symbol("_root_.scala.collection.SetLike#`+`(Ljava/lang/Object;Ljava/lang/Object;Lscala/collection/Seq;)Lscala/collection/Set;.") - ) - val mapPlus2 = SymbolMatcher.exact( - Symbol("_root_.scala.collection.immutable.MapLike#`+`(Lscala/Tuple2;Lscala/Tuple2;Lscala/collection/Seq;)Lscala/collection/immutable/Map;.") - ) - val mutSetPlus = SymbolMatcher.exact( - Symbol("_root_.scala.collection.mutable.SetLike#`+`(Ljava/lang/Object;)Lscala/collection/mutable/Set;.") - ) - val mutMapPlus = SymbolMatcher.exact( - Symbol("_root_.scala.collection.mutable.MapLike#`+`(Lscala/Tuple2;)Lscala/collection/mutable/Map;.") - ) - val mutMapUpdate = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.mutable.MapLike#updated(Ljava/lang/Object;Ljava/lang/Object;)Lscala/collection/mutable/Map;.") - ) - - val iterableSameElement = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.IterableLike#sameElements(Lscala/collection/GenIterable;)Z.") - ) - - def foldSymbol(isLeft: Boolean): SymbolMatcher = { - val op = - if (isLeft) "/:" - else ":\\" - - SymbolMatcher.normalized(Symbol(s"_root_.scala.collection.TraversableOnce.`$op`.")) - } - val foldLeftSymbol = foldSymbol(isLeft = true) - val foldRightSymbol = foldSymbol(isLeft = false) - - val retainMap = - SymbolMatcher.normalized( - Symbol("_root_.scala.collection.mutable.MapLike.retain.") - ) - - val mapMapValues = - SymbolMatcher.exact( - Symbol("_root_.scala.collection.immutable.MapLike#mapValues(Lscala/Function1;)Lscala/collection/immutable/Map;.") - ) - - val retainSet = - SymbolMatcher.normalized( - Symbol("_root_.scala.collection.mutable.SetLike.retain.") - ) - - - val arrayBuilderMake = - SymbolMatcher.normalized( - Symbol("_root_.scala.collection.mutable.ArrayBuilder.make(Lscala/reflect/ClassTag;)Lscala/collection/mutable/ArrayBuilder;.") - ) - - object Breakout { - implicit class RichSymbol(val symbol: Symbol) { - def exact(tree: Tree)(implicit index: SemanticdbIndex): Boolean = - index.symbol(tree).fold(false)(_ == symbol) - } - - val breakOut = SymbolMatcher.exact(Symbol("_root_.scala.collection.package.breakOut(Lscala/collection/generic/CanBuildFrom;)Lscala/collection/generic/CanBuildFrom;.")) - - // infix operators - val `List ++` = Symbol("_root_.scala.collection.immutable.List#`++`(Lscala/collection/GenTraversableOnce;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - val `List +:` = Symbol("_root_.scala.collection.immutable.List#`+:`(Ljava/lang/Object;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - val `SeqLike :+` = Symbol("_root_.scala.collection.SeqLike#`:+`(Ljava/lang/Object;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - val `TraversableLike ++:` = Symbol("_root_.scala.collection.TraversableLike#`++:`(Lscala/collection/Traversable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - - val operatorsIteratorSymbols = List(`List ++`) - val operatorsViewSymbols = List(`List +:`, `SeqLike :+`, `TraversableLike ++:`) - val operatorsSymbols = operatorsViewSymbols ++ operatorsIteratorSymbols - - val operatorsIterator = SymbolMatcher.exact(operatorsIteratorSymbols: _*) - val operatorsView = SymbolMatcher.exact(operatorsViewSymbols: _*) - val operators = SymbolMatcher.exact(operatorsSymbols: _*) - - // select - val `List.collect` = Symbol("_root_.scala.collection.immutable.List#collect(Lscala/PartialFunction;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - val `List.flatMap` = Symbol("_root_.scala.collection.immutable.List#flatMap(Lscala/Function1;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - val `List.map` = Symbol("_root_.scala.collection.immutable.List#map(Lscala/Function1;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - val `IterableLike.zip` = Symbol("_root_.scala.collection.IterableLike#zip(Lscala/collection/GenIterable;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - val `IterableLike.zipAll` = Symbol("_root_.scala.collection.IterableLike#zipAll(Lscala/collection/GenIterable;Ljava/lang/Object;Ljava/lang/Object;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - val `SeqLike.union` = Symbol("_root_.scala.collection.SeqLike#union(Lscala/collection/GenSeq;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - val `SeqLike.updated` = Symbol("_root_.scala.collection.SeqLike#updated(ILjava/lang/Object;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - val `SeqLike.reverseMap` = Symbol("_root_.scala.collection.SeqLike#reverseMap(Lscala/Function1;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.") - - val functionsIteratorSymbols = List(`List.collect`, `List.flatMap`, `List.map`, `IterableLike.zip`, `IterableLike.zipAll`, `SeqLike.union`) - val functionsViewSymbols = List(`SeqLike.updated`) - val functionsReverseIteratorSymbols = List(`SeqLike.reverseMap`) - val functionsSymbols = functionsIteratorSymbols ++ functionsViewSymbols ++ functionsReverseIteratorSymbols - - val functionsIterator = SymbolMatcher.exact(functionsIteratorSymbols: _*) - val functionsReverseIterator = SymbolMatcher.exact(functionsReverseIteratorSymbols: _*) - val functionsView = SymbolMatcher.exact(functionsViewSymbols: _*) - val functions = SymbolMatcher.exact(functionsSymbols: _*) - - // special select - - // iterator - val `TraversableLike.scanLeft` = SymbolMatcher.exact(Symbol("_root_.scala.collection.TraversableLike#scanLeft(Ljava/lang/Object;Lscala/Function2;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;.")) - - def isLeftAssociative(tree: Tree): Boolean = - tree match { - case Term.Name(value) => value.last != ':' - case _ => false - } - } - - def startsWithParens(tree: Tree): Boolean = - tree.tokens.headOption.map(_.is[Token.LeftParen]).getOrElse(false) - - def replaceMutableSet(ctx: RuleCtx) = - ctx.tree.collect { - case retainSet(n: Name) => - ctx.replaceTree(n, "filterInPlace") - }.asPatch - - def replaceMutableMap(ctx: RuleCtx) = - ctx.tree.collect { - case Term.Apply(Term.Select(_, retainMap(n: Name)), List(_: Term.PartialFunction)) => - ctx.replaceTree(n, "filterInPlace") - - case Term.Apply(Term.Select(_, retainMap(n: Name)), List(_: Term.Function)) => - trailingParens(n, ctx).map { case (open, close) => - ctx.replaceToken(open, "{case ") + - ctx.replaceToken(close, "}") + - ctx.replaceTree(n, "filterInPlace") - }.asPatch - }.asPatch - - def replaceSymbolicFold(ctx: RuleCtx) = - ctx.tree.collect { - case Term.Apply(ap @ Term.ApplyInfix(rhs, foldRightSymbol(_), _, List(lhs)), _) => - ctx.replaceTree(ap, s"$rhs.foldRight($lhs)") - - case Term.Apply(ap @ Term.ApplyInfix(lhs, foldLeftSymbol(_), _, List(rhs)), _) => - ctx.replaceTree(ap, s"$rhs.foldLeft($lhs)") - }.asPatch - - def replaceToList(ctx: RuleCtx) = - ctx.tree.collect { - case iterator(t: Name) => - ctx.replaceTree(t, "iterator") - case t @ toTpe(n: Name) if !handledTo.contains(n) => - trailingBrackets(n, ctx).map { case (open, close) => - 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 - - val streamAppend = SymbolMatcher.normalized( - Symbol("_root_.scala.collection.immutable.Stream.append.") - ) - - def replaceStreamAppend(ctx: RuleCtx): Patch = - ctx.tree.collect { - case streamAppend(t: Name) => - ctx.replaceTree(t, "lazyAppendedAll") - }.asPatch - - def replaceSetMapPlus2(ctx: RuleCtx): Patch = { - def rewritePlus(ap: Term.ApplyInfix, lhs: Term, op: Term.Name, rhs1: Term, rhs2: Term): Patch = { - val tokensToReplace = - if(startsWithParens(ap)) { - // don't drop surrounding parens - ap.tokens.slice(1, ap.tokens.size - 1) - } else ap.tokens - - val newTree = - Term.ApplyInfix( - Term.ApplyInfix(lhs, op, Nil, List(rhs1)), - op, - Nil, - List(rhs2) - ).syntax - - ctx.removeTokens(tokensToReplace) + - tokensToReplace.headOption.map(x => ctx.addRight(x, newTree)) - } - ctx.tree.collect { - case ap @ Term.ApplyInfix(lhs, op @ mapPlus2(_), _, List(a, b)) => - rewritePlus(ap, lhs, op, a, b) - - case ap @ Term.ApplyInfix(lhs, op @ setPlus2(_), _, List(a, b)) => - rewritePlus(ap, lhs, op, a, b) - }.asPatch - } - - def replaceMutSetMapPlus(ctx: RuleCtx): Patch = { - def rewriteMutPlus(lhs: Term, op: Term.Name): Patch = { - ctx.addRight(lhs, ".clone()") + - ctx.addRight(op, "=") - } - - ctx.tree.collect { - case Term.ApplyInfix(lhs, op @ mutSetPlus(_), _, List(_)) => - rewriteMutPlus(lhs, op) - - case Term.ApplyInfix(lhs, op @ mutMapPlus(_), _, List(_)) => - rewriteMutPlus(lhs, op) - }.asPatch - } - - def replaceMutMapUpdated(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case Term.Apply(Term.Select(a, up @ mutMapUpdate(_)), List(k, v)) => { - ctx.addRight(up, "clone() += (") + - ctx.removeTokens(up.tokens) + - ctx.addRight(v, ")") - } - }.asPatch - } - - def replaceIterableSameElements(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case Term.Apply(Term.Select(lhs, iterableSameElement(_)), List(_)) => - ctx.addRight(lhs, ".iterator") - }.asPatch - } - - def replaceArrayBuilderMake(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case ap @ Term.Apply(at @ Term.ApplyType(Term.Select(lhs, arrayBuilderMake(_)), args), Nil) => - val extraParens = - ap.tokens.slice(at.tokens.size, ap.tokens.size) - ctx.removeTokens(extraParens) - }.asPatch - } - - - - def replaceMapMapValues(ctx: RuleCtx): Patch = { - ctx.tree.collect { - case ap @ Term.Apply(Term.Select(_, mapMapValues(_)), List(_)) => - ctx.addRight(ap, ".toMap") - }.asPatch - } - - object CanBuildFromNothing { - def apply(paramss: List[List[Term.Param]], body: Term, ctx: RuleCtx): Patch = { - paramss.flatten.collect{ - case - Term.Param( - List(Mod.Implicit()), - param, - Some( - tpe @ Type.Apply( - collectionCanBuildFrom(_), - List( - nothing(_), - t, - cct @ Type.Apply( - cc, - _ - ) - ) - ) - ), - _ - ) => new CanBuildFromNothing(param, tpe, t, cct, cc) - }.map(_.toFactory(body, ctx)).asPatch - } - } - - // example: - // implicit cbf: collection.generic.CanBuildFrom[Nothing, Int, CC[Int]] - // - // param: cbf - // tpe : collection.generic.CanBuildFrom[Nothing, Int, CC[Int]] - // cbf : CanBuildFrom - // v : Int - // cct : CC[Int] - // cc : CC - - case class CanBuildFromNothing(param: Name, tpe: Type.Apply, t: Type, cct: Type.Apply, cc: Type) { - def toFactory(body: Term, ctx: RuleCtx): Patch = { - val matchCbf = SymbolMatcher.exact(ctx.index.symbol(param).get) - - // cbf() / cbf.apply => cbf.newBuilder - def replaceNewBuilder(tree: Tree, cbf2: Term): Patch = - ctx.replaceTree(tree, Term.Select(cbf2, Term.Name("newBuilder")).syntax) - - // don't patch cbf.apply twice (cbf.apply and cbf.apply()) - val visitedCbfCalls = scala.collection.mutable.Set[Tree]() - - val cbfCalls = - body.collect { - // cbf.apply() - case ap @ Term.Apply(sel @ Term.Select(cbf2 @ matchCbf(_), apply), Nil) => - visitedCbfCalls += sel - replaceNewBuilder(ap, cbf2) - - // cbf.apply - case sel @ Term.Select(cbf2 @ matchCbf(_), ap) if (!visitedCbfCalls.contains(sel)) => - replaceNewBuilder(sel, cbf2) - - // cbf() - case ap @ Term.Apply(cbf2 @ matchCbf(_), Nil) => - replaceNewBuilder(ap, cbf2) - }.asPatch - - - val matchCC = SymbolMatcher.exact(ctx.index.symbol(cc).get) - - // e.to[CC] => e.to(cbf) - val toCalls = - body.collect { - case ap @ Term.ApplyType(Term.Select(_, to @ toTpe(_)), List(cc2 @ matchCC(_))) => - handledTo += to - - // e.to[CC](*cbf*) extract implicit parameter - val synth = ctx.index.synthetics.find(_.position.end == ap.pos.end).get - val Term.Apply(_, List(implicitCbf)) = synth.text.parse[Term].get - - // This is a bit unsafe - // https://github.com/scalameta/scalameta/issues/1636 - if (implicitCbf.syntax == param.syntax) { - trailingBrackets(to, ctx).map { case (open, close) => - ctx.replaceTree(cc2, implicitCbf.syntax) + - ctx.replaceToken(open, "(") + - ctx.replaceToken(close, ")") - }.asPatch - } else Patch.empty - - }.asPatch - - // implicit cbf: collection.generic.CanBuildFrom[Nothing, Int, CC[Int]] => - // implicit cbf: collection.Factory[Int, CC[Int]] - val parameterType = - ctx.replaceTree( - tpe, - Type.Apply(Type.Name("collection.Factory"), List(t, cct)).syntax - ) - - parameterType + cbfCalls + toCalls - } - } - - object CanBuildFrom { - def apply(paramss: List[List[Term.Param]], body: Term, ctx: RuleCtx): Patch = { - // CanBuildFrom has def apply() but not CanBuild - def emptyApply(param: Name): Boolean = { - import scala.meta.contrib._ - val matchCbf = SymbolMatcher.exact(ctx.index.symbol(param).get) - body.exists{ - case Term.Apply(Term.Select(matchCbf(_), _), Nil) => true - case Term.Apply(matchCbf(_), Nil) => true - case _ => false - } - } - - paramss.flatten.collect{ - case Term.Param( - List(Mod.Implicit()), - param, - Some( - Type.Apply( - cbf @ collectionCanBuildFrom(_), - List(p1, _, _) - ) - ), - _ - ) if !nothing.matches(p1) && !emptyApply(param) => new CanBuildFrom(param, cbf) - }.map(_.toBuildFrom(body, ctx)).asPatch - } - } - - // example: - // implicit cbf: collection.generic.CanBuildFrom[C0, Int, CC[Int]] - // param: cbf - // cbf : collection.generic.CanBuildFrom - case class CanBuildFrom(param: Name, cbf: Type) { - def toBuildFrom(body: Term, ctx: RuleCtx): Patch = { - - val matchCbf = SymbolMatcher.exact(ctx.index.symbol(param).get) - - // cbf(x) / cbf.apply(x) => cbf.newBuilder(x) - def replaceNewBuilder(tree: Tree, cbf2: Term, x: Term): Patch = - ctx.replaceTree( - tree, - Term.Apply(Term.Select(cbf2, Term.Name("newBuilder")), List(x)).syntax - ) - - val cbfCalls = - body.collect { - // cbf.apply(x) - case ap @ Term.Apply(sel @ Term.Select(cbf2 @ matchCbf(_), apply), List(x)) => - replaceNewBuilder(ap, cbf2, x) - - // cbf(x) - case ap @ Term.Apply(cbf2 @ matchCbf(_), List(x)) => - replaceNewBuilder(ap, cbf2, x) - }.asPatch - - val parameterType = - ctx.replaceTree(cbf, "collection.BuildFrom") - - parameterType + cbfCalls - } - } - - def replaceCanBuildFrom(ctx: RuleCtx): Patch = { - val useSites = - ctx.tree.collect { - case Defn.Def(_, _, _, paramss, _, body) => - CanBuildFromNothing(paramss, body, ctx) + - CanBuildFrom(paramss, body, ctx) - }.asPatch - - val imports = - ctx.tree.collect { - case i: Importee if collectionCanBuildFromImport.matches(i) => - ctx.removeImportee(i) - }.asPatch - - if (useSites.nonEmpty) useSites + imports - else Patch.empty - } - - def replaceBreakout(ctx: RuleCtx): Patch = { - import Breakout._ - - def fixIt(intermediate: String, lhs: Term, ap: Term, breakout: Tree): Patch = { - ctx.addRight(lhs, "." + intermediate) + - ctx.addRight(ap, ".to") + - ctx.replaceTree(breakout, "implicitly") - } - - ctx.tree.collect { - case i: Importee if breakOut.matches(i) => - ctx.removeImportee(i) - - case Term.Apply(ap @ Term.ApplyInfix(lhs, operators(op), _, List(rhs)), List(breakOut(bo))) => - val subject = - if(isLeftAssociative(op)) lhs - else rhs - - val intermediate = - op match { - case operatorsIterator(_) => "iterator" - case operatorsView(_) => "view" - // since operators(op) matches iterator and view - case _ => throw new Exception("impossible") - } - - fixIt(intermediate, subject, ap, bo) - - case Term.Apply(ap @ Term.Apply(Term.Select(lhs, functions(op)), _), List(breakOut(bo))) => - val intermediate = - op match { - case functionsIterator(_) => "iterator" - case functionsView(_) => "view" - case functionsReverseIterator(_) => "reverseIterator" - // since functions(op) matches iterator, view and reverseIterator - case _ => throw new Exception("impossible") - } - - val replaceUnion = - if (`SeqLike.union`.exact(op)) ctx.replaceTree(op, "concat") - else Patch.empty - - val isReversed = `SeqLike.reverseMap`.exact(op) - val replaceReverseMap = - if (isReversed) ctx.replaceTree(op, "map") - else Patch.empty - - fixIt(intermediate, lhs, ap, bo) + replaceUnion + replaceReverseMap - - case Term.Apply(ap @ Term.Apply(Term.Apply(Term.Select(lhs, `TraversableLike.scanLeft`(op)), _), _), List(breakOut(bo))) => - fixIt("iterator", lhs, ap, bo) - }.asPatch - } - - override def fix(ctx: RuleCtx): Patch = { - replaceCanBuildFrom(ctx) + - replaceToList(ctx) + - replaceSymbols(ctx) + - replaceTupleZipped(ctx) + - replaceCopyToBuffer(ctx) + - replaceStreamAppend(ctx) + - replaceMutableMap(ctx) + - replaceMutableSet(ctx) + - replaceSymbolicFold(ctx) + - replaceSetMapPlus2(ctx) + - replaceMutSetMapPlus(ctx) + - replaceMutMapUpdated(ctx) + - replaceArrayBuilderMake(ctx) + - replaceIterableSameElements(ctx) + - replaceMapMapValues(ctx) + - replaceBreakout(ctx) - } -} diff --git a/scalafix/rules/src/main/scala/fix/Stable212Base.scala b/scalafix/rules/src/main/scala/fix/Stable212Base.scala new file mode 100644 index 00000000..530fc83a --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/Stable212Base.scala @@ -0,0 +1,154 @@ +package fix + +import scalafix._ +import scalafix.util._ +import scala.meta._ + +// 2.12 Cross-Compatible +trait Stable212Base { self: SemanticRule => + + // == Symbols == + def foldSymbol(isLeft: Boolean): SymbolMatcher = { + val op = + if (isLeft) "/:" + else ":\\" + + normalized(s"_root_.scala.collection.TraversableOnce.`$op`.") + } + + val toTpe = normalized("_root_.scala.collection.TraversableLike.to.") + val copyToBuffer = normalized("_root_.scala.collection.TraversableOnce.copyToBuffer.") + val arrayBuilderMake = normalized("_root_.scala.collection.mutable.ArrayBuilder.make(Lscala/reflect/ClassTag;)Lscala/collection/mutable/ArrayBuilder;.") + val collectionCanBuildFrom = exact("_root_.scala.collection.generic.CanBuildFrom#") + val collectionCanBuildFromImport = exact("_root_.scala.collection.generic.CanBuildFrom.;_root_.scala.collection.generic.CanBuildFrom#") + val nothing = exact("_root_.scala.Nothing#") + val setPlus2 = exact("_root_.scala.collection.SetLike#`+`(Ljava/lang/Object;Ljava/lang/Object;Lscala/collection/Seq;)Lscala/collection/Set;.") + val mapPlus2 = exact("_root_.scala.collection.immutable.MapLike#`+`(Lscala/Tuple2;Lscala/Tuple2;Lscala/collection/Seq;)Lscala/collection/immutable/Map;.") + val mutSetPlus = exact("_root_.scala.collection.mutable.SetLike#`+`(Ljava/lang/Object;)Lscala/collection/mutable/Set;.") + val mutMapPlus = exact("_root_.scala.collection.mutable.MapLike#`+`(Lscala/Tuple2;)Lscala/collection/mutable/Map;.") + val mutMapUpdate = exact("_root_.scala.collection.mutable.MapLike#updated(Ljava/lang/Object;Ljava/lang/Object;)Lscala/collection/mutable/Map;.") + val foldLeftSymbol = foldSymbol(isLeft = true) + val foldRightSymbol = foldSymbol(isLeft = false) + + // == Rules == + + def replaceSymbols0(ctx: RuleCtx): Patch = { + ctx.replaceSymbols( + "scala.collection.LinearSeq" -> "scala.collection.immutable.List", + "scala.Traversable" -> "scala.Iterable", + "scala.collection.Traversable" -> "scala.collection.Iterable" + ) + } + + def replaceSymbolicFold(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case Term.Apply(ap @ Term.ApplyInfix(rhs, foldRightSymbol(_), _, List(lhs)), _) => + ctx.replaceTree(ap, s"$rhs.foldRight($lhs)") + + case Term.Apply(ap @ Term.ApplyInfix(lhs, foldLeftSymbol(_), _, List(rhs)), _) => + ctx.replaceTree(ap, s"$rhs.foldLeft($lhs)") + }.asPatch + } + + def replaceCopyToBuffer(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case t @ q"${copyToBuffer(Term.Select(collection, _))}($buffer)" => + ctx.replaceTree(t, q"$buffer ++= $collection".syntax) + }.asPatch + } + + def replaceSetMapPlus2(ctx: RuleCtx): Patch = { + def rewritePlus(ap: Term.ApplyInfix, lhs: Term, op: Term.Name, rhs1: Term, rhs2: Term): Patch = { + val tokensToReplace = + if(startsWithParens(ap)) { + // don't drop surrounding parens + ap.tokens.slice(1, ap.tokens.size - 1) + } else ap.tokens + + val newTree = + Term.ApplyInfix( + Term.ApplyInfix(lhs, op, Nil, List(rhs1)), + op, + Nil, + List(rhs2) + ).syntax + + ctx.removeTokens(tokensToReplace) + + tokensToReplace.headOption.map(x => ctx.addRight(x, newTree)) + } + ctx.tree.collect { + case ap @ Term.ApplyInfix(lhs, op @ mapPlus2(_), _, List(a, b)) => + rewritePlus(ap, lhs, op, a, b) + + case ap @ Term.ApplyInfix(lhs, op @ setPlus2(_), _, List(a, b)) => + rewritePlus(ap, lhs, op, a, b) + }.asPatch + } + + def replaceMutSetMapPlus(ctx: RuleCtx): Patch = { + def rewriteMutPlus(lhs: Term, op: Term.Name): Patch = { + ctx.addRight(lhs, ".clone()") + + ctx.addRight(op, "=") + } + + ctx.tree.collect { + case Term.ApplyInfix(lhs, op @ mutSetPlus(_), _, List(_)) => + rewriteMutPlus(lhs, op) + + case Term.ApplyInfix(lhs, op @ mutMapPlus(_), _, List(_)) => + rewriteMutPlus(lhs, op) + }.asPatch + } + + def replaceMutMapUpdated(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case Term.Apply(Term.Select(a, up @ mutMapUpdate(_)), List(k, v)) => { + ctx.addRight(up, "clone() += (") + + ctx.removeTokens(up.tokens) + + ctx.addRight(v, ")") + } + }.asPatch + } + + def replaceArrayBuilderMake(ctx: RuleCtx): Patch = { + ctx.tree.collect { + case ap @ Term.Apply(at @ Term.ApplyType(Term.Select(lhs, arrayBuilderMake(_)), args), Nil) => + val extraParens = + ap.tokens.slice(at.tokens.size, ap.tokens.size) + ctx.removeTokens(extraParens) + }.asPatch + } + + def replaceCanBuildFrom(ctx: RuleCtx): Patch = { + val useSites = + ctx.tree.collect { + case Defn.Def(_, _, _, paramss, _, body) => + CanBuildFromNothing(paramss, body, ctx, collectionCanBuildFrom, nothing, toTpe) + + CanBuildFrom(paramss, body, ctx, collectionCanBuildFrom, nothing) + }.asPatch + + val imports = + ctx.tree.collect { + case i: Importee if collectionCanBuildFromImport.matches(i) => + ctx.removeImportee(i) + }.asPatch + + val compatImport = + ctx.addGlobalImport(importer"scala.collection.compat._") + + if (useSites.nonEmpty) useSites + imports + compatImport + else Patch.empty + } + + override def fix(ctx: RuleCtx): Patch = { + replaceSymbols0(ctx) + + replaceCanBuildFrom(ctx) + + replaceCopyToBuffer(ctx) + + replaceSymbolicFold(ctx) + + replaceSetMapPlus2(ctx) + + replaceMutSetMapPlus(ctx) + + replaceMutMapUpdated(ctx) + + replaceArrayBuilderMake(ctx) + } + +} diff --git a/scalafix/rules/src/main/scala/fix/TypeMatcher.scala b/scalafix/rules/src/main/scala/fix/TypeMatcher.scala new file mode 100644 index 00000000..7c42bf9d --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/TypeMatcher.scala @@ -0,0 +1,21 @@ +package fix + +import scalafix._ +import scalafix.util._ +import scala.meta._ + +// WARNING: TOTAL HACK +// this is only to unblock us until Term.tpe is available: https://github.com/scalameta/scalameta/issues/1212 +// if we have a simple identifier, we can look at his definition at query it's type +// this should be improved in future version of scalameta +object TypeMatcher { + def apply(symbols: Symbol*)(implicit index: SemanticdbIndex): TypeMatcher = + new TypeMatcher(symbols: _*)(index) +} + +final class TypeMatcher(symbols: Symbol*)(implicit index: SemanticdbIndex) { + def unapply(tree: Tree): Boolean = { + index.denotation(tree) + .exists(_.names.headOption.exists(n => symbols.exists(_ == n.symbol))) + } +} diff --git a/scalafix/rules/src/main/scala/fix/package.scala b/scalafix/rules/src/main/scala/fix/package.scala new file mode 100644 index 00000000..249afd4b --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/package.scala @@ -0,0 +1,28 @@ +import scalafix._ +import scalafix.util._ +import scala.meta._ + +package object fix { + def normalized(symbols: String*)(implicit index: SemanticdbIndex): SymbolMatcher = + SymbolMatcher.normalized(symbols.map(Symbol(_)): _*) + + def exact(symbols: String*)(implicit index: SemanticdbIndex): SymbolMatcher = + SymbolMatcher.exact(symbols.map(Symbol(_)): _*) + + def trailingBrackets(tree: Tree, ctx: RuleCtx): Option[(Token.LeftBracket, Token.RightBracket)] = + for { + end <- tree.tokens.lastOption + open <- ctx.tokenList.find(end)(_.is[Token.LeftBracket]).map(_.asInstanceOf[Token.LeftBracket]) + close <- ctx.matchingParens.close(open) + } yield (open, close) + + def startsWithParens(tree: Tree): Boolean = + tree.tokens.headOption.map(_.is[Token.LeftParen]).getOrElse(false) + + def trailingParens(tree: Tree, ctx: RuleCtx): Option[(Token.LeftParen, Token.RightParen)] = + for { + end <- tree.tokens.lastOption + open <- ctx.tokenList.find(end)(_.is[Token.LeftParen]).map(_.asInstanceOf[Token.LeftParen]) + close <- ctx.matchingParens.close(open) + } yield (open, close) +} diff --git a/scalafix/tests/src/test/scala/fix/Collectionstrawman_Tests.scala b/scalafix/tests/src/test/scala/fix/ScalafixTests.scala similarity index 60% rename from scalafix/tests/src/test/scala/fix/Collectionstrawman_Tests.scala rename to scalafix/tests/src/test/scala/fix/ScalafixTests.scala index 65a58ddc..2ab26e4d 100644 --- a/scalafix/tests/src/test/scala/fix/Collectionstrawman_Tests.scala +++ b/scalafix/tests/src/test/scala/fix/ScalafixTests.scala @@ -4,13 +4,14 @@ import scala.meta._ import scalafix.testkit._ import scalafix._ -class Collectionstrawman_Tests +class ScalafixTests extends SemanticRuleSuite( SemanticdbIndex.load(Classpath(AbsolutePath(BuildInfo.inputClassdirectory))), AbsolutePath(BuildInfo.inputSourceroot), Seq( - AbsolutePath(BuildInfo.outputSourceroot), - AbsolutePath(BuildInfo.outputFailureSourceroot) + AbsolutePath(BuildInfo.output212Sourceroot), + AbsolutePath(BuildInfo.output213Sourceroot), + AbsolutePath(BuildInfo.output213FailureSourceroot) ) ) { runAllTests()