Skip to content

Commit

Permalink
Merge pull request #668 from armanbilge/issue/304
Browse files Browse the repository at this point in the history
Upstream `ScalafixProject`
  • Loading branch information
armanbilge authored Dec 1, 2023
2 parents 0ccb1fe + 594ba05 commit 67dd6ee
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
126 changes: 126 additions & 0 deletions scalafix/src/main/scala/org/typelevel/sbt/ScalafixProject.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* 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(rules, input, output, tests)

def componentProjectReferences = componentProjects.map(x => x: ProjectReference)

def in(dir: File): ScalafixProject =
new ScalafixProject(
name,
rules.in(dir / "rules"),
input.in(dir / "input"),
output.in(dir / "output"),
tests.in(dir / "tests")
)

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"$name/rules")).settings(
libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % _root_
.scalafix
.sbt
.BuildInfo
.scalafixVersion
)

lazy val input =
Project(s"$name-input", file(s"$name/input")).enablePlugins(NoPublishPlugin)

lazy val output =
Project(s"$name-output", file(s"$name/output")).enablePlugins(NoPublishPlugin)

lazy val tests = Project(s"$name-tests", file(s"$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)
}
}
Original file line number Diff line number Diff line change
@@ -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 = <methodName> 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))
"<error>"
}
}
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) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package org.typelevel.sbt
import sbt._
import scalafix.sbt.ScalafixPlugin

import scala.language.experimental.macros

import Keys._
import ScalafixPlugin.autoImport._

Expand All @@ -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._
Expand Down

0 comments on commit 67dd6ee

Please sign in to comment.