Skip to content

Commit

Permalink
Create new rule: NoFunctionReferenceToJavaClass (#32)
Browse files Browse the repository at this point in the history
Drive-by: check in some auto generated changes in `.idea` (probably due
to a newer version of IntelliJ)
  • Loading branch information
Hexcles authored May 7, 2024
1 parent 27ebf1b commit c972cc8
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 1 deletion.
1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/main/kotlin/com/faire/detekt/FaireRulesProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.faire.detekt.rules.DoNotUseSingleOnFilter
import com.faire.detekt.rules.DoNotUseSizePropertyInAssert
import com.faire.detekt.rules.GetOrDefaultShouldBeReplacedWithGetOrElse
import com.faire.detekt.rules.NoExtensionFunctionOnNullableReceiver
import com.faire.detekt.rules.NoFunctionReferenceToJavaClass
import com.faire.detekt.rules.NoNonPrivateGlobalVariables
import com.faire.detekt.rules.NoNullableLambdaWithDefaultNull
import com.faire.detekt.rules.NoPairWithAmbiguousTypes
Expand Down Expand Up @@ -45,6 +46,7 @@ internal class FaireRulesProvider : RuleSetProvider {
DoNotUseSizePropertyInAssert(config),
GetOrDefaultShouldBeReplacedWithGetOrElse(config),
NoExtensionFunctionOnNullableReceiver(config),
NoFunctionReferenceToJavaClass(config),
NoNonPrivateGlobalVariables(config),
NoNullableLambdaWithDefaultNull(config),
NoPairWithAmbiguousTypes(config),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.faire.detekt.rules

import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression

/**
* No use of `::javaClass`; use `.javaClass` instead.
*
* `Foo::bar` gives you a reference to function/property `bar` on class `Foo`:
* https://kotlinlang.org/docs/reflection.html#function-references
* [javaClass] is an extension function on [Any]:
* https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/java-class.html
*
* 99% of the time, you want to get the JVM class of some class/object instead of the callable reference to this
* extension function. This bug can hide deep if you then proceed to call any method on [Class]: e.g.
* ```
* println(Foo::javaClass.simpleName) // gives you "javaClass"
* println(Foo.javaClass.simpleName) // gives you "Foo"
* ```
*/
internal class NoFunctionReferenceToJavaClass(config: Config = Config.empty) : Rule(config) {
override val issue = Issue(
id = javaClass.simpleName,
severity = Severity.CodeSmell,
description = RULE_DESCRIPTION,
debt = Debt.FIVE_MINS,
)

override fun visitCallableReferenceExpression(expression: KtCallableReferenceExpression) {
super.visitCallableReferenceExpression(expression)
if (expression.callableReference.getReferencedName() == "javaClass") {
report(
CodeSmell(
issue = issue,
entity = Entity.from(expression),
message = RULE_DESCRIPTION,
),
)
}
}

companion object {
const val RULE_DESCRIPTION = "Do not call ::javaClass; did you mean .javaClass?"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.faire.detekt.rules

import io.gitlab.arturbosch.detekt.test.lint
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

internal class NoFunctionReferenceToJavaClassTest {
private val rule: NoFunctionReferenceToJavaClass = NoFunctionReferenceToJavaClass()

@Test
fun `should report double-colon javaClass`() {
val findings = rule.lint(
"""
class Foo
val buggy = Foo::javaClass.name
""".trimIndent(),
)

assertThat(findings).hasSize(1)
assertThat(findings.first().issue.id).isEqualTo("NoFunctionReferenceToJavaClass")
}

@Test
fun `should not report dot javaClass`() {
val findings = rule.lint(
"""
class Foo
val correct = Foo.javaClass.name
""".trimIndent(),
)

assertThat(findings).isEmpty()
}
}

0 comments on commit c972cc8

Please sign in to comment.