Browse Source

Konsist: check that if `sealed interface` is used in parameter of `Composable`, it has the `@Stable` or `@Immutable` annotation

pull/1731/head
Benoit Marty 11 months ago committed by Benoit Marty
parent
commit
21200df294
  1. 3
      features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinDigit.kt
  2. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
  3. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt
  4. 3
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateContent.kt
  5. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt
  6. 4
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/ButtonVisuals.kt
  7. 2
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt
  8. 2
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt
  9. 2
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt
  10. 2
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSupportingText.kt
  11. 2
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt
  12. 2
      libraries/matrix/api/build.gradle.kts
  13. 3
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt
  14. 2
      libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt
  15. 2
      libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MessageComposerMode.kt
  16. 2
      libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt
  17. 2
      services/apperror/api/build.gradle.kts
  18. 3
      services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorState.kt
  19. 24
      tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt

3
features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinDigit.kt

@ -16,6 +16,9 @@
package io.element.android.features.lockscreen.impl.pin.model package io.element.android.features.lockscreen.impl.pin.model
import androidx.compose.runtime.Immutable
@Immutable
sealed interface PinDigit { sealed interface PinDigit {
data object Empty : PinDigit data object Empty : PinDigit
data class Filled(val value: Char) : PinDigit data class Filled(val value: Char) : PinDigit

2
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt

@ -20,6 +20,7 @@ import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.Uri import android.net.Uri
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -409,6 +410,7 @@ class MessageComposerPresenter @Inject constructor(
} }
} }
@Immutable
sealed interface RoomMemberSuggestion { sealed interface RoomMemberSuggestion {
data object Room : RoomMemberSuggestion data object Room : RoomMemberSuggestion
data class Member(val roomMember: RoomMember) : RoomMemberSuggestion data class Member(val roomMember: RoomMember) : RoomMemberSuggestion

2
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt

@ -32,6 +32,7 @@ import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -103,6 +104,7 @@ fun MessagesReactionButton(
} }
} }
@Immutable
sealed interface MessagesReactionsButtonContent { sealed interface MessagesReactionsButtonContent {
data class Text(val text: String) : MessagesReactionsButtonContent data class Text(val text: String) : MessagesReactionsButtonContent
data class Icon(@DrawableRes val resourceId: Int) : MessagesReactionsButtonContent data class Icon(@DrawableRes val resourceId: Int) : MessagesReactionsButtonContent

3
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateContent.kt

@ -16,6 +16,9 @@
package io.element.android.features.messages.impl.timeline.model.event package io.element.android.features.messages.impl.timeline.model.event
import androidx.compose.runtime.Immutable
@Immutable
sealed interface TimelineItemStateContent : TimelineItemEventContent { sealed interface TimelineItemStateContent : TimelineItemEventContent {
val body: String val body: String
} }

2
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt

@ -16,8 +16,10 @@
package io.element.android.features.messages.impl.timeline.model.event package io.element.android.features.messages.impl.timeline.model.event
import androidx.compose.runtime.Immutable
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@Immutable
sealed interface TimelineItemTextBasedContent : TimelineItemEventContent { sealed interface TimelineItemTextBasedContent : TimelineItemEventContent {
val body: String val body: String
val htmlDocument: Document? val htmlDocument: Document?

4
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/ButtonVisuals.kt

@ -18,14 +18,16 @@ package io.element.android.libraries.designsystem.components.button
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable 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.Button
import io.element.android.libraries.designsystem.theme.components.IconButton 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.IconSource
import io.element.android.libraries.designsystem.theme.components.TextButton 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 { sealed interface ButtonVisuals {
val action: () -> Unit val action: () -> Unit

2
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt

@ -19,6 +19,7 @@ package io.element.android.libraries.designsystem.components.list
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpSize 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. * This is a helper to set default leading and trailing content for [ListItem]s.
*/ */
@Immutable
sealed interface ListItemContent { sealed interface ListItemContent {
/** /**
* Default Switch content for [ListItem]. * Default Switch content for [ListItem].

2
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt

@ -37,6 +37,7 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -238,6 +239,7 @@ private fun ButtonInternal(
} }
} }
@Immutable
sealed interface IconSource { sealed interface IconSource {
val contentDescription: String? val contentDescription: String?

2
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt

@ -23,6 +23,7 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalTextStyle
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -134,6 +135,7 @@ fun ListItem(
/** /**
* The style to use for a [ListItem]. * The style to use for a [ListItem].
*/ */
@Immutable
sealed interface ListItemStyle { sealed interface ListItemStyle {
data object Default : ListItemStyle data object Default : ListItemStyle
data object Primary : ListItemStyle data object Primary : ListItemStyle

2
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSupportingText.kt

@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.ExperimentalTextApi
@ -82,6 +83,7 @@ fun ListSupportingText(
object ListSupportingTextDefaults { object ListSupportingTextDefaults {
/** Specifies the padding to use for the supporting text. */ /** Specifies the padding to use for the supporting text. */
@Immutable
sealed interface Padding { sealed interface Padding {
/** No padding. */ /** No padding. */
data object None : Padding data object None : Padding

2
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt

@ -30,6 +30,7 @@ import androidx.compose.material3.SearchBarColors
import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@ -180,6 +181,7 @@ object ElementSearchBarDefaults {
) )
} }
@Immutable
sealed interface SearchBarResultState<in T> { sealed interface SearchBarResultState<in T> {
/** No search results are available yet (e.g. because the user hasn't entered a search term). */ /** No search results are available yet (e.g. because the user hasn't entered a search term). */
class NotSearching<T> : SearchBarResultState<T> class NotSearching<T> : SearchBarResultState<T>

2
libraries/matrix/api/build.gradle.kts

@ -15,7 +15,7 @@
*/ */
plugins { plugins {
id("io.element.android-library") id("io.element.android-compose-library")
id("kotlin-parcelize") id("kotlin-parcelize")
alias(libs.plugins.anvil) alias(libs.plugins.anvil)
kotlin("plugin.serialization") version "1.9.10" kotlin("plugin.serialization") version "1.9.10"

3
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt

@ -16,6 +16,9 @@
package io.element.android.libraries.matrix.api.room package io.element.android.libraries.matrix.api.room
import androidx.compose.runtime.Immutable
@Immutable
sealed interface MatrixRoomMembersState { sealed interface MatrixRoomMembersState {
data object Unknown : MatrixRoomMembersState data object Unknown : MatrixRoomMembersState
data class Pending(val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState data class Pending(val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState

2
libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt

@ -20,8 +20,10 @@ import android.net.Uri
import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Immutable
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
@Immutable
sealed interface PickerType<Input, Output> { sealed interface PickerType<Input, Output> {
fun getContract(): ActivityResultContract<Input, Output> fun getContract(): ActivityResultContract<Input, Output>
fun getDefaultRequest(): Input fun getDefaultRequest(): Input

2
libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MessageComposerMode.kt

@ -17,11 +17,13 @@
package io.element.android.libraries.textcomposer.model package io.element.android.libraries.textcomposer.model
import android.os.Parcelable 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.EventId
import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@Immutable
sealed interface MessageComposerMode : Parcelable { sealed interface MessageComposerMode : Parcelable {
@Parcelize @Parcelize
data object Normal: MessageComposerMode data object Normal: MessageComposerMode

2
libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt

@ -16,9 +16,11 @@
package io.element.android.libraries.textcomposer.model package io.element.android.libraries.textcomposer.model
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlin.time.Duration import kotlin.time.Duration
@Immutable
sealed interface VoiceMessageState { sealed interface VoiceMessageState {
data object Idle: VoiceMessageState data object Idle: VoiceMessageState

2
services/apperror/api/build.gradle.kts

@ -15,7 +15,7 @@
*/ */
plugins { plugins {
id("io.element.android-library") id("io.element.android-compose-library")
} }
android { android {

3
services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorState.kt

@ -16,6 +16,9 @@
package io.element.android.services.apperror.api package io.element.android.services.apperror.api
import androidx.compose.runtime.Immutable
@Immutable
sealed interface AppErrorState { sealed interface AppErrorState {
data object NoError : AppErrorState data object NoError : AppErrorState

24
tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt

@ -16,11 +16,16 @@
package io.element.android.tests.konsist 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.Konsist
import com.lemonappdev.konsist.api.ext.list.constructors import com.lemonappdev.konsist.api.ext.list.constructors
import com.lemonappdev.konsist.api.ext.list.modifierprovider.withSealedModifier import com.lemonappdev.konsist.api.ext.list.modifierprovider.withSealedModifier
import com.lemonappdev.konsist.api.ext.list.parameters 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.withNameEndingWith
import com.lemonappdev.konsist.api.ext.list.withoutAnnotationOf
import com.lemonappdev.konsist.api.ext.list.withoutConstructors import com.lemonappdev.konsist.api.ext.list.withoutConstructors
import com.lemonappdev.konsist.api.ext.list.withoutName import com.lemonappdev.konsist.api.ext.list.withoutName
import com.lemonappdev.konsist.api.ext.list.withoutParents import com.lemonappdev.konsist.api.ext.list.withoutParents
@ -67,4 +72,23 @@ class KonsistArchitectureTest {
.withoutParents() .withoutParents()
.assertEmpty(additionalMessage = "Sealed class without constructor MUST be sealed interface") .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
}
}
}
} }

Loading…
Cancel
Save