diff --git a/.github/workflows/scalafix.yaml b/.github/workflows/scalafix.yaml new file mode 100644 index 00000000..a90948c6 --- /dev/null +++ b/.github/workflows/scalafix.yaml @@ -0,0 +1,43 @@ +name: Scalafix Migration Rules + +on: + pull_request: + branches: [main] + paths: + - 'scalafix/**' + + push: + branches: [main] + paths: + - 'scalafix/**' + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + scalafix: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout current branch (full) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Java (corretto@11) + id: setup-java-corretto-11 + uses: actions/setup-java@v3 + with: + distribution: corretto + java-version: 11 + cache: sbt + + - name: sbt update + if: steps.setup-java-corretto-11.outputs.cache-hit == 'false' + run: sbt update + working-directory: scalafix + + - name: Test + run: sbt test + working-directory: scalafix diff --git a/.gitignore b/.gitignore index deba1c2b..1e84b290 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ .bsp .metals .vscode -project target diff --git a/scalafix/.sbtopts b/scalafix/.sbtopts new file mode 100644 index 00000000..162e0a33 --- /dev/null +++ b/scalafix/.sbtopts @@ -0,0 +1,7 @@ +-J-Xms2G +-J-Xmx8G +-J-XX:ReservedCodeCacheSize=512m +-J-XX:+TieredCompilation +-J-XX:+UseParallelGC +-Dfile.encoding=UTF8 + diff --git a/scalafix/build.sbt b/scalafix/build.sbt new file mode 100644 index 00000000..d163fb6a --- /dev/null +++ b/scalafix/build.sbt @@ -0,0 +1,98 @@ +lazy val V = _root_.scalafix.sbt.BuildInfo + +inThisBuild( + List( + resolvers ++= Resolver.sonatypeOssRepos("snapshots"), + organization := "com.spotify", + scalaVersion := V.scala212, + scalacOptions ++= List("-Yrangepos"), + publish / skip := true, + semanticdbEnabled := true, + semanticdbVersion := scalafixSemanticdb.revision, + semanticdbIncludeInJar := true, + scalafmtOnCompile := false, + scalafmtConfig := baseDirectory.value / ".." / ".scalafmt.conf", + ) +) + +lazy val root = project + .in(file(".")) + .aggregate( + tests.projectRefs ++ Seq[sbt.ProjectReference]( + // 0.7 + `input-0_7`, + `output-0_7`, + // scalafix + rules + ): _* + ) + +lazy val rules = project + .settings( + moduleName := "scalafix", + libraryDependencies ++= Seq( + "ch.epfl.scala" %% "scalafix-core" % V.scalafixVersion + ) + ) + +def magnolify(version: String): List[ModuleID] = { + val modules = List( + "magnolify-avro", + "magnolify-bigquery", + "magnolify-bigtable", + "magnolify-cats", + "magnolify-datastore", + "magnolify-guava", + "magnolify-neo4j", + "magnolify-parquet", + "magnolify-protobuf", + "magnolify-refined", + "magnolify-shared", + "magnolify-scalacheck", + "magnolify-tensorflow" + ) + + val libs = List ( + "org.apache.avro" % "avro" % "1.11.2", + "com.google.apis" % "google-api-services-bigquery" % "v2-rev20231111-2.0.0", + "com.google.api.grpc" % "proto-google-cloud-bigtable-v2" % "2.32.0", + "org.typelevel" %% "cats-core" % "2.10.0", + "com.google.cloud.datastore" % "datastore-v1-proto-client" % "2.18.2", + "com.google.guava" % "guava" % "33.0.0-jre", + "org.neo4j.driver" % "neo4j-java-driver" % "4.4.12", + "org.apache.parquet" % "parquet-hadoop" % "1.13.1", + "com.google.protobuf" % "protobuf-java" % "3.25.2", + "eu.timepit" %% "refined" % "0.11.1", + "org.scalacheck" %% "scalacheck" % "1.17.0", + "org.tensorflow" % "tensorflow-core-api" % "0.5.0" + ) + + modules.map("com.spotify" %% _ % version) ++ libs +} + +lazy val `input-0_7` = project + .settings( + libraryDependencies ++= magnolify("0.6.0") + ) + +lazy val `output-0_7` = project + .settings( + libraryDependencies ++= magnolify("0.7.0") + ) + + +lazy val magnolify0_7 = ConfigAxis("-magnolify-0_7", "-0_7-") + +lazy val tests = projectMatrix + .in(file("tests")) + .enablePlugins(ScalafixTestkitPlugin) + .customRow( + scalaVersions = Seq(V.scala212), + axisValues = Seq(magnolify0_7, VirtualAxis.jvm), + _.settings( + moduleName := name.value + magnolify0_7.idSuffix, + scalafixTestkitOutputSourceDirectories := (`output-0_7` / Compile / unmanagedSourceDirectories).value, + scalafixTestkitInputSourceDirectories := (`input-0_7` / Compile / unmanagedSourceDirectories).value, + scalafixTestkitInputClasspath := (`input-0_7` / Compile / fullClasspath).value + ).dependsOn(rules) + ) diff --git a/scalafix/input-0_7/src/main/scala/fix/MapBasedConverters.scala b/scalafix/input-0_7/src/main/scala/fix/MapBasedConverters.scala new file mode 100644 index 00000000..848164c0 --- /dev/null +++ b/scalafix/input-0_7/src/main/scala/fix/MapBasedConverters.scala @@ -0,0 +1,19 @@ +/* +rule = MapBasedConverters + */ +package fix + +import magnolify.tensorflow._ +import org.tensorflow.proto.example.Example + +object MapBasedConverters { + + final case class Person(name: String, age: Int) + + // ExampleType + val etPerson: ExampleType[Person] = ??? + val e: Example = ??? + val p: Person = ??? + etPerson.from(e) + etPerson.to(p) +} diff --git a/scalafix/output-0_7/src/main/scala/fix/MapBasedConverters.scala b/scalafix/output-0_7/src/main/scala/fix/MapBasedConverters.scala new file mode 100644 index 00000000..8098d428 --- /dev/null +++ b/scalafix/output-0_7/src/main/scala/fix/MapBasedConverters.scala @@ -0,0 +1,16 @@ +package fix + +import magnolify.tensorflow._ +import org.tensorflow.proto.example.Example + +object MapBasedConverters { + + final case class Person(name: String, age: Int) + + // ExampleType + val etPerson: ExampleType[Person] = ??? + val e: Example = ??? + val p: Person = ??? + etPerson(e) + etPerson(p) +} diff --git a/scalafix/project/ConfigAxis.scala b/scalafix/project/ConfigAxis.scala new file mode 100644 index 00000000..a957e041 --- /dev/null +++ b/scalafix/project/ConfigAxis.scala @@ -0,0 +1,2 @@ +import sbt._ +case class ConfigAxis(idSuffix: String, directorySuffix: String) extends VirtualAxis.WeakAxis {} diff --git a/scalafix/project/build.properties b/scalafix/project/build.properties new file mode 100644 index 00000000..abbbce5d --- /dev/null +++ b/scalafix/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.9.8 diff --git a/scalafix/project/plugins.sbt b/scalafix/project/plugins.sbt new file mode 100644 index 00000000..1d81f9dc --- /dev/null +++ b/scalafix/project/plugins.sbt @@ -0,0 +1,4 @@ +resolvers += Resolver.sonatypeRepo("releases") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.1") +addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.9.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") diff --git a/scalafix/rules/src/main/resources/META-INF/services/scalafix.v1.Rule b/scalafix/rules/src/main/resources/META-INF/services/scalafix.v1.Rule new file mode 100644 index 00000000..9e615557 --- /dev/null +++ b/scalafix/rules/src/main/resources/META-INF/services/scalafix.v1.Rule @@ -0,0 +1,2 @@ +fix.v0_7_0.MapBasedConverters + diff --git a/scalafix/rules/src/main/scala/fix/MapBasedConverters.scala b/scalafix/rules/src/main/scala/fix/MapBasedConverters.scala new file mode 100644 index 00000000..5dda8fd5 --- /dev/null +++ b/scalafix/rules/src/main/scala/fix/MapBasedConverters.scala @@ -0,0 +1,31 @@ +package fix.v0_7_0 + +import scalafix.v1._ + +import scala.meta._ + +object MapBasedConverters { + private val ExampleType: SymbolMatcher = + SymbolMatcher.normalized("magnolify/tensorflow/ExampleType") + + private val ConverterFn: SymbolMatcher = + SymbolMatcher.normalized("magnolify/shared/Converter#from") + + SymbolMatcher.normalized("magnolify/shared/Converter#to") +} + +class MapBasedConverters extends SemanticRule("MapBasedConverters") { + import MapBasedConverters._ + + def isExampleType(term: Term)(implicit doc: SemanticDocument): Boolean = + term.symbol.info.map(_.signature) match { + case Some(MethodSignature(_, _, TypeRef(_, sym, _))) => ExampleType.matches(sym) + case _ => false + } + + override def fix(implicit doc: SemanticDocument): Patch = + doc.tree.collect { + case t @ q"$qual.$fn(..$params)" if isExampleType(qual) && ConverterFn.matches(fn) => + Patch.replaceTree(t, q"$qual(..$params)".syntax) + }.asPatch + +} diff --git a/scalafix/tests/src/test/scala/fix/RuleSuite.scala b/scalafix/tests/src/test/scala/fix/RuleSuite.scala new file mode 100644 index 00000000..5ca361bc --- /dev/null +++ b/scalafix/tests/src/test/scala/fix/RuleSuite.scala @@ -0,0 +1,8 @@ +package fix + +import org.scalatest.funsuite.AnyFunSuiteLike +import scalafix.testkit.AbstractSemanticRuleSuite + +class RuleSuite extends AbstractSemanticRuleSuite with AnyFunSuiteLike { + runAllTests() +}