Repository: scala-steward-org/scala-steward
Pull request #2427 (2021-12-24 06:32:14)

Change return type of `PruningAlg#needsAttention`
Instead of `(Boolean, List[Update.Single])` where the first element
designate if the second is empty, we use
`Option[Nel[UpdateState.WithUpdate]]` instead. This is a little bit
nicer since we don't need to interpret the `Boolean` but just traverse
the `Option`.

Having the `UpdateState`s available in `StewardAlg` will probably also
come in handy when dealing with #2355. I imagine that some kind of rate
limiter in `StewardAlg` will solve that issue.


Patch:
diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala
index 97d6b0ed..4f294f95 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/application/StewardAlg.scala
@@ -80,8 +80,8 @@ final class StewardAlg[F[_]](config: Config)(implicit
       logger.attemptError.label(util.string.lineLeftRight(label), Some(label)) {
         F.guarantee(
           repoCacheAlg.checkCache(repo).flatMap { case (data, fork) =>
-            pruningAlg.needsAttention(data).flatMap { case (attentionNeeded, updates) =>
-              F.whenA(attentionNeeded)(nurtureAlg.nurture(data, fork, updates))
+            pruningAlg.needsAttention(data).flatMap {
+              _.traverse_(states => nurtureAlg.nurture(data, fork, states.map(_.update)))
             }
           },
           gitAlg.removeClone(repo)
diff --git a/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
index dce31969..f264041e 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
@@ -26,7 +26,7 @@ import org.scalasteward.core.data._
 import org.scalasteward.core.edit.{EditAlg, EditAttempt}
 import org.scalasteward.core.git.{Branch, Commit, GitAlg}
 import org.scalasteward.core.repoconfig.PullRequestUpdateStrategy
-import org.scalasteward.core.util.UrlChecker
+import org.scalasteward.core.util.{Nel, UrlChecker}
 import org.scalasteward.core.util.logger.LoggerOps
 import org.scalasteward.core.vcs.data._
 import org.scalasteward.core.vcs.{VCSApiAlg, VCSExtraAlg, VCSRepoAlg}
@@ -45,11 +45,11 @@ final class NurtureAlg[F[_]](config: VCSCfg)(implicit
     urlChecker: UrlChecker[F],
     F: Concurrent[F]
 ) {
-  def nurture(data: RepoData, fork: RepoOut, updates: List[Update.Single]): F[Unit] =
+  def nurture(data: RepoData, fork: RepoOut, updates: Nel[Update.Single]): F[Unit] =
     for {
       _ <- logger.info(s"Nurture ${data.repo.show}")
       baseBranch <- cloneAndSync(data.repo, fork)
-      _ <- updateDependencies(data, fork.repo, baseBranch, Update.groupByGroupId(updates))
+      _ <- updateDependencies(data, fork.repo, baseBranch, Update.groupByGroupId(updates.toList))
     } yield ()
 
   private def cloneAndSync(repo: Repo, fork: RepoOut): F[Branch] =
diff --git a/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala
index 3e5bced1..e34af494 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala
@@ -27,7 +27,7 @@ import org.scalasteward.core.update.PruningAlg._
 import org.scalasteward.core.update.data.UpdateState
 import org.scalasteward.core.update.data.UpdateState._
 import org.scalasteward.core.util
-import org.scalasteward.core.util.{dateTime, DateTimeAlg}
+import org.scalasteward.core.util.{dateTime, DateTimeAlg, Nel}
 import org.scalasteward.core.vcs.data.Repo
 import org.typelevel.log4cats.Logger
 import scala.concurrent.duration._
@@ -39,7 +39,7 @@ final class PruningAlg[F[_]](implicit
     updateAlg: UpdateAlg[F],
     F: Monad[F]
 ) {
-  def needsAttention(data: RepoData): F[(Boolean, List[Update.Single])] = {
+  def needsAttention(data: RepoData): F[Option[Nel[WithUpdate]]] = {
     val dependencies = data.cache.dependencyInfos
       .flatMap(_.sequence)
       .collect { case info if !ignoreDependency(info.value) => info.map(_.dependency) }
@@ -50,7 +50,7 @@ final class PruningAlg[F[_]](implicit
   private def findUpdatesNeedingAttention(
       data: RepoData,
       dependencies: List[Scope.Dependency]
-  ): F[(Boolean, List[Update.Single])] = {
+  ): F[Option[Nel[WithUpdate]]] = {
     val repo = data.repo
     val repoCache = data.cache
     val repoConfig = data.config
@@ -73,7 +73,7 @@ final class PruningAlg[F[_]](implicit
       }
       (updateStates1, updates1) = res
       _ <- logger.info(util.logger.showUpdates(updates1.widen[Update]))
-      result <- checkUpdateStates(repo, repoConfig, updateStates1)
+      result <- filterUpdateStates(repo, repoConfig, updateStates1)
     } yield result
   }
 
@@ -124,23 +124,22 @@ final class PruningAlg[F[_]](implicit
         }
     }
 
-  private def checkUpdateStates(
+  private def filterUpdateStates(
       repo: Repo,
       repoConfig: RepoConfig,
       updateStates: List[UpdateState]
-  ): F[(Boolean, List[Update.Single])] =
+  ): F[Option[Nel[WithUpdate]]] =
     newPullRequestsAllowed(repo, repoConfig.pullRequests.frequencyOrDefault).flatMap { allowed =>
-      val (outdatedStates, updates) = updateStates.collect {
-        case s: DependencyOutdated if allowed => (s, s.update)
-        case s: PullRequestOutdated           => (s, s.update)
-      }.separate
-      val isOutdated = outdatedStates.nonEmpty
-      val message = if (isOutdated) {
-        val states = util.string.indentLines(outdatedStates.map(UpdateState.show).sorted)
-        s"${repo.show} is outdated:\n" + states
-      } else
-        s"${repo.show} is up-to-date"
-      logger.info(message).as((isOutdated, updates))
+      Nel.fromList(updateStates.collect {
+        case s: DependencyOutdated if allowed => s
+        case s: PullRequestOutdated           => s
+      }) match {
+        case some @ Some(states) =>
+          val lines = util.string.indentLines(states.map(UpdateState.show).sorted)
+          logger.info(s"${repo.show} is outdated:\n" + lines).as(some)
+        case None =>
+          logger.info(s"${repo.show} is up-to-date").as(None)
+      }
     }
 
   private def newPullRequestsAllowed(repo: Repo, frequency: PullRequestFrequency): F[Boolean] =


Test patch:
diff --git a/modules/core/src/main/scala/org/scalasteward/core/update/data/UpdateState.scala b/modules/core/src/main/scala/org/scalasteward/core/update/data/UpdateState.scala
index 45a00cf8..ca00f64f 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/update/data/UpdateState.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/update/data/UpdateState.scala
@@ -24,6 +24,14 @@ sealed trait UpdateState extends Product with Serializable {
 }
 
 object UpdateState {
+  sealed trait WithUpdate extends UpdateState {
+    def update: Update.Single
+  }
+
+  sealed trait WithPullRequest extends WithUpdate {
+    def pullRequest: Uri
+  }
+
   final case class DependencyUpToDate(
       crossDependency: CrossDependency
   ) extends UpdateState
@@ -31,25 +39,25 @@ object UpdateState {
   final case class DependencyOutdated(
       crossDependency: CrossDependency,
       update: Update.Single
-  ) extends UpdateState
+  ) extends WithUpdate
 
   final case class PullRequestUpToDate(
       crossDependency: CrossDependency,
       update: Update.Single,
       pullRequest: Uri
-  ) extends UpdateState
+  ) extends WithPullRequest
 
   final case class PullRequestOutdated(
       crossDependency: CrossDependency,
       update: Update.Single,
       pullRequest: Uri
-  ) extends UpdateState
+  ) extends WithPullRequest
 
   final case class PullRequestClosed(
       crossDependency: CrossDependency,
       update: Update.Single,
       pullRequest: Uri
-  ) extends UpdateState
+  ) extends WithPullRequest
 
   def show(updateState: UpdateState): String = {
     val groupId = updateState.crossDependency.head.groupId
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 de57a8cf..0fe6989c 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
@@ -208,8 +208,7 @@ class PruningAlgTest extends FunSuite {
     (for {
       initial <- MockState.empty.addFiles(versionsFile -> versionsContent)
       // This should not propose an update from 2.12.13 to 2.13.5.
-      res <- pruningAlg.needsAttention(data).runA(initial)
-      (_, updates) = res
-    } yield assertEquals(updates, List.empty)).unsafeRunSync()
+      updateStates <- pruningAlg.needsAttention(data).runA(initial)
+    } yield assertEquals(updateStates, None)).unsafeRunSync()
   }
 }