Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore private operators when we don't have ContextBingding in UnusedPrivateMember #4441

Merged
merged 1 commit into from Jan 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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() }
BraisGabin marked this conversation as resolved.
Show resolved Hide resolved
}
.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") {
BraisGabin marked this conversation as resolved.
Show resolved Hide resolved
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 invoke 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