diff --git a/build.sbt b/build.sbt index b1828ee..1ec9ff2 100644 --- a/build.sbt +++ b/build.sbt @@ -69,6 +69,35 @@ commonSettings publish / skip := true +lazy val resourceGenSettings = Def.settings( + Compile / resourceGenerators += Def.task { + val rules = (Compile / compile).value + .asInstanceOf[sbt.internal.inc.Analysis] + .apis + .internal + .collect { + case (className, analyzed) if analyzed.api.classApi.structure.parents.collect { case p: xsbti.api.Projection => + p.id + }.exists(Set("SyntacticRule", "SemanticRule")) => + className + } + .toList + .sorted + assert(rules.nonEmpty) + val output = (Compile / resourceManaged).value / "META-INF" / "services" / "scalafix.v1.Rule" + IO.writeLines(output, rules) + Seq(output) + }.taskValue, +) + +lazy val myRuleRule = project.settings( + commonSettings, + scalaVersion := V.scala212, + libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % V.scalafixVersion, + resourceGenSettings, + publish / skip := true +) + lazy val rules = projectMatrix .settings( commonSettings, @@ -89,24 +118,7 @@ lazy val rules = projectMatrix Nil } }, - Compile / resourceGenerators += Def.task { - val rules = (Compile / compile).value - .asInstanceOf[sbt.internal.inc.Analysis] - .apis - .internal - .collect { - case (className, analyzed) if analyzed.api.classApi.structure.parents.collect { - case p: xsbti.api.Projection => p.id - }.exists(Set("SyntacticRule", "SemanticRule")) => - className - } - .toList - .sorted - assert(rules.nonEmpty) - val output = (Compile / resourceManaged).value / "META-INF" / "services" / "scalafix.v1.Rule" - IO.writeLines(output, rules) - Seq(output) - }.taskValue, + resourceGenSettings, ) .defaultAxes(VirtualAxis.jvm) .jvmPlatform(rulesCrossVersions) @@ -114,8 +126,11 @@ lazy val rules = projectMatrix lazy val rules212 = rules .jvm(V.scala212) .enablePlugins(ScriptedPlugin) + .enablePlugins(ScalafixPlugin) + .dependsOn(myRuleRule % ScalafixConfig) .settings( Test / test := (Test / test).dependsOn(scripted.toTask("")).value, + Compile / compile := (Compile / compile).dependsOn((Compile / scalafix).toTask(" MyScalafixRuleRule")).value, scriptedBufferLog := false, scriptedLaunchOpts += ("-Dscalafix-rules.version=" + version.value), scriptedLaunchOpts += ("-Dscalafix.version=" + _root_.scalafix.sbt.BuildInfo.scalafixVersion), diff --git a/myRuleRule/src/main/scala/fix/MyScalafixRuleRule.scala b/myRuleRule/src/main/scala/fix/MyScalafixRuleRule.scala new file mode 100644 index 0000000..3c79c1d --- /dev/null +++ b/myRuleRule/src/main/scala/fix/MyScalafixRuleRule.scala @@ -0,0 +1,93 @@ +package fix + +import scala.meta.Defn +import scala.meta.Init +import scala.meta.Lit +import scala.meta.Mod +import scala.meta.Template +import scala.meta.Term +import scala.meta.Type +import scalafix.Patch +import scalafix.lint.Diagnostic +import scalafix.lint.LintSeverity +import scalafix.v1.SyntacticDocument +import scalafix.v1.SyntacticRule + +class MyScalafixRuleRule extends SyntacticRule("MyScalafixRuleRule") { + private[this] def when(cond: Boolean)(patch: => Patch): Patch = { + if (cond) { + patch + } else { + Patch.empty + } + } + + override def fix(implicit doc: SyntacticDocument): Patch = { + doc.tree.collect { + case Defn.Class.After_4_6_0( + _, + className, + _, + primaryCtor, + Template.After_4_4_0( + _, + List( + Init.After_4_6_0( + Type.Name("SyntacticRule" | "SemanticRule"), + _, + List(Term.ArgClause(Lit.String(ruleName) :: Nil, _)) + ) + ), + _, + stats, + _ + ) + ) => + val withConfigurationMethodOpt = + stats.collectFirst { + case d: Defn.Def if d.name.value == "withConfiguration" && d.mods.exists(_.is[Mod.Override]) => + d + } + Seq( + when(className.value != ruleName) { + Patch.lint( + Diagnostic( + id = "", + message = s"${className} != ${ruleName}", + position = className.pos, + severity = LintSeverity.Error + ) + ) + }, + when(primaryCtor.paramClauses.nonEmpty) { + withConfigurationMethodOpt match { + case Some(withConfigurationMethod) => + when( + withConfigurationMethod.body.collect { + case Lit.String(x) if x == ruleName => () + }.isEmpty + ) { + Patch.lint( + Diagnostic( + id = "", + message = "maybe incorrect `withConfiguration` method", + position = className.pos, + severity = LintSeverity.Error + ) + ) + } + case None => + Patch.lint( + Diagnostic( + id = "", + message = "there is primary constructor args but not defined `withConfiguration` method", + position = className.pos, + severity = LintSeverity.Error + ) + ) + } + } + ).asPatch + }.asPatch + } +}