Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ streamProject {

coverage {
includedModules = setOf("stream-android-core")
sonarCoverageExclusions = setOf(
"**/lint/**",
)
}
}

Expand Down
7 changes: 3 additions & 4 deletions stream-android-core-lint/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import com.vanniktech.maven.publish.JavadocJar
import com.vanniktech.maven.publish.KotlinJvm

plugins {
libs.plugins.stream.java.library
alias(libs.plugins.stream.java.library)
alias(libs.plugins.jetbrains.kotlin.jvm)
alias(libs.plugins.maven.publish)
alias(libs.plugins.dokka)
}


dependencies {
compileOnly(libs.lint.api)
compileOnly(libs.lint.checks)
Expand All @@ -28,12 +27,12 @@ mavenPublishing {
coordinates(
groupId = io.getstream.core.Configuration.artifactGroup,
artifactId = "stream-android-core-lint",
version = rootProject.version.toString()
version = rootProject.version.toString(),
)
configure(
KotlinJvm(
javadocJar = JavadocJar.Dokka("dokkaJavadoc"),
sourcesJar = true,
)
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.android.core.lint

import com.android.tools.lint.client.api.IssueRegistry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.android.core.lint.detectors

import com.android.tools.lint.client.api.UElementHandler
Expand Down Expand Up @@ -229,20 +230,20 @@ class ExposeAsStateFlowDetector : Detector(), Detector.UastScanner {
briefDescription = "Expose MutableStateFlow as read-only via asStateFlow()",
explanation =
"""
Exposing a MutableStateFlow directly (even when typed as StateFlow) allows consumers to downcast
and mutate your internal state. Wrap the internal MutableStateFlow with asStateFlow() when you
expose it.
Problematic:
private val _state = MutableStateFlow(initial)
val state: StateFlow<State> = _state
val state: StateFlow<State> get() = _state
Correct:
private val _state = MutableStateFlow(initial)
val state: StateFlow<State> = _state.asStateFlow()
val state: StateFlow<State> get() = _state.asStateFlow()
"""
Exposing a MutableStateFlow directly (even when typed as StateFlow) allows consumers to downcast
and mutate your internal state. Wrap the internal MutableStateFlow with asStateFlow() when you
expose it.

Problematic:
private val _state = MutableStateFlow(initial)
val state: StateFlow<State> = _state
val state: StateFlow<State> get() = _state

Correct:
private val _state = MutableStateFlow(initial)
val state: StateFlow<State> = _state.asStateFlow()
val state: StateFlow<State> get() = _state.asStateFlow()
"""
.trimIndent(),
category = Category.CORRECTNESS,
priority = 7,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.android.core.lint.detectors

import com.android.tools.lint.client.api.UElementHandler
Expand Down Expand Up @@ -373,13 +374,13 @@ class KeepInstanceDetector : Detector(), Detector.UastScanner {
"Comma-separated FQNs of stateful types that must not be used as temporaries",
explanation =
"""
Any constructor/factory call whose expression type is the same as, or a subtype of, one of these
fully-qualified names will be treated as a stateful instance that needs to be kept (stored or returned),
not created and immediately used or discarded.
Any constructor/factory call whose expression type is the same as, or a subtype of, one of these
fully-qualified names will be treated as a stateful instance that needs to be kept (stored or returned),
not created and immediately used or discarded.

Example:
com.example.SomeType, io.getstream.video.android.core.StreamClient
"""
Example:
com.example.SomeType, io.getstream.video.android.core.StreamClient
"""
.trimIndent(),
)

Expand All @@ -391,32 +392,32 @@ class KeepInstanceDetector : Detector(), Detector.UastScanner {
"Don’t use stateful instances as temporaries or discard them",
explanation =
"""
Types that expose observable or long-lived state (for example, via StateFlow) must be kept.
Creating an instance inline and immediately chaining a call, or creating it as a standalone
expression and discarding the value, can stop updates from being delivered and may leak resources.

Flags:
• SomeType(...).state.collect { ... } // calling members on a temporary instance
• SomeType(...) // created and discarded

How to fix:
• Store the instance in a variable and use it, or
• Return the instance from the current function.

Correct:
val client = StreamClient(config)
client.connectionState.collect { /* ... */ }

Problematic:
StreamClient(config).connectionState.collect { /* ... */ }

Configuration:
You can configure which types are considered stateful via lint.xml:
<issue id="NotKeepingInstance">
<option name="keepInstanceOf"
value="com.example.SomeType, io.getstream.video.android.core.StreamClient"/>
</issue>
"""
Types that expose observable or long-lived state (for example, via StateFlow) must be kept.
Creating an instance inline and immediately chaining a call, or creating it as a standalone
expression and discarding the value, can stop updates from being delivered and may leak resources.

Flags:
• SomeType(...).state.collect { ... } // calling members on a temporary instance
• SomeType(...) // created and discarded

How to fix:
• Store the instance in a variable and use it, or
• Return the instance from the current function.

Correct:
val client = StreamClient(config)
client.connectionState.collect { /* ... */ }

Problematic:
StreamClient(config).connectionState.collect { /* ... */ }

Configuration:
You can configure which types are considered stateful via lint.xml:
<issue id="NotKeepingInstance">
<option name="keepInstanceOf"
value="com.example.SomeType, io.getstream.video.android.core.StreamClient"/>
</issue>
"""
.trimIndent(),
category = Category.CORRECTNESS,
priority = 7,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.android.core.lint.detectors

import com.android.tools.lint.client.api.UElementHandler
Expand Down Expand Up @@ -195,9 +196,9 @@ class MustBeInternalDetector : Detector(), Detector.UastScanner {
"Comma-separated package **glob** patterns that represent internal packages; declarations here must not be public.",
explanation =
"""
Supports wildcards: '*' matches any sequence (including dots), '?' matches a single char. " +
Examples: 'io.getstream.core.internal', 'io.getstream.*.internal', 'com.example.internal*'."
"""
Supports wildcards: '*' matches any sequence (including dots), '?' matches a single char. " +
Examples: 'io.getstream.core.internal', 'io.getstream.*.internal', 'com.example.internal*'."
"""
.trimIndent(),
)

Expand All @@ -208,9 +209,9 @@ class MustBeInternalDetector : Detector(), Detector.UastScanner {
briefDescription = "Disallow `public` in internal packages",
explanation =
"""
Declarations located in packages marked as internal must not be `public`. \
Use `internal` (preferred) or `private`.
"""
Declarations located in packages marked as internal must not be `public`. \
Use `internal` (preferred) or `private`.
"""
.trimIndent(),
category = Category.CORRECTNESS,
priority = 7,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.android.core.lint.detectors

import com.android.tools.lint.client.api.UElementHandler
Expand Down Expand Up @@ -230,12 +231,12 @@ class StreamApiExplicitMarkerDetector : Detector(), Detector.UastScanner {
description = "Comma-separated package **glob** patterns where the rule applies.",
explanation =
"""
Supports wildcards: '*' (any sequence) and '?' (single char).
Examples:
- 'io.getstream.android.core.api'
- 'io.getstream.android.core.*.api'
- 'io.getstream.android.*'
"""
Supports wildcards: '*' (any sequence) and '?' (single char).
Examples:
- 'io.getstream.android.core.api'
- 'io.getstream.android.core.*.api'
- 'io.getstream.android.*'
"""
.trimIndent(),
)

Expand All @@ -245,8 +246,8 @@ class StreamApiExplicitMarkerDetector : Detector(), Detector.UastScanner {
description = "Comma-separated package **glob** patterns to exclude from the rule.",
explanation =
"""
Same glob syntax as 'packages'. Evaluated after includes.
"""
Same glob syntax as 'packages'. Evaluated after includes.
"""
.trimIndent(),
)

Expand All @@ -259,9 +260,9 @@ class StreamApiExplicitMarkerDetector : Detector(), Detector.UastScanner {
"StreamApiExplicitMarkerMissing",
"Public API must be explicitly marked",
"""
To prevent accidental exposure, all top-level public declarations must be explicitly \
marked as @StreamPublishedApi (allowed to leak) or @StreamInternalApi (not allowed to leak).
"""
To prevent accidental exposure, all top-level public declarations must be explicitly \
marked as @StreamPublishedApi (allowed to leak) or @StreamInternalApi (not allowed to leak).
"""
.trimIndent(),
Category.CORRECTNESS,
7,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.android.core.lint.detectors

import com.android.tools.lint.client.api.UElementHandler
Expand Down Expand Up @@ -116,11 +117,11 @@ class SuspendRunCatchingDetector : Detector(), Detector.UastScanner {
briefDescription = "Use runCatchingCancellable in suspend contexts",
explanation =
"""
Using `kotlin.runCatching { ... }` inside a suspend \
function or a suspend lambda, cancellation is not propagated as expected. \
Prefer `runCatchingCancellable { ... }`, which rethrows `CancellationException` while \
still returning `Result` for other failures.
"""
Using `kotlin.runCatching { ... }` inside a suspend \
function or a suspend lambda, cancellation is not propagated as expected. \
Prefer `runCatchingCancellable { ... }`, which rethrows `CancellationException` while \
still returning `Result` for other failures.
"""
.trimIndent(),
category = Category.CORRECTNESS,
priority = 7,
Expand Down