All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.gitlab.arturbosch.detekt.rules.style.VarCouldBeVal.kt Maven / Gradle / Ivy

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.internal.ActiveByDefault
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtQualifiedExpression
import org.jetbrains.kotlin.psi.KtUnaryExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall

private val unaryAssignmentOperators = setOf(KtTokens.MINUSMINUS, KtTokens.PLUSPLUS)

/**
 * Reports var declarations (locally-scoped variables) that could be val, as they are not re-assigned.
 * Val declarations are assign-once (read-only), which makes understanding the current state easier.
 *
 * 
 * fun example() {
 *     var i = 1 // violation: this variable is never re-assigned
 *     val j = i + 1
 * }
 * 
 *
 * 
 * fun example() {
 *     val i = 1
 *     val j = i + 1
 * }
 * 
 */
@ActiveByDefault(since = "1.16.0")
@RequiresTypeResolution
class VarCouldBeVal(config: Config = Config.empty) : Rule(config) {

    override val defaultRuleIdAliases: Set = setOf("CanBeVal")

    override val issue: Issue = Issue(
        "VarCouldBeVal",
        Severity.Maintainability,
        "Var declaration could be val.",
        Debt.FIVE_MINS
    )

    override fun visitNamedFunction(function: KtNamedFunction) {
        if (bindingContext == BindingContext.EMPTY || function.isSomehowNested()) {
            return
        }

        val assignmentVisitor = AssignmentVisitor(bindingContext)
        function.accept(assignmentVisitor)

        assignmentVisitor.getNonReAssignedDeclarations().forEach {
            report(CodeSmell(issue, Entity.from(it), "Variable '${it.nameAsSafeName.identifier}' could be val."))
        }
        super.visitNamedFunction(function)
    }

    private fun KtNamedFunction.isSomehowNested() =
        getStrictParentOfType() != null

    private class AssignmentVisitor(private val bindingContext: BindingContext) : DetektVisitor() {

        private val declarations = mutableSetOf()
        private val assignments = mutableMapOf>()

        fun getNonReAssignedDeclarations(): List {
            return declarations.filterNot { it.hasAssignments() }
        }

        private fun KtNamedDeclaration.hasAssignments(): Boolean {
            val declarationName = nameAsSafeName.toString()
            val assignments = assignments[declarationName]
            if (assignments.isNullOrEmpty()) return false
            val declarationDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, this]
            return assignments.any {
                it.getResolvedCall(bindingContext)?.resultingDescriptor == declarationDescriptor
            }
        }

        override fun visitProperty(property: KtProperty) {
            if (property.isVar) {
                declarations.add(property)
            }
            super.visitProperty(property)
        }

        override fun visitUnaryExpression(expression: KtUnaryExpression) {
            if (expression.operationToken in unaryAssignmentOperators) {
                visitAssignment(expression.baseExpression)
            }
            super.visitUnaryExpression(expression)
        }

        override fun visitBinaryExpression(expression: KtBinaryExpression) {
            if (expression.operationToken in KtTokens.ALL_ASSIGNMENTS) {
                visitAssignment(expression.left)
            }
            super.visitBinaryExpression(expression)
        }

        private fun visitAssignment(assignedExpression: KtExpression?) {
            if (assignedExpression == null) return
            val name = if (assignedExpression is KtQualifiedExpression) {
                assignedExpression.selectorExpression
            } else {
                assignedExpression
            }?.text ?: return
            assignments.getOrPut(name) { mutableSetOf() }.add(assignedExpression)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy