Konsist: check that if sealed interface is used in parameter of Composable, it has the @Stable or @Immutable annotation
This commit is contained in:
committed by
Benoit Marty
parent
c5f3562454
commit
8b85aa51e2
@@ -16,6 +16,9 @@
|
||||
|
||||
package io.element.android.features.lockscreen.impl.pin.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed interface PinDigit {
|
||||
data object Empty : PinDigit
|
||||
data class Filled(val value: Char) : PinDigit
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -409,6 +410,7 @@ class MessageComposerPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface RoomMemberSuggestion {
|
||||
data object Room : RoomMemberSuggestion
|
||||
data class Member(val roomMember: RoomMember) : RoomMemberSuggestion
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -103,6 +104,7 @@ fun MessagesReactionButton(
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface MessagesReactionsButtonContent {
|
||||
data class Text(val text: String) : MessagesReactionsButtonContent
|
||||
data class Icon(@DrawableRes val resourceId: Int) : MessagesReactionsButtonContent
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed interface TimelineItemStateContent : TimelineItemEventContent {
|
||||
val body: String
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
@Immutable
|
||||
sealed interface TimelineItemTextBasedContent : TimelineItemEventContent {
|
||||
val body: String
|
||||
val htmlDocument: Document?
|
||||
|
||||
@@ -18,14 +18,16 @@ package io.element.android.libraries.designsystem.components.button
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
|
||||
/**
|
||||
* A sealed class that represents the different visual styles that a button can have.
|
||||
* A sealed interface that represents the different visual styles that a button can have.
|
||||
*/
|
||||
@Immutable
|
||||
sealed interface ButtonVisuals {
|
||||
|
||||
val action: () -> Unit
|
||||
|
||||
@@ -19,6 +19,7 @@ package io.element.android.libraries.designsystem.components.list
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
@@ -34,6 +35,7 @@ import io.element.android.libraries.designsystem.theme.components.Text as TextCo
|
||||
/**
|
||||
* This is a helper to set default leading and trailing content for [ListItem]s.
|
||||
*/
|
||||
@Immutable
|
||||
sealed interface ListItemContent {
|
||||
/**
|
||||
* Default Switch content for [ListItem].
|
||||
|
||||
@@ -37,6 +37,7 @@ import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -238,6 +239,7 @@ private fun ButtonInternal(
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface IconSource {
|
||||
val contentDescription: String?
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -134,6 +135,7 @@ fun ListItem(
|
||||
/**
|
||||
* The style to use for a [ListItem].
|
||||
*/
|
||||
@Immutable
|
||||
sealed interface ListItemStyle {
|
||||
data object Default : ListItemStyle
|
||||
data object Primary : ListItemStyle
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.ExperimentalTextApi
|
||||
@@ -82,6 +83,7 @@ fun ListSupportingText(
|
||||
object ListSupportingTextDefaults {
|
||||
|
||||
/** Specifies the padding to use for the supporting text. */
|
||||
@Immutable
|
||||
sealed interface Padding {
|
||||
/** No padding. */
|
||||
data object None : Padding
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.compose.material3.SearchBarColors
|
||||
import androidx.compose.material3.SearchBarDefaults
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -180,6 +181,7 @@ object ElementSearchBarDefaults {
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface SearchBarResultState<in T> {
|
||||
/** No search results are available yet (e.g. because the user hasn't entered a search term). */
|
||||
class NotSearching<T> : SearchBarResultState<T>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
id("io.element.android-compose-library")
|
||||
id("kotlin-parcelize")
|
||||
alias(libs.plugins.anvil)
|
||||
kotlin("plugin.serialization") version "1.9.10"
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed interface MatrixRoomMembersState {
|
||||
data object Unknown : MatrixRoomMembersState
|
||||
data class Pending(val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState
|
||||
|
||||
@@ -20,8 +20,10 @@ import android.net.Uri
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
|
||||
@Immutable
|
||||
sealed interface PickerType<Input, Output> {
|
||||
fun getContract(): ActivityResultContract<Input, Output>
|
||||
fun getDefaultRequest(): Input
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
package io.element.android.libraries.textcomposer.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Immutable
|
||||
sealed interface MessageComposerMode : Parcelable {
|
||||
@Parcelize
|
||||
data object Normal: MessageComposerMode
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
package io.element.android.libraries.textcomposer.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlin.time.Duration
|
||||
|
||||
@Immutable
|
||||
sealed interface VoiceMessageState {
|
||||
data object Idle: VoiceMessageState
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package io.element.android.services.apperror.api
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed interface AppErrorState {
|
||||
data object NoError : AppErrorState
|
||||
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
|
||||
package io.element.android.tests.konsist
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.lemonappdev.konsist.api.Konsist
|
||||
import com.lemonappdev.konsist.api.ext.list.constructors
|
||||
import com.lemonappdev.konsist.api.ext.list.modifierprovider.withSealedModifier
|
||||
import com.lemonappdev.konsist.api.ext.list.parameters
|
||||
import com.lemonappdev.konsist.api.ext.list.withAnnotationOf
|
||||
import com.lemonappdev.konsist.api.ext.list.withNameEndingWith
|
||||
import com.lemonappdev.konsist.api.ext.list.withoutAnnotationOf
|
||||
import com.lemonappdev.konsist.api.ext.list.withoutConstructors
|
||||
import com.lemonappdev.konsist.api.ext.list.withoutName
|
||||
import com.lemonappdev.konsist.api.ext.list.withoutParents
|
||||
@@ -67,4 +72,23 @@ class KonsistArchitectureTest {
|
||||
.withoutParents()
|
||||
.assertEmpty(additionalMessage = "Sealed class without constructor MUST be sealed interface")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Composable MUST not have sealed interface in parameter`() {
|
||||
// List all sealed interface without Immutable nor Stable annotation in the project
|
||||
val forbiddenInterfacesForComposableParameter = Konsist.scopeFromProject()
|
||||
.interfaces()
|
||||
.withSealedModifier()
|
||||
.withoutAnnotationOf(Immutable::class, Stable::class)
|
||||
.map { it.fullyQualifiedName }
|
||||
|
||||
Konsist.scopeFromProject()
|
||||
.functions()
|
||||
.withAnnotationOf(Composable::class)
|
||||
.assertTrue(additionalMessage = "Consider adding the @Immutable or @Stable annotation to the sealed interface") {
|
||||
it.parameters.all { param ->
|
||||
param.type.fullyQualifiedName !in forbiddenInterfacesForComposableParameter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user