Repository: scala-steward-org/scala-steward
Pull request #3517 (2025-01-04 19:12:51)

Remove circe-generic-extras
This concludes the work started in #3514 and #3515 to remove the dependency on circe-generic-extras.

Here are some notes about the changes:
- `Resolver` cases had non-optional fields with defaults that needed to be made optional so that the derived decoder does not fail if those are not provided
- `ArtifactChange` and `ArtifactChanges` could have always used `deriveDecoder` since the case classes have no defaults
- `ScalafixMigrations` had a default value but a `scalafix-migrations.conf` without a `migrations` field makes no sense to me and it should be the same as `ArtifactChanges` anyway


Patch:
diff --git a/build.sbt b/build.sbt
index 52281f58..a0851c47 100644
--- a/build.sbt
+++ b/build.sbt
@@ -128,7 +128,6 @@ lazy val core = myCrossProject("core")
       Dependencies.catsParse,
       Dependencies.circeConfig,
       Dependencies.circeGeneric,
-      Dependencies.circeGenericExtras,
       Dependencies.circeParser,
       Dependencies.circeRefined,
       Dependencies.commonsIo,
diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala b/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala
index c7eb1d55..c174b029 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala
@@ -320,7 +320,7 @@ object Cli {
   private val defaultMavenRepo: Opts[Resolver] = {
     val default = Resolver.mavenCentral
     option[String]("default-maven-repo", s"default: ${default.location}")
-      .map(location => Resolver.MavenRepository("default", location, None, Nil))
+      .map(location => Resolver.MavenRepository("default", location, None, None))
       .withDefault(default)
   }
 
diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/maven/parser.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/maven/parser.scala
index 2925ad64..d95ce1cc 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/buildtool/maven/parser.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/maven/parser.scala
@@ -72,7 +72,7 @@ object parser {
       id <- stringNoSpace
       _ <- wsp.rep0 ~ Parser.string("url:") ~ wsp
       url <- stringNoSpace
-    } yield Resolver.MavenRepository(id, url, None, Nil)
+    } yield Resolver.MavenRepository(id, url, None, None)
 
   def parseResolvers(input: List[String]): List[Resolver] =
     input.mkString.split("""\[INFO]""").toList.flatMap { line =>
diff --git a/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/parser.scala b/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/parser.scala
index 0932884b..b06dbe9c 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/parser.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/buildtool/mill/parser.scala
@@ -54,7 +54,7 @@ object parser {
     *
     * @param artifactName
     *   name of the artifact parsed from the build file
-    * @param millVerion
+    * @param millVersion
     *   the current Mill version being used
     * @return
     *   the newly put together ArtifactId
@@ -127,13 +127,13 @@ object MillModule {
           url <- c.downField("url").as[String]
           creds <- c.downField("auth").as[Option[Resolver.Credentials]]
           headers <- c.downField("headers").as[Option[List[Resolver.Header]]]
-        } yield Resolver.MavenRepository(url, url, creds, headers.getOrElse(Nil))
+        } yield Resolver.MavenRepository(url, url, creds, headers)
       case "ivy" =>
         for {
           url <- c.downField("pattern").as[String]
           creds <- c.downField("auth").as[Option[Resolver.Credentials]]
           headers <- c.downField("headers").as[Option[List[Resolver.Header]]]
-        } yield Resolver.IvyRepository(url, url, creds, headers.getOrElse(Nil))
+        } yield Resolver.IvyRepository(url, url, creds, headers)
       case typ => Left(DecodingFailure(s"Not a matching resolver type, $typ", c.history))
     }
   }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/coursier/CoursierAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/coursier/CoursierAlg.scala
