Skip to content

Commit 1856639

Browse files
committed
add FinalCaseClasses rule to enforce final modifier on case classes and include tests
1 parent 04ff725 commit 1856639

File tree

3 files changed

+55
-0
lines changed

3 files changed

+55
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ final class AnalyzerPlugin(val global: Global) extends Plugin { plugin =>
6161
new BasePackage(global),
6262
new ImplicitValueClasses(global),
6363
new FinalValueClasses(global),
64+
new FinalCaseClasses(global),
6465
new ImplicitParamDefaults(global),
6566
new CatchThrowable(global),
6667
new ImplicitFunctionParams(global),
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.avsystem.commons
2+
package analyzer
3+
4+
import scala.tools.nsc.Global
5+
6+
class FinalCaseClasses(g: Global) extends AnalyzerRule(g, "finalCaseClasses", Level.Warn) {
7+
8+
import global.*
9+
10+
def analyze(unit: CompilationUnit): Unit = unit.body.foreach {
11+
case cd: ClassDef if !cd.mods.hasFlag(Flag.FINAL) && cd.mods.hasFlag(Flag.CASE) =>
12+
report(cd.pos, "Case classes should be marked as final")
13+
case _ =>
14+
}
15+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.avsystem.commons
2+
package analyzer
3+
4+
import org.scalatest.funsuite.AnyFunSuite
5+
6+
class FinalCaseClassesTest extends AnyFunSuite with AnalyzerTest {
7+
test("case classes should be marked as final") {
8+
assertErrors(2,
9+
//language=scala
10+
"""
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+
| }
36+
|}
37+
""".stripMargin)
38+
}
39+
}

0 commit comments

Comments
 (0)