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

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

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.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.rules.companionObject
import io.gitlab.arturbosch.detekt.rules.isConstant
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtPrefixExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.isPrivate

/**
 * Classes which implement the `Serializable` interface should also correctly declare a `serialVersionUID`.
 * This rule verifies that a `serialVersionUID` was correctly defined and declared as `private`.
 *
 * [More about `SerialVersionUID`](https://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html)
 *
 * 
 * class IncorrectSerializable : Serializable {
 *
 *     companion object {
 *         val serialVersionUID = 1 // wrong declaration for UID
 *     }
 * }
 * 
 *
 * 
 * class CorrectSerializable : Serializable {
 *
 *     companion object {
 *         const val serialVersionUID = 1L
 *     }
 * }
 * 
 */
@ActiveByDefault(since = "1.16.0")
class SerialVersionUIDInSerializableClass(config: Config = Config.empty) : Rule(config) {

    override val issue = Issue(
        javaClass.simpleName,
        Severity.Warning,
        "A class which implements the Serializable interface does not define a correct serialVersionUID field. " +
            "The serialVersionUID field should be a private constant long value inside a companion object.",
        Debt.FIVE_MINS
    )

    override fun visitClass(klass: KtClass) {
        super.visitClass(klass)
        if (!klass.isInterface() && isImplementingSerializable(klass)) {
            val companionObject = klass.companionObject()
            if (companionObject == null) {
                report(
                    CodeSmell(
                        issue,
                        Entity.atName(klass),
                        klass.getIssueMessage("class")
                    )
                )
            } else {
                val finding = searchSerialVersionUIDFinding(companionObject, klass) ?: return
                reportFinding(finding)
            }
        }
    }

    override fun visitObjectDeclaration(declaration: KtObjectDeclaration) {
        super.visitObjectDeclaration(declaration)
        if (!declaration.isCompanion() && isImplementingSerializable(declaration)) {
            val finding = searchSerialVersionUIDFinding(declaration) ?: return
            reportFinding(finding)
        }
    }

    private fun reportFinding(finding: SerialVersionUIDFindings) {
        report(
            CodeSmell(
                issue,
                Entity.atName(finding.violatingElement),
                finding.issueMsg
            )
        )
    }

    private fun isImplementingSerializable(classOrObject: KtClassOrObject) =
        classOrObject.superTypeListEntries.any { it.text == "Serializable" }

    private fun searchSerialVersionUIDFinding(
        declaration: KtObjectDeclaration,
        parentDeclaration: KtNamedDeclaration = declaration,
    ): SerialVersionUIDFindings? {
        val property = declaration.body?.properties?.firstOrNull { it.name == "serialVersionUID" }
            ?: return SerialVersionUIDFindings(
                parentDeclaration,
                parentDeclaration.getIssueMessage(
                    if (parentDeclaration is KtClass) "class" else "object"
                )
            )
        val isPropertyPrivate = declaration.isPrivate() || property.isPrivate()
        return if (property.isConstant() && isLongProperty(property) && isPropertyPrivate) {
            null
        } else {
            SerialVersionUIDFindings(
                property,
                "The property `serialVersionUID` signature is not correct. `serialVersionUID` should be " +
                    "`private` and `constant` and its type should be `Long`"
            )
        }
    }

    private fun KtNamedDeclaration.getIssueMessage(typeOfDeclaration: String) =
        "The $typeOfDeclaration ${this.nameAsSafeName} " +
            "implements the `Serializable` interface and should thus define a `serialVersionUID`."

    private fun isLongProperty(property: KtProperty) = hasLongType(property) || hasLongAssignment(property)

    private fun hasLongType(property: KtProperty) = property.typeReference?.text == "Long"

    private fun hasLongAssignment(property: KtProperty): Boolean {
        val assignmentText = property.children
            .singleOrNull { it is KtConstantExpression || it is KtPrefixExpression }
            ?.text
        return assignmentText != null && assignmentText.last() == 'L' &&
            assignmentText.substring(0, assignmentText.length - 1).toLongOrNull() != null
    }

    private data class SerialVersionUIDFindings(
        val violatingElement: KtNamedDeclaration,
        val issueMsg: String,
    )
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy