Repository: scala-steward-org/scala-steward
Pull request #1696 (2020-10-28 06:33:00)

Differentiate between non-set and empty file extensions
See https://github.com/scala-steward-org/scala-steward/pull/1488#pullrequestreview-434222425

The main purpose here is to get rid of `nonExistingFileExtension`.

codecov[bot]: # [Codecov](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696?src=pr&el=h1) Report
> Merging [#1696](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696?src=pr&el=desc) into [master](https://codecov.io/gh/scala-steward-org/scala-steward/commit/5b6601d06f23b6bcc7dde9c65d8332ade7992a8d?el=desc) will **increase** coverage by `0.08%`.
> The diff coverage is `66.66%`.

[![Impacted file tree graph](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696/graphs/tree.svg?width=650&height=150&src=pr&token=udzMsc1Dne)](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1696      +/-   ##
==========================================
+ Coverage   73.68%   73.77%   +0.08%     
==========================================
  Files         115      114       -1     
  Lines        1946     1941       -5     
  Branches       57       50       -7     
==========================================
- Hits         1434     1432       -2     
+ Misses        512      509       -3     
```


| [Impacted Files](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...g/scalasteward/core/repoconfig/UpdatesConfig.scala](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696/diff?src=pr&el=tree#diff-bW9kdWxlcy9jb3JlL3NyYy9tYWluL3NjYWxhL29yZy9zY2FsYXN0ZXdhcmQvY29yZS9yZXBvY29uZmlnL1VwZGF0ZXNDb25maWcuc2NhbGE=) | `98.30% <50.00%> (+2.99%)` | :arrow_up: |
| [...ain/scala/org/scalasteward/core/util/package.scala](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696/diff?src=pr&el=tree#diff-bW9kdWxlcy9jb3JlL3NyYy9tYWluL3NjYWxhL29yZy9zY2FsYXN0ZXdhcmQvY29yZS91dGlsL3BhY2thZ2Uuc2NhbGE=) | `100.00% <100.00%> (ø)` | |
| [...la-2.12/org/scalasteward/mill/plugin/package.scala](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696/diff?src=pr&el=tree#diff-bW9kdWxlcy9taWxsLXBsdWdpbi9zcmMvbWFpbi9zY2FsYS0yLjEyL29yZy9zY2FsYXN0ZXdhcmQvbWlsbC9wbHVnaW4vcGFja2FnZS5zY2FsYQ==) | | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696?src=pr&el=footer). Last update [5b6601d...409d1d8](https://codecov.io/gh/scala-steward-org/scala-steward/pull/1696?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 80f8dff4..158035f1 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,7 @@ Thanks goes to these wonderful people for contributing to Scala Steward:
 * [Rikito Taniguchi](https://github.com/tanishiking)
 * [Robert Stoll](https://github.com/robstoll)
 * [Scott Rice](https://github.com/scottrice10)
+* [solar](https://github.com/solar)
 * [Stanislav Chetvertkov](https://github.com/stanislav-chetvertkov)
 * [sullis](https://github.com/sullis)
 * [TATSUNO Yasuhiro](https://github.com/exoego)
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala
index 82e9be78..ed8ce1d4 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala
@@ -30,7 +30,7 @@ import org.scalasteward.core.update.FilterAlg.{
   NotAllowedByConfig,
   VersionPinnedByConfig
 }
-import org.scalasteward.core.util.Nel
+import org.scalasteward.core.util.{combineOptions, Nel}
 
 final case class UpdatesConfig(
     pin: List[UpdatePattern] = List.empty,
@@ -38,16 +38,13 @@ final case class UpdatesConfig(
     ignore: List[UpdatePattern] = List.empty,
     limit: Option[PosInt] = None,
     includeScala: Option[Boolean] = None,
-    fileExtensions: List[String] = List.empty
+    fileExtensions: Option[List[String]] = None
 ) {
   def keep(update: Update.Single): FilterResult =
     isAllowed(update).flatMap(isPinned).flatMap(isIgnored)
 
   def fileExtensionsOrDefault: Set[String] =
-    if (fileExtensions.isEmpty)
-      UpdatesConfig.defaultFileExtensions
-    else
-      fileExtensions.toSet
+    fileExtensions.fold(UpdatesConfig.defaultFileExtensions)(_.toSet)
 
   def includeScalaOrDefault: Boolean =
     includeScala.getOrElse(UpdatesConfig.defaultIncludeScala)
@@ -94,7 +91,6 @@ object UpdatesConfig {
   val defaultFileExtensions: Set[String] =
     Set(".scala", ".sbt", ".sbt.shared", ".sc", ".yml", "pom.xml")
 
-  private[repoconfig] val nonExistingFileExtension: List[String] = List(".non-exist")
   private[repoconfig] val nonExistingUpdatePattern: List[UpdatePattern] = List(
     UpdatePattern(GroupId("non-exist"), None, None)
   )
@@ -189,14 +185,10 @@ object UpdatesConfig {
       y: List[UpdatePattern]
   ): List[UpdatePattern] =
     (x ::: y).distinct
-  private[repoconfig] def mergeFileExtensions(x: List[String], y: List[String]): List[String] =
-    (x, y) match {
-      case (Nil, second) => second
-      case (first, Nil)  => first
-      case _ =>
-        val result = x.intersect(y)
-        //  Since empty result represents [*] any extension, we gonna set artificial extension instead.
-        if (result.nonEmpty) result
-        else nonExistingFileExtension
-    }
+
+  private[repoconfig] def mergeFileExtensions(
+      x: Option[List[String]],
+      y: Option[List[String]]
+  ): Option[List[String]] =
+    combineOptions(x, y)(_.intersect(_))
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/util/package.scala b/modules/core/src/main/scala/org/scalasteward/core/util/package.scala
index 7b923779..9e60ab11 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/util/package.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/util/package.scala
@@ -56,6 +56,17 @@ package object util {
   def bindUntilTrue[G[_]: Foldable, F[_]: Monad](gfb: G[F[Boolean]]): F[Boolean] =
     gfb.existsM(identity)
 
+  /** Like `Semigroup[Option[A]].combine` but allows to specify how `A`s are combined. */
+  def combineOptions[A](x: Option[A], y: Option[A])(f: (A, A) => A): Option[A] =
+    x match {
+      case None => y
+      case Some(xv) =>
+        y match {
+          case None     => x
+          case Some(yv) => Some(f(xv, yv))
+        }
+    }
+
   /** Returns true if there is an element that is both in `fa` and `ga`. */
   def intersects[F[_]: UnorderedFoldable, G[_]: UnorderedFoldable, A: Eq](
       fa: F[A],


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala
index 278f38a4..6ab714c6 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala
@@ -65,7 +65,7 @@ class RepoConfigAlgTest extends AnyFunSuite with Matchers {
         ),
         limit = Some(PosInt.unsafeFrom(4)),
         includeScala = Some(true),
-        fileExtensions = List(".txt")
+        fileExtensions = Some(List(".txt"))
       ),
       commits = CommitsConfig(
         message = Some("Update ${artifactName} from ${currentVersion} to ${nextVersion}")
diff --git a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/UpdatesConfigTest.scala b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/UpdatesConfigTest.scala
index f77a7d78..ccef4148 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/UpdatesConfigTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/UpdatesConfigTest.scala
@@ -33,7 +33,7 @@ class UpdatesConfigTest extends AnyFunSuite with Matchers {
       ignore = List(aa0),
       limit = Some(PosInt(10)),
       includeScala = Some(false),
-      fileExtensions = List(".txt", ".scala", ".sbt")
+      fileExtensions = Some(List(".txt", ".scala", ".sbt"))
     )
     cfg |+| cfg shouldBe cfg
 
@@ -46,7 +46,7 @@ class UpdatesConfigTest extends AnyFunSuite with Matchers {
       ignore = List(ab0),
       limit = Some(PosInt(20)),
       includeScala = Some(true),
-      fileExtensions = List(".sbt", ".scala")
+      fileExtensions = Some(List(".sbt", ".scala"))
     )
 
     cfg |+| cfg2 shouldBe UpdatesConfig(
@@ -55,7 +55,7 @@ class UpdatesConfigTest extends AnyFunSuite with Matchers {
       ignore = List(aa0, ab0),
       limit = Some(PosInt(10)),
       includeScala = Some(false),
-      fileExtensions = List(".scala", ".sbt")
+      fileExtensions = Some(List(".scala", ".sbt"))
     )
 
     cfg2 |+| cfg shouldBe UpdatesConfig(
@@ -64,7 +64,7 @@ class UpdatesConfigTest extends AnyFunSuite with Matchers {
       ignore = List(ab0, aa0),
       limit = Some(PosInt(20)),
       includeScala = Some(true),
-      fileExtensions = List(".sbt", ".scala")
+      fileExtensions = Some(List(".sbt", ".scala"))
     )
   }
 
@@ -112,14 +112,18 @@ class UpdatesConfigTest extends AnyFunSuite with Matchers {
   }
 
   test("mergeFileExtensions: basic checks") {
-    UpdatesConfig.mergeFileExtensions(Nil, Nil) shouldBe Nil
-    UpdatesConfig.mergeFileExtensions(Nil, List("txt")) shouldBe List("txt")
-    UpdatesConfig.mergeFileExtensions(List("txt"), Nil) shouldBe List("txt")
-    UpdatesConfig.mergeFileExtensions(List("txt"), List("txt")) shouldBe List("txt")
-    UpdatesConfig.mergeFileExtensions(List("a", "b", "c"), List("b", "d")) shouldBe List("b")
-    UpdatesConfig.mergeFileExtensions(
-      List("a"),
-      List("b")
-    ) shouldBe UpdatesConfig.nonExistingFileExtension
+    UpdatesConfig.mergeFileExtensions(None, None) shouldBe None
+    UpdatesConfig.mergeFileExtensions(None, Some(List("txt"))) shouldBe Some(List("txt"))
+    UpdatesConfig.mergeFileExtensions(Some(List("txt")), None) shouldBe Some(List("txt"))
+    UpdatesConfig.mergeFileExtensions(Some(List("txt")), Some(List("txt"))) shouldBe
+      Some(List("txt"))
+    UpdatesConfig.mergeFileExtensions(Some(List("a", "b", "c")), Some(List("b", "d"))) shouldBe
+      Some(List("b"))
+    UpdatesConfig.mergeFileExtensions(Some(List("a")), Some(List("b"))) shouldBe Some(List())
+  }
+
+  test("fileExtensionsOrDefault: non-set != empty") {
+    UpdatesConfig(fileExtensions = None).fileExtensionsOrDefault shouldNot
+      be(UpdatesConfig(fileExtensions = Some(Nil)).fileExtensionsOrDefault)
   }
 }