Repository: http4s/http4s
Pull request #6914 (2023-01-08 04:32:37)

Remove nonValidatingCopyConstructor linter
Scala 2.13 creates the copy constructor with the same visibility as the primary constructor.  This is a good lint, but only pertinent through 2.12.


Patch:
diff --git a/core/shared/src/main/scala/org/http4s/Charset.scala b/core/shared/src/main/scala/org/http4s/Charset.scala
index 9254f2ee4e..7611ad97fa 100644
--- a/core/shared/src/main/scala/org/http4s/Charset.scala
+++ b/core/shared/src/main/scala/org/http4s/Charset.scala
@@ -35,7 +35,6 @@ import java.util.HashMap
 import java.util.Locale
 import scala.jdk.CollectionConverters._
 
-// scalafix:off Http4sGeneralLinters; bincompat until 1.0
 final case class Charset private[http4s] (nioCharset: NioCharset) extends Renderable {
   def withQuality(q: QValue): CharsetRange.Atom = CharsetRange.Atom(this, q)
   def toRange: CharsetRange.Atom = withQuality(QValue.One)
diff --git a/core/shared/src/main/scala/org/http4s/HttpVersion.scala b/core/shared/src/main/scala/org/http4s/HttpVersion.scala
index a01c18f14a..fc44bd4072 100644
--- a/core/shared/src/main/scala/org/http4s/HttpVersion.scala
+++ b/core/shared/src/main/scala/org/http4s/HttpVersion.scala
@@ -40,11 +40,9 @@ import org.http4s.util._
   * [[https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#protocol.version
   * HTTP Semantics, Protocol Versioning]]
   */
-// scalafix:off Http4sGeneralLinters.nonValidatingCopyConstructor; bincompat until 1.0
 final case class HttpVersion private (major: Int, minor: Int)
     extends Renderable
     with Ordered[HttpVersion] {
-  // scalafix:on
 
   /** Renders as an HTTP/1.1 string
     *
diff --git a/core/shared/src/main/scala/org/http4s/Uri.scala b/core/shared/src/main/scala/org/http4s/Uri.scala
index 4958c9e517..f281fc85d4 100644
--- a/core/shared/src/main/scala/org/http4s/Uri.scala
+++ b/core/shared/src/main/scala/org/http4s/Uri.scala
@@ -609,7 +609,7 @@ object Uri extends UriPlatform {
     *
     * @see [[https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1 RFC 3986, Section 3.2.1, User Information]]
     */
-  final case class UserInfo private ( // scalafix:ok Http4sGeneralLinters.nonValidatingCopyConstructor; bincompat until 1.0
+  final case class UserInfo private (
       username: String,
       password: Option[String],
   ) extends Ordered[UserInfo] {
diff --git a/core/shared/src/main/scala/org/http4s/headers/Access-Control-Max-Age.scala b/core/shared/src/main/scala/org/http4s/headers/Access-Control-Max-Age.scala
index d16f4d39fa..63786cf0fd 100644
--- a/core/shared/src/main/scala/org/http4s/headers/Access-Control-Max-Age.scala
+++ b/core/shared/src/main/scala/org/http4s/headers/Access-Control-Max-Age.scala
@@ -38,7 +38,7 @@ object `Access-Control-Max-Age` {
     *
     * @param age age of the response (in seconds)
     */
-  final case class Cache private (age: Long) extends `Access-Control-Max-Age` { // scalafix:ok Http4sGeneralLinters.nonValidatingCopyConstructor; bincompat until 1.0
+  final case class Cache private (age: Long) extends `Access-Control-Max-Age` {
     def duration: Option[FiniteDuration] = Try(age.seconds).toOption
     def unsafeDuration: FiniteDuration = age.seconds
   }
diff --git a/core/shared/src/main/scala/org/http4s/headers/Age.scala b/core/shared/src/main/scala/org/http4s/headers/Age.scala
index 90c80b9971..d7e3922b9c 100644
--- a/core/shared/src/main/scala/org/http4s/headers/Age.scala
+++ b/core/shared/src/main/scala/org/http4s/headers/Age.scala
@@ -55,7 +55,7 @@ object Age {
   *
   * @param age age of the response
   */
-final case class Age private (age: Long) { // scalafix:ok Http4sGeneralLinters.nonValidatingCopyConstructor; bincompat until 1.0
+final case class Age private (age: Long) {
   def duration: Option[FiniteDuration] = Try(age.seconds).toOption
 
   def unsafeDuration: FiniteDuration = age.seconds
diff --git a/core/shared/src/main/scala/org/http4s/headers/Content-Type.scala b/core/shared/src/main/scala/org/http4s/headers/Content-Type.scala
index 635abdbf92..b2aefa5ab0 100644
--- a/core/shared/src/main/scala/org/http4s/headers/Content-Type.scala
+++ b/core/shared/src/main/scala/org/http4s/headers/Content-Type.scala
@@ -76,7 +76,7 @@ object `Content-Type` {
   *
   * [[https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.5 RFC-7231]]
   */
-final case class `Content-Type` private (mediaType: MediaType, charset: Option[Charset]) { // scalafix:ok; private for API ergonomics, not correctness
+final case class `Content-Type` private (mediaType: MediaType, charset: Option[Charset]) {
   def withMediaType(mediaType: MediaType): `Content-Type` =
     if (mediaType != this.mediaType) copy(mediaType = mediaType) else this
   def withCharset(charset: Charset): `Content-Type` =
diff --git a/core/shared/src/main/scala/org/http4s/headers/Sec-WebSocket-Version.scala b/core/shared/src/main/scala/org/http4s/headers/Sec-WebSocket-Version.scala
index 83d83a7c47..ff0df48cc0 100644
--- a/core/shared/src/main/scala/org/http4s/headers/Sec-WebSocket-Version.scala
+++ b/core/shared/src/main/scala/org/http4s/headers/Sec-WebSocket-Version.scala
@@ -50,4 +50,4 @@ object `Sec-WebSocket-Version` {
 
 final case class `Sec-WebSocket-Version` private (
     version: Long
-) // scalafix:ok Http4sGeneralLinters.nonValidatingCopyConstructor; bincompat until 1.0
+)
diff --git a/core/shared/src/main/scala/org/http4s/internal/CharPredicate.scala b/core/shared/src/main/scala/org/http4s/internal/CharPredicate.scala
index b9b03f6f15..5fc12c3731 100644
--- a/core/shared/src/main/scala/org/http4s/internal/CharPredicate.scala
+++ b/core/shared/src/main/scala/org/http4s/internal/CharPredicate.scala
@@ -155,8 +155,6 @@ object CharPredicate {
 
   private def unmaskable(c: Char) = c >= 128
 
-  // scalafix:off Http4sGeneralLinters.nonValidatingCopyConstructor; bincompat until 1.0
-
   // efficient handling of 7bit-ASCII chars
   final case class MaskBased private[CharPredicate] (lowMask: Long, highMask: Long)
       extends CharPredicate {
@@ -351,6 +349,4 @@ object CharPredicate {
 
     override def toString(): String = "CharPredicate.General@" + System.identityHashCode(this)
   }
-
-  // scalafix:on
 }
diff --git a/scalafix-internal/rules/src/main/scala-2/fix/GeneralLinters.scala b/scalafix-internal/rules/src/main/scala-2/fix/GeneralLinters.scala
index ac0dd2d2e4..e048fefbc8 100644
--- a/scalafix-internal/rules/src/main/scala-2/fix/GeneralLinters.scala
+++ b/scalafix-internal/rules/src/main/scala-2/fix/GeneralLinters.scala
@@ -22,7 +22,7 @@ import scala.meta._
 
 class GeneralLinters extends SemanticRule("Http4sGeneralLinters") {
   override def fix(implicit doc: SemanticDocument): Patch =
-    noFinalObject + noNonFinalCaseClass + leakingSealedHierarchy + nonValidatingCopyConstructor
+    noFinalObject + noNonFinalCaseClass + leakingSealedHierarchy
 
   def noFinalObject(implicit doc: SemanticDocument) =
     doc.tree.collect { case o @ Defn.Object(mods, _, _) =>
@@ -64,14 +64,6 @@ class GeneralLinters extends SemanticRule("Http4sGeneralLinters") {
         doCheck(t, mods, inits)
     }.asPatch
   }
-
-  def nonValidatingCopyConstructor(implicit doc: SemanticDocument) =
-    doc.tree.collect {
-      case c @ Defn.Class(mods, _, _, Ctor.Primary(ctorMods, _, _), _)
-          if mods.exists(_.is[Mod.Case]) && ctorMods
-            .exists(_.is[Mod.Private]) && !mods.exists(_.is[Mod.Abstract]) =>
-        Patch.lint(NonValidatingCopyConstructor(c))
-    }.asPatch
 }
 
 final case class CaseClassWithoutAccessModifier(c: Defn.Class) extends Diagnostic {
@@ -89,12 +81,3 @@ final case class LeakingSealedHierarchy(t: Tree) extends Diagnostic {
 
   override def categoryID: String = "leakingSealedHierarchy"
 }
-
-final case class NonValidatingCopyConstructor(c: Defn.Class) extends Diagnostic {
-  override def message: String =
-    "Case classes with private constructors should be abstract to prevent exposing a non-validating copy constructor"
-
-  override def position: Position = c.pos
-
-  override def categoryID: String = "nonValidatingCopyConstructor"
-}


Test patch:
diff --git a/scalafix-internal/input/src/main/scala/fix/GeneralLintersTest.scala b/scalafix-internal/input/src/main/scala/fix/GeneralLintersTest.scala
index a5d934cdbe..58c955ec0c 100644
--- a/scalafix-internal/input/src/main/scala/fix/GeneralLintersTest.scala
+++ b/scalafix-internal/input/src/main/scala/fix/GeneralLintersTest.scala
@@ -12,7 +12,3 @@ class Dog extends Animal// assert: Http4sGeneralLinters.leakingSealedHierarchy
 
 sealed abstract class Plant
 trait Tree extends Plant// assert: Http4sGeneralLinters.leakingSealedHierarchy
-
-package pkg {
-  final case class Foo private[pkg](v: String)// assert: Http4sGeneralLinters.nonValidatingCopyConstructor
-}
\ No newline at end of file
diff --git a/scalafix-internal/output/src/main/scala/fix/GeneralLintersTest.scala b/scalafix-internal/output/src/main/scala/fix/GeneralLintersTest.scala
index 921668f6a6..98a83cf23f 100644
--- a/scalafix-internal/output/src/main/scala/fix/GeneralLintersTest.scala
+++ b/scalafix-internal/output/src/main/scala/fix/GeneralLintersTest.scala
@@ -8,7 +8,3 @@ class Dog extends Animal
 
 sealed abstract class Plant
 trait Tree extends Plant
-
-package pkg {
-  final case class Foo private[pkg](v: String)
-}
\ No newline at end of file