index 9f8913f5..d768e80d 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/coursier/CoursierAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/coursier/CoursierAlg.scala
@@ -123,10 +123,10 @@ object CoursierAlg {
   private def toCoursierRepository(resolver: Resolver): Either[String, coursier.Repository] =
     resolver match {
       case Resolver.MavenRepository(_, location, creds, headers) =>
-        val authentication = toCoursierAuthentication(creds, headers)
+        val authentication = toCoursierAuthentication(creds, headers.getOrElse(Nil))
         Right(coursier.maven.SbtMavenRepository.apply(location, authentication))
       case Resolver.IvyRepository(_, pattern, creds, headers) =>
-        val authentication = toCoursierAuthentication(creds, headers)
+        val authentication = toCoursierAuthentication(creds, headers.getOrElse(Nil))
         coursier.ivy.IvyRepository.parse(pattern, authentication = authentication)
     }
 
diff --git a/modules/core/src/main/scala/org/scalasteward/core/data/Resolver.scala b/modules/core/src/main/scala/org/scalasteward/core/data/Resolver.scala
index 0944815b..ec1ecd8c 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/data/Resolver.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/data/Resolver.scala
@@ -18,8 +18,6 @@ package org.scalasteward.core.data
 
 import cats.Order
 import io.circe.Codec
-import io.circe.generic.extras.Configuration
-import io.circe.generic.extras.semiauto.deriveConfiguredCodec
 import io.circe.generic.semiauto._
 import org.scalasteward.core.data.Resolver._
 
@@ -41,28 +39,27 @@ object Resolver {
   }
   final case class Header(key: String, value: String)
   object Header {
-    implicit val headerCodec: Codec[Header] = deriveCodec
+    implicit val headerCodec: Codec[Header] =
+      deriveCodec
   }
   final case class MavenRepository(
       name: String,
       location: String,
       credentials: Option[Credentials],
-      headers: List[Header] = Nil
+      headers: Option[List[Header]]
   ) extends Resolver
   final case class IvyRepository(
       name: String,
       pattern: String,
       credentials: Option[Credentials],
-      headers: List[Header] = Nil
+      headers: Option[List[Header]]
   ) extends Resolver
 
   val mavenCentral: MavenRepository =
-    MavenRepository("public", "https://repo1.maven.org/maven2/", None, Nil)
+    MavenRepository("public", "https://repo1.maven.org/maven2/", None, None)
 
-  implicit val resolverCodec: Codec[Resolver] = {
-    implicit val customConfig: Configuration = Configuration.default.withDefaults
-    deriveConfiguredCodec
-  }
+  implicit val resolverCodec: Codec[Resolver] =
+    deriveCodec
 
   implicit val resolverOrder: Order[Resolver] =
     Order.by {
diff --git a/modules/core/src/main/scala/org/scalasteward/core/edit/scalafix/ScalafixMigrations.scala b/modules/core/src/main/scala/org/scalasteward/core/edit/scalafix/ScalafixMigrations.scala
index cb9fa4e1..ce24e5c7 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/edit/scalafix/ScalafixMigrations.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/edit/scalafix/ScalafixMigrations.scala
@@ -17,17 +17,11 @@
 package org.scalasteward.core.edit.scalafix
 
 import io.circe.Decoder
-import io.circe.generic.extras.Configuration
-import io.circe.generic.extras.semiauto._
+import io.circe.generic.semiauto._
 
-final case class ScalafixMigrations(
-    migrations: List[ScalafixMigration] = List.empty
-)
+final case class ScalafixMigrations(migrations: List[ScalafixMigration])
 
 object ScalafixMigrations {
-  implicit val configuration: Configuration =
-    Configuration.default.withDefaults
-
   implicit val scalafixMigrationsDecoder: Decoder[ScalafixMigrations] =
-    deriveConfiguredDecoder
+    deriveDecoder
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/update/artifact/ArtifactChange.scala b/modules/core/src/main/scala/org/scalasteward/core/update/artifact/ArtifactChange.scala
index 4b886e5c..54c502a2 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/update/artifact/ArtifactChange.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/update/artifact/ArtifactChange.scala
@@ -17,7 +17,7 @@
 package org.scalasteward.core.update.artifact
 
 import io.circe.Decoder
-import io.circe.generic.extras.{semiauto, Configuration}
+import io.circe.generic.semiauto
 import org.scalasteward.core.data.GroupId
 
 final case class ArtifactChange(
@@ -28,12 +28,9 @@ final case class ArtifactChange(
 )
 
 object ArtifactChange {
-  implicit val configuration: Configuration =
-    Configuration.default.withDefaults
-
-  implicit val decoder: Decoder[ArtifactChange] =
+  implicit val artifactChangeDecoder: Decoder[ArtifactChange] =
     semiauto
-      .deriveConfiguredDecoder[ArtifactChange]
+      .deriveDecoder[ArtifactChange]
       .ensure(
         change => change.groupIdBefore.isDefined || change.artifactIdBefore.isDefined,
         "At least one of groupIdBefore and/or artifactIdBefore must be set"
diff --git a/modules/core/src/main/scala/org/scalasteward/core/update/artifact/ArtifactChanges.scala b/modules/core/src/main/scala/org/scalasteward/core/update/artifact/ArtifactChanges.scala
index b258ba7c..03680296 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/update/artifact/ArtifactChanges.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/update/artifact/ArtifactChanges.scala
@@ -17,14 +17,11 @@
 package org.scalasteward.core.update.artifact
 
 import io.circe.Decoder
-import io.circe.generic.extras.{semiauto, Configuration}
+import io.circe.generic.semiauto._
 
 final case class ArtifactChanges(changes: List[ArtifactChange])
 
 object ArtifactChanges {
-  implicit val configuration: Configuration =
-    Configuration.default.withDefaults
-
   implicit val artifactChangesDecoder: Decoder[ArtifactChanges] =
-    semiauto.deriveConfiguredDecoder
+    deriveDecoder
 }
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 844ad305..2aaa0e67 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -9,7 +9,6 @@ object Dependencies {
   val catsParse = "org.typelevel" %% "cats-parse" % "1.1.0"
   val circeConfig = "io.circe" %% "circe-config" % "0.10.1"
   val circeGeneric = "io.circe" %% "circe-generic" % "0.14.10"
-  val circeGenericExtras = "io.circe" %% "circe-generic-extras" % "0.14.4"
   val circeLiteral = "io.circe" %% "circe-literal" % circeGeneric.revision
   val circeParser = "io.circe" %% "circe-parser" % circeGeneric.revision
   val circeRefined = "io.circe" %% "circe-refined" % "0.15.1"


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/TestSyntax.scala b/modules/core/src/test/scala/org/scalasteward/core/TestSyntax.scala
index 12583075..d8718acf 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/TestSyntax.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/TestSyntax.scala
@@ -7,7 +7,7 @@ import org.scalasteward.core.util.Nel
 object TestSyntax {
   val sbtPluginReleases: IvyRepository = {
     val pattern = "https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/[defaultPattern]"
-    IvyRepository("sbt-plugin-releases", pattern, None)
+    IvyRepository("sbt-plugin-releases", pattern, None, None)
   }
 
   implicit class GenericOps[A](val self: A) extends AnyVal {
diff --git a/modules/core/src/test/scala/org/scalasteward/core/buildtool/maven/parserTest.scala b/modules/core/src/test/scala/org/scalasteward/core/buildtool/maven/parserTest.scala
index 797579d8..a2ffcbaa 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/buildtool/maven/parserTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/buildtool/maven/parserTest.scala
@@ -64,10 +64,10 @@ class parserTest extends FunSuite {
         "sonatype-nexus-snapshots",
         "https://oss.sonatype.org/content/repositories/snapshots",
         None,
-        Nil
+        None
       ),
-      MavenRepository("bintrayakkamaven", "https://dl.bintray.com/akka/maven/", None, Nil),
-      MavenRepository("apache.snapshots", "http://repository.apache.org/snapshots", None, Nil)
+      MavenRepository("bintrayakkamaven", "https://dl.bintray.com/akka/maven/", None, None),
+      MavenRepository("apache.snapshots", "http://repository.apache.org/snapshots", None, None)
     )
     assertEquals(resolvers, expected)
   }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/buildtool/mill/MillDepParserTest.scala b/modules/core/src/test/scala/org/scalasteward/core/buildtool/mill/MillDepParserTest.scala
index 4afae061..cd7c4f33 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/buildtool/mill/MillDepParserTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/buildtool/mill/MillDepParserTest.scala
@@ -2,6 +2,7 @@ package org.scalasteward.core.buildtool.mill
 
 import munit.FunSuite
 import org.scalasteward.core.TestSyntax._
+import org.scalasteward.core.data.Resolver
 
 class MillDepParserTest extends FunSuite {
   test("parse dependencies from https://github.com/lihaoyi/requests-scala") {
@@ -137,4 +138,13 @@ class MillDepParserTest extends FunSuite {
 
     assertEquals(result.find(_.name == "requests[2.13.0]").map(_.dependencies), Some(dep13))
   }
+
+  test("parse an IvyRepository") {
+    val pattern =
+      "https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/[organisation]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)([branch]/)[revision]/[type]s/[artifact](-[classifier]).[ext]"
+    val json = s""" { "pattern": "$pattern", "type": "ivy", "headers": [] } """
+    val obtained = io.circe.parser.decode(json)(MillModule.resolverDecoder)
+    val expected = Right(Resolver.IvyRepository(pattern, pattern, None, Some(Nil)))
+    assertEquals(obtained, expected)
+  }
 }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/parserTest.scala b/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/parserTest.scala
index 8d19646f..f35b0b9e 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/parserTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/buildtool/sbt/parserTest.scala
@@ -28,7 +28,7 @@ class parserTest extends FunSuite {
          |[info] { "groupId": "com.dwijnand", "artifactId": { "name": "sbt-travisci", "maybeCrossName": null }, "version": "1.1.3",  "sbtVersion": "1.0" }
          |[info] { "groupId": "com.eed3si9n", "artifactId": { "name": "sbt-assembly", "maybeCrossName": null }, "version": "0.14.8", "sbtVersion": "1.0", "configurations": "foo" }
          |{ "groupId": "org.scalameta", "artifactId": { "name": "sbt-scalafmt", "maybeCrossName": null }, "version": "2.4.6", "sbtVersion": "1.0", "scalaVersion": "2.12", "configurations": null }
-         |[info] { "IvyRepository" : { "name": "sbt-plugin-releases", "pattern": "https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/[organisation]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)([branch]/)[revision]/[type]s/[artifact](-[classifier]).[ext]", "headers": [] } }
+         |[info] { "IvyRepository" : { "name": "sbt-plugin-releases", "pattern": "https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/[organisation]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)([branch]/)[revision]/[type]s/[artifact](-[classifier]).[ext]" } }
          |[info] { "IvyRepository" : { "name": "sbt-plugin-releases-with-creds", "pattern": "https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/[organisation]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)([branch]/)[revision]/[type]s/[artifact](-[classifier]).[ext]", "credentials": { "user": "tony", "pass": "m0ntana" }, "headers": [] } }
          |[info] --- snip ---
          |""".stripMargin.linesIterator.toList
@@ -45,19 +45,19 @@ class parserTest extends FunSuite {
             "bintray-ovotech-maven",
             "https://dl.bintray.com/ovotech/maven/",
             None,
-            Nil
+            Some(Nil)
           ),
           MavenRepository(
             "confluent-release",
             "http://packages.confluent.io/maven/",
             Some(Credentials("donny", "brasc0")),
-            Nil
+            Some(Nil)
           ),
           MavenRepository(
             "gitlab-internal",
             "http://gitlab.example.com/maven/",
             None,
-            List(Resolver.Header("private-token", "token123"))
+            Some(List(Resolver.Header("private-token", "token123")))
           )
         )
       ),
@@ -76,13 +76,13 @@ class parserTest extends FunSuite {
             "sbt-plugin-releases",
             "https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/[organisation]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)([branch]/)[revision]/[type]s/[artifact](-[classifier]).[ext]",
             None,
-            Nil
+            None
           ),
           IvyRepository(
             "sbt-plugin-releases-with-creds",
             "https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/[organisation]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)([branch]/)[revision]/[type]s/[artifact](-[classifier]).[ext]",
             Some(Credentials("tony", "m0ntana")),
-            Nil
+            Some(Nil)
           )
         )
       )
diff --git a/modules/core/src/test/scala/org/scalasteward/core/coursier/CoursierAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/coursier/CoursierAlgTest.scala
index b6e516f0..ed981d97 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/coursier/CoursierAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/coursier/CoursierAlgTest.scala
@@ -103,7 +103,7 @@ class CoursierAlgTest extends CatsEffectSuite {
   test("getMetadata: resolver with headers") {
     val dep = "org.typelevel".g % ("cats-effect", "cats-effect_2.12").a % "1.0.0"
     val resolvers =
-      List(Resolver.mavenCentral.copy(headers = List(Resolver.Header("X-Foo", "bar"))))
+      List(Resolver.mavenCentral.copy(headers = Some(List(Resolver.Header("X-Foo", "bar")))))
     val obtained =
       coursierAlg.getMetadata(dep, resolvers).runA(MockState.empty).map(_.repoUrl.isDefined)
     assertIOBoolean(obtained)
diff --git a/modules/core/src/test/scala/org/scalasteward/core/update/PruningAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/update/PruningAlgTest.scala
index 5b5e2066..1e69473a 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/update/PruningAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/update/PruningAlgTest.scala
@@ -191,7 +191,7 @@ class PruningAlgTest extends FunSuite {
             DependencyInfo("org.scala-lang".g % "scala-library".a % "2.12.14", List("build.sbt")),
             DependencyInfo("org.scala-lang".g % "scala-library".a % "2.13.5", List("build.sbt"))
           ),
-          List(MavenRepository("public", "https://repo5.org/maven/", None, Nil))
+          List(MavenRepository("public", "https://repo5.org/maven/", None, None))
         )
       )
     )