All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fix.CirceCodec.scala Maven / Gradle / Ivy

The newest version!
package fix

import scala.meta.Defn
import scala.meta.Import
import scala.meta.Importee
import scala.meta.Importer
import scala.meta.Init
import scala.meta.Lit
import scala.meta.Mod.Annot
import scala.meta.Name.Indeterminate
import scala.meta.Pkg
import scala.meta.Source
import scala.meta.Term
import scala.meta.Term.Select
import scala.meta.Type
import scala.meta.XtensionCollectionLikeUI
import scalafix.Patch
import scalafix.v1.SyntacticDocument
import scalafix.v1.SyntacticRule
import scalafix.v1.XtensionSeqPatch

object CirceCodec {
  private sealed abstract class TypeClass extends Product with Serializable
  private case object Both extends TypeClass
  private case object Encoder extends TypeClass
  private case object Decoder extends TypeClass

  private object TypeClass {
    def unapply(args: List[List[Term]]): Option[CirceCodec.TypeClass] =
      PartialFunction.condOpt(args) {
        case Nil =>
          CirceCodec.Both
        case List(Term.Assign(Term.Name("encodeOnly"), Lit.Boolean(true)) :: Nil) =>
          CirceCodec.Encoder
        case List(Term.Assign(Term.Name("decodeOnly"), Lit.Boolean(true)) :: Nil) =>
          CirceCodec.Decoder
      }
  }
}

class CirceCodec extends SyntacticRule("CirceCodec") {
  override def fix(implicit doc: SyntacticDocument): Patch = {
    doc.tree.collect { case src: Source =>
      val result = src.collect { case clazz: Defn.Class =>
        clazz.mods.collect {
          case annotation @ Annot(Init.Initial(Type.Name("JsonCodec"), _, CirceCodec.TypeClass(t))) =>
            val objectOpt = src.collect {
              case obj: Defn.Object if obj.name.value == clazz.name.value => obj
            }.headOption

            val className = clazz.name.value

            val instance = "\n\n  implicit " + {
              clazz.tparamClause.values match {
                case Nil =>
                  t match {
                    case CirceCodec.Both =>
                      s"val codec: Codec.AsObject[${className}] = deriveCodec[${className}]"
                    case CirceCodec.Encoder =>
                      s"val encoder: Encoder.AsObject[${className}] = deriveEncoder[${className}]"
                    case CirceCodec.Decoder =>
                      s"val decoder: Decoder[${className}] = deriveDecoder[${className}]"
                  }
                case tparams =>
                  val nameWithTypeParams = className + "[" + tparams.map(_.name).mkString(", ") + "]"
                  def params(t: String) = tparams.map(a => s"${a.name}: $t[${a.name}]").mkString(", ")
                  t match {
                    case CirceCodec.Both =>
                      s"def codec[${tparams.mkString(", ")}](implicit ${params("Codec")}): Codec.AsObject[${nameWithTypeParams}] = deriveCodec[$nameWithTypeParams]"
                    case CirceCodec.Encoder =>
                      s"def encoder[${tparams.mkString(", ")}](implicit ${params("Encoder")}): Encoder.AsObject[${nameWithTypeParams}] = deriveEncoder[${nameWithTypeParams}]"
                    case CirceCodec.Decoder =>
                      s"def decoder[${tparams.mkString(", ")}](implicit ${params("Decoder")}): Decoder[${nameWithTypeParams}] = deriveDecoder[${nameWithTypeParams}]"
                  }
              }
            } + "\n"

            List(
              (
                t,
                Seq(
                  Patch.removeTokens(annotation.tokens),
                  objectOpt match {
                    case Some(obj) =>
                      Patch.addRight(obj.templ.body.stats.last, instance)
                    case None =>
                      Patch.addRight(clazz, s"\n\nobject ${className} {${instance}\n}")
                  }
                ).asPatch
              )
            )
          case _ =>
            Nil
        }.flatten
      }.flatten

      if (result.nonEmpty) {
        val pkg = src.collect { case p: Pkg => p.body.stats.head }.head

        val annotationImport = src.collect {
          case i @ Import(
                Importer(
                  Select(Select(Term.Name("io"), Term.Name("circe")), Term.Name("generic")),
                  List(Importee.Name(Indeterminate("JsonCodec")))
                )
                :: Nil
              ) =>
            Patch.removeTokens(i.tokens)
        }.asPatch

        val imports = {
          val types = result.map(_._1).toSet
          List(
            if (types(CirceCodec.Encoder)) {
              List("Encoder", "generic.semiauto.deriveEncoder")
            } else {
              Nil
            },
            if (types(CirceCodec.Decoder)) {
              List("Decoder", "generic.semiauto.deriveDecoder")
            } else {
              Nil
            },
            if (types(CirceCodec.Both)) {
              List("Codec", "generic.semiauto.deriveCodec")
            } else {
              Nil
            },
          ).flatten.map("import io.circe." + _).sorted.mkString("\n", "\n", "\n")
        }

        Seq(
          annotationImport,
          Patch.addLeft(pkg, imports),
          result.map(_._2).asPatch
        ).asPatch
      } else {
        Patch.empty
      }
    }.asPatch

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy