Skip to content

Commit

Permalink
Ignore private operators when we don't have ContextBingding in Unused…
Browse files Browse the repository at this point in the history
…PrivateMember
  • Loading branch information
BraisGabin committed Jan 2, 2022
1 parent eb0f1cf commit 02e361f
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 42 deletions.
Expand Up @@ -97,58 +97,65 @@ private class UnusedFunctionVisitor(
private val functionReferences = mutableMapOf<String, MutableList<KtReferenceExpression>>()
private val propertyDelegates = mutableListOf<KtPropertyDelegate>()

@Suppress("ComplexMethod")
override fun getUnusedReports(issue: Issue): List<CodeSmell> {
val propertyDelegateResultingDescriptors by lazy(LazyThreadSafetyMode.NONE) {
propertyDelegates.flatMap { it.resultingDescriptors() }
}
return functionDeclarations.flatMap { (functionName, functions) ->
val isOperator = functions.any { it.isOperator() }
val references = functionReferences[functionName].orEmpty()
val unusedFunctions = when {
(functions.size > 1 || isOperator) && bindingContext != BindingContext.EMPTY -> {
val functionNameAsName = Name.identifier(functionName)
val referencesViaOperator = if (isOperator) {
val operatorToken = OperatorConventions.getOperationSymbolForName(functionNameAsName)
val operatorValue = (operatorToken as? KtSingleValueToken)?.value
val directReferences = operatorValue?.let { functionReferences[it] }.orEmpty()
val assignmentReferences = when (operatorToken) {
KtTokens.PLUS,
KtTokens.MINUS,
KtTokens.MUL,
KtTokens.DIV,
KtTokens.PERC -> operatorValue?.let { functionReferences["$it="] }.orEmpty()
else -> emptyList()
}
val containingReferences = if (functionNameAsName == OperatorNameConventions.CONTAINS) {
listOf(KtTokens.IN_KEYWORD, KtTokens.NOT_IN).flatMap {
functionReferences[it.value].orEmpty()
return functionDeclarations
.filterNot { (_, functions) ->
// Without a binding context we can't know if an operator is called. So we ignore it to avoid
// false positives. More context at #4242
bindingContext == BindingContext.EMPTY && functions.any { it.isOperator() }
}
.flatMap { (functionName, functions) ->
val isOperator = functions.any { it.isOperator() }
val references = functionReferences[functionName].orEmpty()
val unusedFunctions = when {
(functions.size > 1 || isOperator) && bindingContext != BindingContext.EMPTY -> {
val functionNameAsName = Name.identifier(functionName)
val referencesViaOperator = if (isOperator) {
val operatorToken = OperatorConventions.getOperationSymbolForName(functionNameAsName)
val operatorValue = (operatorToken as? KtSingleValueToken)?.value
val directReferences = operatorValue?.let { functionReferences[it] }.orEmpty()
val assignmentReferences = when (operatorToken) {
KtTokens.PLUS,
KtTokens.MINUS,
KtTokens.MUL,
KtTokens.DIV,
KtTokens.PERC -> operatorValue?.let { functionReferences["$it="] }.orEmpty()
else -> emptyList()
}
} else emptyList()
directReferences + assignmentReferences + containingReferences
} else {
emptyList()
}
val referenceDescriptors = (references + referencesViaOperator)
.mapNotNull { it.getResolvedCall(bindingContext)?.resultingDescriptor }
.map { it.original }
.let {
if (functionNameAsName in OperatorNameConventions.DELEGATED_PROPERTY_OPERATORS) {
it + propertyDelegateResultingDescriptors
} else {
it
val containingReferences = if (functionNameAsName == OperatorNameConventions.CONTAINS) {
listOf(KtTokens.IN_KEYWORD, KtTokens.NOT_IN).flatMap {
functionReferences[it.value].orEmpty()
}
} else emptyList()
directReferences + assignmentReferences + containingReferences
} else {
emptyList()
}
val referenceDescriptors = (references + referencesViaOperator)
.mapNotNull { it.getResolvedCall(bindingContext)?.resultingDescriptor }
.map { it.original }
.let {
if (functionNameAsName in OperatorNameConventions.DELEGATED_PROPERTY_OPERATORS) {
it + propertyDelegateResultingDescriptors
} else {
it
}
}
functions.filterNot {
bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] in referenceDescriptors
}
functions.filterNot {
bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] in referenceDescriptors
}
references.isEmpty() -> functions
else -> emptyList()
}
unusedFunctions.map {
CodeSmell(issue, Entity.from(it), "Private function $functionName is unused.")
}
references.isEmpty() -> functions
else -> emptyList()
}
unusedFunctions.map {
CodeSmell(issue, Entity.from(it), "Private function $functionName is unused.")
}
}
}

override fun visitNamedFunction(function: KtNamedFunction) {
Expand Down
Expand Up @@ -4,6 +4,7 @@ import io.gitlab.arturbosch.detekt.api.SourceLocation
import io.gitlab.arturbosch.detekt.rules.setupKotlinEnvironment
import io.gitlab.arturbosch.detekt.test.TestConfig
import io.gitlab.arturbosch.detekt.test.assertThat
import io.gitlab.arturbosch.detekt.test.compileAndLint
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
import io.gitlab.arturbosch.detekt.test.lint
import io.gitlab.arturbosch.detekt.test.lintWithContext
Expand Down Expand Up @@ -1028,6 +1029,32 @@ class UnusedPrivateMemberSpec : Spek({
assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}

it("does not report used plus operator without type solving - #4242") {
val code = """
import java.util.Date
class Foo {
val bla: Date = Date(System.currentTimeMillis()) + 300L
companion object {
private operator fun Date.plus(diff: Long): Date = Date(this.time + diff)
}
}
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}

it("does not report used plus operator without type solving - #4435") {
val code = """
object Test {
private operator fun invoke(i: Int): Int = i
fun answer() = Test(1)
}
val answer = Test.answer()
"""
assertThat(subject.compileAndLint(code)).isEmpty()
}

it("does not report used operator methods when used with the equal sign") {
val code = """
class Test {
Expand Down

0 comments on commit 02e361f

Please sign in to comment.