Skip to content

Commit 7456851

Browse files
committed
enhance rules and tests for FinalCaseClasses, ImplicitValueClasses, and ImplicitParamDefaults; improve error messages and test coverage
1 parent 0c118fc commit 7456851

File tree

10 files changed

+534
-321
lines changed

10 files changed

+534
-321
lines changed

.idea/codeStyles/Project.xml

Lines changed: 0 additions & 74 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

analyzer/src/main/scala/com/avsystem/commons/analyzer/ImplicitParamDefaults.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class ImplicitParamDefaults(g: Global) extends AnalyzerRule(g, "implicitParamDef
1313
if (paramList.nonEmpty && paramList.head.mods.hasFlag(Flag.IMPLICIT)) {
1414
paramList.foreach { param =>
1515
if (param.rhs != EmptyTree) {
16-
report(param.pos, "Do not declare default values for implicit parameters")
16+
report(param.pos, "Implicit parameters should not have default values")
1717
}
1818
}
1919
}

analyzer/src/main/scala/com/avsystem/commons/analyzer/ImplicitValueClasses.scala

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ class ImplicitValueClasses(g: Global) extends AnalyzerRule(g, "implicitValueClas
1010

1111
private lazy val defaultClasses = Set[Symbol](AnyClass, AnyValClass, ObjectClass)
1212

13-
private lazy val reportOnNestedClasses = argument match {
14-
case null => false
15-
case a => a.toBoolean
16-
}
13+
private lazy val reportOnNestedClasses = ImplicitValueClasses.Options.fromString(argument).reportOnNestedClasses
1714

1815
def analyze(unit: CompilationUnit): Unit = unit.body.foreach {
1916
case cd: ClassDef if cd.mods.hasFlag(Flag.IMPLICIT) =>
@@ -51,3 +48,17 @@ class ImplicitValueClasses(g: Global) extends AnalyzerRule(g, "implicitValueClas
5148
case _ =>
5249
}
5350
}
51+
52+
object ImplicitValueClasses {
53+
private sealed abstract class Options(val reportOnNestedClasses: Boolean)
54+
private object Options {
55+
private case object All extends Options(reportOnNestedClasses = true)
56+
private case object TopLevelOnly extends Options(reportOnNestedClasses = false)
57+
58+
def fromString(s: String): Options = s match {
59+
case "all" => All
60+
case "top-level-only" | null => TopLevelOnly
61+
case _ => throw new IllegalArgumentException(s"Unknown ImplicitValueClasses option: $s")
62+
}
63+
}
64+
}

analyzer/src/test/scala/com/avsystem/commons/analyzer/CatchThrowableTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package analyzer
33

44
import org.scalatest.funsuite.AnyFunSuite
55

6-
class CatchThrowableTest extends AnyFunSuite with AnalyzerTest {
6+
final class CatchThrowableTest extends AnyFunSuite with AnalyzerTest {
77
test("catching Throwable should be rejected") {
88
assertErrors(1,
99
//language=Scala

analyzer/src/test/scala/com/avsystem/commons/analyzer/FinalCaseClassesTest.scala

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,59 @@ package analyzer
33

44
import org.scalatest.funsuite.AnyFunSuite
55

6-
class FinalCaseClassesTest extends AnyFunSuite with AnalyzerTest {
7-
test("case classes should be marked as final") {
8-
assertErrors(2,
6+
final class FinalCaseClassesTest extends AnyFunSuite with AnalyzerTest {
7+
test("final case class should pass") {
8+
assertNoErrors(
99
//language=scala
1010
"""
11-
|object whatever {
12-
| // This should pass - final case class
13-
| final case class GoodCaseClass(x: Int, y: String) {
14-
| def double: Int = x * 2
15-
| }
16-
|
17-
| // This should fail - case class not marked as final
18-
| case class BadCaseClass1(x: Int, y: String) {
19-
| def double: Int = x * 2
20-
| }
21-
|
22-
| // This should fail - another case class not marked as final
23-
| case class BadCaseClass2[T](x: T, y: String) {
24-
| def double: String = y * 2
25-
| }
26-
|
27-
| // Regular class - should not be affected
28-
| class RegularClass(val x: Int, val y: String) {
29-
| def double: Int = x * 2
30-
| }
31-
|
32-
| // Regular class with case-like constructor - should not be affected
33-
| class RegularClass2(x: Int, y: String) {
34-
| def double: Int = x * 2
35-
| }
11+
|final case class GoodCaseClass(x: Int, y: String) {
12+
| def double: Int = x * 2
13+
|}
14+
""".stripMargin)
15+
}
16+
17+
test("case class not marked as final should fail") {
18+
assertErrors(1,
19+
//language=scala
20+
"""
21+
|case class BadCaseClass1(x: Int, y: String) {
22+
| def double: Int = x * 2
23+
|}
24+
""".stripMargin)
25+
}
26+
27+
test("generic case class not marked as final should fail") {
28+
assertErrors(1,
29+
//language=scala
30+
"""
31+
|case class BadCaseClass2[T](x: T, y: String) {
32+
| def double: String = y * 2
33+
|}
34+
""".stripMargin)
35+
}
36+
37+
test("regular class should not be affected") {
38+
assertNoErrors(
39+
//language=scala
40+
"""
41+
|class RegularClass(val x: Int, val y: String) {
42+
| def double: Int = x * 2
43+
|}
44+
""".stripMargin)
45+
}
46+
47+
test("regular class with case-like constructor should not be affected") {
48+
assertNoErrors(
49+
//language=scala
50+
"""
51+
|class RegularClass2(x: Int, y: String) {
52+
| def double: Int = x * 2
3653
|}
3754
""".stripMargin)
3855
}
3956

4057
// SI-4440 https://github.com/scala/bug/issues/4440
41-
test("should not be affected due to SI-4440") {
58+
test("inner case class in trait should not be affected due to SI-4440") {
4259
assertNoErrors(
4360
//language=scala
4461
"""
@@ -47,13 +64,20 @@ class FinalCaseClassesTest extends AnyFunSuite with AnalyzerTest {
4764
| def double: Int = x * 2
4865
| }
4966
|}
50-
|
67+
""".stripMargin
68+
)
69+
}
70+
71+
test("inner case class in class should not be affected due to SI-4440") {
72+
assertNoErrors(
73+
//language=scala
74+
"""
5175
|class Outer2 {
5276
| case class Inner(x: Int, y: String) {
5377
| def double: Int = x * 2
5478
| }
5579
|}
56-
""".stripMargin
80+
""".stripMargin
5781
)
5882
}
5983

@@ -68,7 +92,7 @@ class FinalCaseClassesTest extends AnyFunSuite with AnalyzerTest {
6892
| val jeden = SealedCaseClass(1)
6993
| val dwa = SealedCaseClass(2)
7094
|}
71-
""".stripMargin
95+
""".stripMargin
7296
)
7397
}
74-
}
98+
}

analyzer/src/test/scala/com/avsystem/commons/analyzer/FinalValueClassesTest.scala

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,54 @@ package analyzer
33

44
import org.scalatest.funsuite.AnyFunSuite
55

6-
class FinalValueClassesTest extends AnyFunSuite with AnalyzerTest {
7-
test("value classes should be marked as final") {
8-
assertErrors(2,
6+
final class FinalValueClassesTest extends AnyFunSuite with AnalyzerTest {
7+
test("final value class should pass") {
8+
assertNoErrors(
9+
//language=scala
910
"""
10-
|object whatever {
11-
| // This should pass - final value class
12-
| final class GoodValueClass(val x: Int) extends AnyVal {
13-
| def double: Int = x * 2
14-
| }
15-
|
16-
| // This should fail - value class not marked as final
17-
| class BadValueClass1(val x: Int) extends AnyVal {
18-
| def double: Int = x * 2
19-
| }
20-
|
21-
| // This should fail - another value class not marked as final
22-
| class BadValueClass2[T <: Int](val x: T) extends AnyVal {
23-
| def double: Int = x * 2
24-
| }
25-
|
26-
| // Regular class extending AnyVal but not marked as final - should not be affected
27-
| // because it has multiple parameters
28-
| class RegularClass(val x: Int, val y: Int) {
29-
| def double: Int = x * 2
30-
| }
31-
|
32-
| // Regular class not extending AnyVal - should not be affected
33-
| class RegularClass2(val x: Int) {
34-
| def double: Int = x * 2
35-
| }
11+
|final class GoodValueClass(val x: Int) extends AnyVal {
12+
| def double: Int = x * 2
3613
|}
3714
""".stripMargin)
3815
}
39-
}
16+
17+
test("value class not marked as final should fail") {
18+
assertErrors(1,
19+
//language=scala
20+
"""
21+
|class BadValueClass1(val x: Int) extends AnyVal {
22+
| def double: Int = x * 2
23+
|}
24+
""".stripMargin)
25+
}
26+
27+
test("generic value class not marked as final should fail") {
28+
assertErrors(1,
29+
//language=scala
30+
"""
31+
|class BadValueClass2[T <: Int](val x: T) extends AnyVal {
32+
| def double: Int = x * 2
33+
|}
34+
""".stripMargin)
35+
}
36+
37+
test("regular class with multiple parameters should not be affected") {
38+
assertNoErrors(
39+
//language=scala
40+
"""
41+
|class RegularClass(val x: Int, val y: Int) {
42+
| def double: Int = x * 2
43+
|}
44+
""".stripMargin)
45+
}
46+
47+
test("regular class not extending AnyVal should not be affected") {
48+
assertNoErrors(
49+
//language=scala
50+
"""
51+
|class RegularClass2(val x: Int) {
52+
| def double: Int = x * 2
53+
|}
54+
""".stripMargin)
55+
}
56+
}

0 commit comments

Comments
 (0)