
io.gitlab.arturbosch.detekt.rules.style.optional.OptionalUnit.kt Maven / Gradle / Ivy
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.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.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.callUtil.getType
import org.jetbrains.kotlin.types.typeUtil.isNothing
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
*
*/
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) {
if (function.hasDeclaredReturnType()) {
checkFunctionWithExplicitReturnType(function)
} 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) {
val typeReference = function.typeReference
val typeElementText = typeReference?.typeElement?.text
if (typeElementText == UNIT) {
if (function.initializer.isNothingType()) 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?.isNothingType() =
bindingContext != BindingContext.EMPTY && this?.getType(bindingContext)?.isNothing() == true
companion object {
private const val UNIT = "Unit"
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy