From 128e0779ab0ca6b9fd894c900da6cb4fb0b71482 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Fri, 24 Nov 2023 22:15:17 +0000 Subject: [PATCH 1/6] Upstream `ScalafixProject` --- build.sbt | 1 + .../org/typelevel/sbt/ScalafixProject.scala | 119 ++++++++++++++++++ .../typelevel/sbt/ScalafixProjectMacros.scala | 68 ++++++++++ .../sbt/TypelevelScalafixPlugin.scala | 4 + 4 files changed, 192 insertions(+) create mode 100644 scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala create mode 100644 scalafix/src/main/scala/org/typelevel/sbt/ScalafixProjectMacros.scala diff --git a/build.sbt b/build.sbt index 3720952a..7a74814c 100644 --- a/build.sbt +++ b/build.sbt @@ -151,6 +151,7 @@ lazy val scalafix = project name := "sbt-typelevel-scalafix", tlVersionIntroduced := Map("2.12" -> "0.4.10") ) + .dependsOn(noPublish) lazy val ciSigning = project .in(file("ci-signing")) diff --git a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala new file mode 100644 index 00000000..600b5442 --- /dev/null +++ b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala @@ -0,0 +1,119 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.sbt + +import sbt._ +import scalafix.sbt._ + +import Keys._ +import ScalafixTestkitPlugin.autoImport._ + +final class ScalafixProject private ( + val name: String, + val rules: Project, + val input: Project, + val output: Project, + val tests: Project +) extends CompositeProject { + + lazy val componentProjects = Seq(all, rules, input, output, tests) + + lazy val all = Project(name, file(s"target/$name-aggregate")) + .aggregate(rules, input, output, tests) + .enablePlugins(NoPublishPlugin) + + def rulesSettings(ss: Def.SettingsDefinition*): ScalafixProject = + rulesConfigure(_.settings(ss: _*)) + + def inputSettings(ss: Def.SettingsDefinition*): ScalafixProject = + inputConfigure(_.settings(ss: _*)) + + def outputSettings(ss: Def.SettingsDefinition*): ScalafixProject = + outputConfigure(_.settings(ss: _*)) + + def testsSettings(ss: Def.SettingsDefinition*): ScalafixProject = + testsConfigure(_.settings(ss: _*)) + + def rulesConfigure(transforms: (Project => Project)*): ScalafixProject = + new ScalafixProject( + name, + rules.configure(transforms: _*), + input, + output, + tests + ) + + def inputConfigure(transforms: (Project => Project)*): ScalafixProject = + new ScalafixProject( + name, + rules, + input.configure(transforms: _*), + output, + tests + ) + + def outputConfigure(transforms: (Project => Project)*): ScalafixProject = + new ScalafixProject( + name, + rules, + input, + output.configure(transforms: _*), + tests + ) + + def testsConfigure(transforms: (Project => Project)*): ScalafixProject = + new ScalafixProject( + name, + rules, + input, + output, + tests.configure(transforms: _*) + ) + +} + +object ScalafixProject { + def apply(name: String): ScalafixProject = { + + lazy val rules = Project(s"$name-rules", file(s"modules/$name/rules")).settings( + libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % _root_ + .scalafix + .sbt + .BuildInfo + .scalafixVersion + ) + + lazy val input = + Project(s"$name-input", file(s"modules/$name/input")).enablePlugins(NoPublishPlugin) + + lazy val output = + Project(s"$name-output", file(s"modules/$name/output")).enablePlugins(NoPublishPlugin) + + lazy val tests = Project(s"$name-tests", file(s"modules/$name/tests")) + .settings( + scalafixTestkitOutputSourceDirectories := (output / Compile / unmanagedSourceDirectories).value, + scalafixTestkitInputSourceDirectories := (input / Compile / unmanagedSourceDirectories).value, + scalafixTestkitInputClasspath := (input / Compile / fullClasspath).value, + scalafixTestkitInputScalacOptions := (input / Compile / scalacOptions).value, + scalafixTestkitInputScalaVersion := (input / Compile / scalaVersion).value + ) + .dependsOn(rules) + .enablePlugins(NoPublishPlugin, ScalafixTestkitPlugin) + + new ScalafixProject(name, rules, input, output, tests) + } +} diff --git a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProjectMacros.scala b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProjectMacros.scala new file mode 100644 index 00000000..a60f6187 --- /dev/null +++ b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProjectMacros.scala @@ -0,0 +1,68 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.sbt + +import scala.annotation.tailrec +import scala.reflect.macros.blackbox + +private[sbt] object ScalafixProjectMacros { + + // Copied from sbt.std.KeyMacro + def definingValName(c: blackbox.Context, invalidEnclosingTree: String => String): String = { + import c.universe.{Apply => ApplyTree, _} + val methodName = c.macroApplication.symbol.name + def processName(n: Name): String = + n.decodedName + .toString + .trim // trim is not strictly correct, but macros don't expose the API necessary + @tailrec def enclosingVal(trees: List[c.Tree]): String = { + trees match { + case ValDef(_, name, _, _) :: _ => processName(name) + case (_: ApplyTree | _: Select | _: TypeApply) :: xs => enclosingVal(xs) + // lazy val x: X = has this form for some reason (only when the explicit type is present, though) + case Block(_, _) :: DefDef(mods, name, _, _, _, _) :: _ if mods.hasFlag(Flag.LAZY) => + processName(name) + case _ => + c.error(c.enclosingPosition, invalidEnclosingTree(methodName.decodedName.toString)) + "" + } + } + enclosingVal(enclosingTrees(c).toList) + } + + // Copied from sbt.std.KeyMacro + def enclosingTrees(c: blackbox.Context): Seq[c.Tree] = + c.asInstanceOf[reflect.macros.runtime.Context] + .callsiteTyper + .context + .enclosingContextChain + .map(_.tree.asInstanceOf[c.Tree]) + + def scalafixProjectImpl(c: blackbox.Context): c.Expr[ScalafixProject] = { + import c.universe._ + + val enclosingValName = definingValName( + c, + methodName => + s"""$methodName must be directly assigned to a val, such as `val x = $methodName`. Alternatively, you can use `org.typelevel.sbt.ScalafixProject.apply`""" + ) + + val name = c.Expr[String](Literal(Constant(enclosingValName))) + + reify { ScalafixProject(name.splice) } + } +} diff --git a/scalafix/src/main/scala/org/typelevel/sbt/TypelevelScalafixPlugin.scala b/scalafix/src/main/scala/org/typelevel/sbt/TypelevelScalafixPlugin.scala index b5b794f2..7358e7cd 100644 --- a/scalafix/src/main/scala/org/typelevel/sbt/TypelevelScalafixPlugin.scala +++ b/scalafix/src/main/scala/org/typelevel/sbt/TypelevelScalafixPlugin.scala @@ -19,6 +19,8 @@ package org.typelevel.sbt import sbt._ import scalafix.sbt.ScalafixPlugin +import scala.language.experimental.macros + import Keys._ import ScalafixPlugin.autoImport._ @@ -32,6 +34,8 @@ object TypelevelScalafixPlugin extends AutoPlugin { val tlTypelevelScalafixVersion = settingKey[Option[String]]( "The version of typelevel-scalafix to add to the scalafix dependency classpath, or None to omit the dependency entirely." ) + + def tlScalafixProject: ScalafixProject = macro ScalafixProjectMacros.scalafixProjectImpl } import autoImport._ From 6f61ef75d50b5792c6a45b37786722ac029c533c Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Fri, 24 Nov 2023 22:49:22 +0000 Subject: [PATCH 2/6] Add `ScalafixProject#in` --- .../org/typelevel/sbt/ScalafixProject.scala | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala index 600b5442..83481941 100644 --- a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala +++ b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala @@ -32,7 +32,16 @@ final class ScalafixProject private ( lazy val componentProjects = Seq(all, rules, input, output, tests) - lazy val all = Project(name, file(s"target/$name-aggregate")) + def in(dir: File): ScalafixProject = + new ScalafixProject( + name, + rules.in(dir / "rules"), + input.in(dir / "input"), + output.in(dir / "output"), + tests.in(dir / "tests") + ) + + lazy val all = Project(name, file(s"$name-aggregate")) .aggregate(rules, input, output, tests) .enablePlugins(NoPublishPlugin) @@ -89,7 +98,7 @@ final class ScalafixProject private ( object ScalafixProject { def apply(name: String): ScalafixProject = { - lazy val rules = Project(s"$name-rules", file(s"modules/$name/rules")).settings( + lazy val rules = Project(s"$name-rules", file(s"$name/rules")).settings( libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % _root_ .scalafix .sbt @@ -98,12 +107,12 @@ object ScalafixProject { ) lazy val input = - Project(s"$name-input", file(s"modules/$name/input")).enablePlugins(NoPublishPlugin) + Project(s"$name-input", file(s"$name/input")).enablePlugins(NoPublishPlugin) lazy val output = - Project(s"$name-output", file(s"modules/$name/output")).enablePlugins(NoPublishPlugin) + Project(s"$name-output", file(s"$name/output")).enablePlugins(NoPublishPlugin) - lazy val tests = Project(s"$name-tests", file(s"modules/$name/tests")) + lazy val tests = Project(s"$name-tests", file(s"$name/tests")) .settings( scalafixTestkitOutputSourceDirectories := (output / Compile / unmanagedSourceDirectories).value, scalafixTestkitInputSourceDirectories := (input / Compile / unmanagedSourceDirectories).value, From db40ea303c2a8d6514ace90ddda45c87015c2b48 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Fri, 24 Nov 2023 23:40:20 +0000 Subject: [PATCH 3/6] Remove auto-generated aggregate --- .../main/scala/org/typelevel/sbt/ScalafixProject.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala index 83481941..ae1c7f41 100644 --- a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala +++ b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala @@ -27,10 +27,10 @@ final class ScalafixProject private ( val rules: Project, val input: Project, val output: Project, - val tests: Project + val tests: Project, ) extends CompositeProject { - lazy val componentProjects = Seq(all, rules, input, output, tests) + lazy val componentProjects = Seq(rules, input, output, tests) def in(dir: File): ScalafixProject = new ScalafixProject( @@ -41,10 +41,6 @@ final class ScalafixProject private ( tests.in(dir / "tests") ) - lazy val all = Project(name, file(s"$name-aggregate")) - .aggregate(rules, input, output, tests) - .enablePlugins(NoPublishPlugin) - def rulesSettings(ss: Def.SettingsDefinition*): ScalafixProject = rulesConfigure(_.settings(ss: _*)) From b8b9269eb3c2dd128ebac07ce7540aafc08ebcfb Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Fri, 24 Nov 2023 23:45:15 +0000 Subject: [PATCH 4/6] Formatting --- scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala index ae1c7f41..f478e651 100644 --- a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala +++ b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala @@ -27,7 +27,7 @@ final class ScalafixProject private ( val rules: Project, val input: Project, val output: Project, - val tests: Project, + val tests: Project ) extends CompositeProject { lazy val componentProjects = Seq(rules, input, output, tests) From 19977325e7a6499030ff65808ae1c342e38a7367 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 25 Nov 2023 02:27:05 +0000 Subject: [PATCH 5/6] Add `ScalafixProject#componentProjectReferences` --- scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala index f478e651..0059ad6c 100644 --- a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala +++ b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala @@ -32,6 +32,8 @@ final class ScalafixProject private ( lazy val componentProjects = Seq(rules, input, output, tests) + def componentProjectReferences = componentProjects.map(x => (x: ProjectReference)) + def in(dir: File): ScalafixProject = new ScalafixProject( name, From 594ba051f0d7dfdcc2dc07c18603e3733a9f27f1 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 25 Nov 2023 04:09:28 +0000 Subject: [PATCH 6/6] Formatting --- scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala index 0059ad6c..d54cc0b7 100644 --- a/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala +++ b/scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala @@ -32,7 +32,7 @@ final class ScalafixProject private ( lazy val componentProjects = Seq(rules, input, output, tests) - def componentProjectReferences = componentProjects.map(x => (x: ProjectReference)) + def componentProjectReferences = componentProjects.map(x => x: ProjectReference) def in(dir: File): ScalafixProject = new ScalafixProject(