Skip to content

Commit

Permalink
Take for loops into account for MultipleContentEmitters rule (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmans0n authored Nov 27, 2023
1 parent 9fad65c commit 4faef6d
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import io.nlopez.rules.core.util.emitsContent
import io.nlopez.rules.core.util.findChildrenByClass
import io.nlopez.rules.core.util.hasReceiverType
import io.nlopez.rules.core.util.isComposable
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.psi.KtFunction

class MultipleContentEmitters : ComposeKtVisitor {
Expand Down Expand Up @@ -69,11 +71,27 @@ class MultipleContentEmitters : ComposeKtVisitor {
companion object {
internal val KtFunction.directUiEmitterCount: Int
get() = bodyBlockExpression?.let { block ->
block.statements
.filterIsInstance<KtCallExpression>()
.count { it.emitsContent }
// If there's content emitted in a for loop, we assume there's at
// least two iterations and thus count any emitters in them as multiple
val forLoopCount = when {
block.forLoopHasUiEmitters -> 2
else -> 0
}
block.directUiEmitterCount + forLoopCount
} ?: 0

internal val KtBlockExpression.forLoopHasUiEmitters: Boolean
get() = statements.filterIsInstance<KtForExpression>().any {
when (val body = it.body) {
is KtBlockExpression -> body.directUiEmitterCount > 0
is KtCallExpression -> body.emitsContent
else -> false
}
}

internal val KtBlockExpression.directUiEmitterCount: Int
get() = statements.filterIsInstance<KtCallExpression>().count { it.emitsContent }

internal fun KtFunction.indirectUiEmitterCount(mapping: Map<KtFunction, Int>): Int {
val bodyBlock = bodyBlockExpression ?: return 0
return bodyBlock.statements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,33 @@ class MultipleContentEmittersCheckTest {
.hasStartSourceLocation(2, 5)
assertThat(errors.first()).hasMessage(MultipleContentEmitters.MultipleContentEmittersDetected)
}

@Test
fun `for loops are captured`() {
@Language("kotlin")
val code = """
@Composable
fun MultipleContent(texts: List<String>, modifier: Modifier = Modifier) {
for (text in texts) {
Text(text)
}
}
@Composable
fun MultipleContent(otherTexts: List<String>, modifier: Modifier = Modifier) {
Text("text 1")
for (otherText in otherTexts) {
Text(otherText)
}
}
""".trimIndent()
val errors = rule.lint(code)
assertThat(errors)
.hasStartSourceLocations(
SourceLocation(2, 5),
SourceLocation(8, 5),
)
for (error in errors) {
assertThat(error).hasMessage(MultipleContentEmitters.MultipleContentEmittersDetected)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,36 @@ class MultipleContentEmittersCheckTest {
),
)
}

@Test
fun `for loops are captured`() {
@Language("kotlin")
val code = """
@Composable
fun MultipleContent(texts: List<String>, modifier: Modifier = Modifier) {
for (text in texts) {
Text(text)
}
}
@Composable
fun MultipleContent(otherTexts: List<String>, modifier: Modifier = Modifier) {
Text("text 1")
for (otherText in otherTexts) {
Text(otherText)
}
}
""".trimIndent()
emittersRuleAssertThat(code).hasLintViolationsWithoutAutoCorrect(
LintViolation(
line = 2,
col = 5,
detail = MultipleContentEmitters.MultipleContentEmittersDetected,
),
LintViolation(
line = 8,
col = 5,
detail = MultipleContentEmitters.MultipleContentEmittersDetected,
),
)
}
}

0 comments on commit 4faef6d

Please sign in to comment.