![JAR search and dependency download from the Maven repository](/logo.png)
io.gitlab.arturbosch.detekt.rules.style.optional.OptionalUnit.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.optional
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 io.gitlab.arturbosch.detekt.rules.isOverride
import org.jetbrains.kotlin.cfg.WhenChecker
import org.jetbrains.kotlin.js.translate.callTranslator.getReturnType
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.util.getType
import org.jetbrains.kotlin.types.typeUtil.isNothing
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
/**
* It is not necessary to define a return type of `Unit` on functions or to specify a lone Unit statement.
* This rule detects and reports instances where the `Unit` return type is specified on functions and the occurrences
* of a lone Unit statement.
*
*
* fun foo(): Unit {
* return Unit
* }
* fun foo() = Unit
*
* fun doesNothing() {
* Unit
* }
*
*
*
* fun foo() { }
*
* // overridden no-op functions are allowed
* override fun foo() = Unit
*
*/
@Suppress("ViolatesTypeResolutionRequirements")
class OptionalUnit(config: Config = Config.empty) : Rule(config) {
override val issue = Issue(
javaClass.simpleName,
Severity.Style,
"Return type of `Unit` is unnecessary and can be safely removed.",
Debt.FIVE_MINS
)
override fun visitNamedFunction(function: KtNamedFunction) {
val typeReference = function.typeReference
if (typeReference != null) {
checkFunctionWithExplicitReturnType(function, typeReference)
} else if (!function.isOverride()) {
checkFunctionWithInferredReturnType(function)
}
super.visitNamedFunction(function)
}
override fun visitBlockExpression(expression: KtBlockExpression) {
val statements = expression.statements
val lastStatement = statements.lastOrNull() ?: return
statements
.filter {
when {
it !is KtNameReferenceExpression || it.text != UNIT -> false
it != lastStatement || bindingContext == BindingContext.EMPTY -> true
!it.isUsedAsExpression(bindingContext) -> true
else -> {
val prev =
it.siblings(forward = false, withItself = false).firstIsInstanceOrNull()
prev?.getType(bindingContext)?.isUnit() == true && prev.canBeUsedAsValue()
}
}
}
.onEach {
report(
CodeSmell(
issue,
Entity.from(expression),
"A single Unit expression is unnecessary and can safely be removed."
)
)
}
super.visitBlockExpression(expression)
}
private fun KtExpression.canBeUsedAsValue(): Boolean {
return when (this) {
is KtIfExpression -> {
val elseExpression = `else`
if (elseExpression is KtIfExpression) elseExpression.canBeUsedAsValue() else elseExpression != null
}
is KtWhenExpression ->
entries.lastOrNull()?.elseKeyword != null || WhenChecker.getMissingCases(this, bindingContext).isEmpty()
else ->
true
}
}
private fun checkFunctionWithExplicitReturnType(function: KtNamedFunction, typeReference: KtTypeReference) {
val typeElementText = typeReference.typeElement?.text
if (typeElementText == UNIT) {
val initializer = function.initializer
if (initializer?.isGenericOrNothingType() == true) return
report(CodeSmell(issue, Entity.from(typeReference), createMessage(function)))
}
}
private fun checkFunctionWithInferredReturnType(function: KtNamedFunction) {
val referenceExpression = function.bodyExpression as? KtNameReferenceExpression
if (referenceExpression != null && referenceExpression.text == UNIT) {
report(CodeSmell(issue, Entity.from(referenceExpression), createMessage(function)))
}
}
private fun createMessage(function: KtNamedFunction) = "The function ${function.name} " +
"defines a return type of Unit. This is unnecessary and can safely be removed."
private fun KtExpression.isGenericOrNothingType(): Boolean {
if (bindingContext == BindingContext.EMPTY) return false
val isGenericType = getResolvedCall(bindingContext)?.getReturnType()?.isTypeParameter() == true
val isNothingType = getType(bindingContext)?.isNothing() == true
// Either the function initializer returns Nothing or it is a generic function
// into which Unit is passed, but not both.
return (isGenericType && !isNothingType) || (isNothingType && !isGenericType)
}
companion object {
private const val UNIT = "Unit"
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy