io.gitlab.arturbosch.detekt.rules.style.UnusedPrivateMember.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of detekt-rules-style Show documentation
Show all versions of detekt-rules-style Show documentation
Static code analysis for Kotlin
The newest version!
package io.gitlab.arturbosch.detekt.rules.style
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.DetektVisitor
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 io.gitlab.arturbosch.detekt.api.config
import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault
import io.gitlab.arturbosch.detekt.api.internal.Configuration
import io.gitlab.arturbosch.detekt.rules.isOperator
import org.jetbrains.kotlin.backend.jvm.ir.psiElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtArrayAccessExpression
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtOperationReferenceExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyDelegate
import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isPrivate
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.util.OperatorNameConventions
private const val ARRAY_GET_METHOD_NAME = "get"
/**
* Reports unused private functions.
*
* If these private functions are unused they should be removed. Otherwise, this dead code
* can lead to confusion and potential bugs.
*/
@Suppress("ViolatesTypeResolutionRequirements")
@ActiveByDefault(since = "1.16.0")
class UnusedPrivateMember(config: Config = Config.empty) : Rule(config) {
override val defaultRuleIdAliases: Set = setOf("UNUSED_VARIABLE", "UNUSED_PARAMETER", "unused")
override val issue: Issue = Issue(
"UnusedPrivateMember",
Severity.Maintainability,
"Private function is unused and should be removed.",
Debt.FIVE_MINS,
)
@Configuration("unused private function names matching this regex are ignored")
private val allowedNames: Regex by config("", String::toRegex)
override fun visit(root: KtFile) {
super.visit(root)
val visitor = UnusedFunctionVisitor(allowedNames, bindingContext)
root.accept(visitor)
visitor.getUnusedReports(issue).forEach { report(it) }
}
}
private class UnusedFunctionVisitor(
private val allowedNames: Regex,
private val bindingContext: BindingContext,
) : DetektVisitor() {
private val functionDeclarations = mutableMapOf>()
private val functionReferences = mutableMapOf>()
private val invokeOperatorReferences = mutableMapOf>()
private val propertyDelegates = mutableListOf()
@Suppress("ComplexMethod", "LongMethod")
fun getUnusedReports(issue: Issue): List {
val propertyDelegateResultingDescriptors by lazy(LazyThreadSafetyMode.NONE) {
propertyDelegates.flatMap { it.resultingDescriptors() }
}
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 operatorToken = OperatorConventions.getOperationSymbolForName(functionNameAsName)
val referencesViaOperator = if (
isOperator &&
functionNameAsName != OperatorNameConventions.INVOKE
) {
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()
}
} else {
emptyList()
}
directReferences + assignmentReferences + containingReferences
} else if (functionNameAsName == OperatorNameConventions.INVOKE) {
getInvokeReferences(functions)
} 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
}
}
references.isEmpty() -> functions
else -> emptyList()
}
unusedFunctions.map {
CodeSmell(issue, Entity.atName(it), "Private function `$functionName` is unused.")
}
}
}
private fun getInvokeReferences(functions: MutableList) =
functions.flatMap { function ->
val callableDescriptor =
bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, function]
callableDescriptor?.let {
invokeOperatorReferences[it]
}.orEmpty()
}
override fun visitNamedFunction(function: KtNamedFunction) {
if (!isDeclaredInsideAnInterface(function) && function.isPrivate()) {
collectFunction(function)
}
super.visitNamedFunction(function)
}
private fun isDeclaredInsideAnInterface(function: KtNamedFunction) =
function.getStrictParentOfType()?.isInterface() == true
private fun collectFunction(function: KtNamedFunction) {
val name = function.nameAsSafeName.identifier
if (!allowedNames.matches(name)) {
functionDeclarations.getOrPut(name) { mutableListOf() }.add(function)
}
}
private fun KtPropertyDelegate.resultingDescriptors(): List {
val property = this.parent as? KtProperty ?: return emptyList()
val propertyDescriptor =
bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, property] as? PropertyDescriptor
return listOfNotNull(propertyDescriptor?.getter, propertyDescriptor?.setter).mapNotNull {
bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, it]?.resultingDescriptor
}
}
override fun visitPropertyDelegate(delegate: KtPropertyDelegate) {
super.visitPropertyDelegate(delegate)
propertyDelegates.add(delegate)
}
/*
* We need to collect all private function declarations and references to these functions
* for the whole file as Kotlin allows access to private and internal object declarations
* from everywhere in the file.
*/
override fun visitReferenceExpression(expression: KtReferenceExpression) {
super.visitReferenceExpression(expression)
val name = when (expression) {
is KtOperationReferenceExpression -> expression.getReferencedName()
is KtNameReferenceExpression -> expression.getReferencedName()
is KtArrayAccessExpression -> ARRAY_GET_METHOD_NAME
is KtCallExpression -> {
if (bindingContext != BindingContext.EMPTY) {
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
val callableDescriptor = resolvedCall.resultingDescriptor
if ((callableDescriptor.psiElement as? KtNamedFunction)?.isOperator() == true) {
invokeOperatorReferences.getOrPut(callableDescriptor) { mutableListOf() }.add(expression)
}
}
null
}
else -> null
} ?: return
functionReferences.getOrPut(name) { mutableListOf() }.add(expression)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy