Skip to content

Commit 2a98bf6

Browse files
author
Roman Janusz
committed
analyzer rule that checks if Scala constants are properly declared
1 parent f002133 commit 2a98bf6

File tree

4 files changed

+120
-2
lines changed

4 files changed

+120
-2
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ final class AnalyzerPlugin(val global: Global) extends Plugin { plugin =>
5656
new Any2StringAdd(global),
5757
new ThrowableObjects(global),
5858
new DiscardedMonixTask(global),
59-
new BadSingletonComponent(global)
59+
new BadSingletonComponent(global),
60+
new ConstantDeclarations(global),
6061
)
6162

6263
private lazy val rulesByName = rules.map(r => (r.name, r)).toMap
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.avsystem.commons
2+
package analyzer
3+
4+
import scala.tools.nsc.Global
5+
6+
class ConstantDeclarations(g: Global) extends AnalyzerRule(g, "constantDeclarations") {
7+
8+
import global._
9+
10+
def analyze(unit: CompilationUnit): Unit = unit.body.foreach {
11+
case t@ValDef(mods, name, tpt, rhs)
12+
if t.symbol.hasGetter && t.symbol.owner.isEffectivelyFinal =>
13+
14+
val getter = t.symbol.getterIn(t.symbol.owner)
15+
if (getter.isPublic && getter.isStable && getter.overrides.isEmpty) {
16+
val constantValue = rhs.tpe match {
17+
case ConstantType(_) => true
18+
case _ => false
19+
}
20+
21+
val firstChar = name.toString.charAt(0)
22+
if (constantValue && (firstChar.isLower || !getter.isFinal)) {
23+
report(t.pos, "a constant should be declared as a `final val` with an UpperCamelCase name")
24+
}
25+
if (firstChar.isUpper && !getter.isFinal) {
26+
report(t.pos, "a constant with UpperCamelCase name should be declared as a `final val`")
27+
}
28+
if (getter.isFinal && constantValue && !(tpt.tpe =:= rhs.tpe)) {
29+
report(t.pos, "a constant with a literal value should not have an explicit type annotation")
30+
}
31+
}
32+
case _ =>
33+
}
34+
}

commons-analyzer/src/test/scala/com/avsystem/commons/analyzer/CheckMacroPrivateTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class CheckMacroPrivateTest extends AnyFunSuite with AnalyzerTest {
5050
|object test {
5151
| @macroPrivate def macroPrivateMethod = { println("whatever"); 5 }
5252
| @macroPrivate object macroPrivateObject {
53-
| val x = 42
53+
| final val X = 42
5454
| }
5555
|}
5656
""".stripMargin
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.avsystem.commons
2+
package analyzer
3+
4+
import org.scalatest.funsuite.AnyFunSuite
5+
6+
class ConstantDeclarationsTest extends AnyFunSuite with AnalyzerTest {
7+
test("literal-valued constants should be non-lazy final vals with UpperCamelCase and no type annotation") {
8+
assertErrors(4,
9+
"""
10+
|object Whatever {
11+
| // bad
12+
| val a = 10
13+
| val B = 10
14+
| final val c = 10
15+
| final val D: Int = 10
16+
|
17+
| // good
18+
| final val E = 10
19+
|}
20+
""".stripMargin)
21+
}
22+
23+
test("effectively final, non-literal UpperCamelCase vals should be final") {
24+
assertErrors(1,
25+
"""
26+
|object Whatever {
27+
| // bad
28+
| val A = "foo".trim
29+
|
30+
| // good
31+
| final val B = "foo".trim
32+
| val c = "foo".trim
33+
|}
34+
""".stripMargin)
35+
}
36+
37+
test("no constant checking in traits or non-final classes") {
38+
assertNoErrors(
39+
"""
40+
|trait Whatever {
41+
| val a = 10
42+
| val B = 10
43+
| final val c = 10
44+
| final val D: Int = 10
45+
| val A = "foo".trim
46+
|}
47+
|
48+
|class Stuff {
49+
| val a = 10
50+
| val B = 10
51+
| final val c = 10
52+
| final val D: Int = 10
53+
| val A = "foo".trim
54+
|}
55+
""".stripMargin)
56+
}
57+
58+
test("no constant checking for overrides") {
59+
assertNoErrors(
60+
"""
61+
|trait Whatever {
62+
| def a: Int
63+
|}
64+
|
65+
|object Stuff extends Whatever {
66+
| val a: Int = 42
67+
|}
68+
""".stripMargin)
69+
}
70+
71+
test("no constant checking for privates") {
72+
assertNoErrors(
73+
"""
74+
|object Whatever {
75+
| private val a = 10
76+
| private val B = 10
77+
| private final val c = 10
78+
| private final val D: Int = 10
79+
| private val A = "foo".trim
80+
|}
81+
""".stripMargin)
82+
}
83+
}

0 commit comments

Comments
 (0)