[go: nahoru, domu]

blob: 7390a5e04a26fed32a2a42fc14ad775de886eef0 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.plugins.kotlin
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtFunctionLiteral
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.resolve.BindingContext
class ScopeComposabilityTests : AbstractCodegenTest() {
fun testNormalFunctions() = assertComposability(
"""
import androidx.compose.*
fun Foo() {
<normal>
}
class Bar {
fun bam() { <normal> }
val baz: Int get() { <normal>return 123 }
}
"""
)
fun testPropGetter() = assertComposability(
"""
import androidx.compose.*
val baz: Int get() { <normal>return 123 }
"""
)
fun testBasicComposable() = assertComposability(
"""
import androidx.compose.*
@Composable
fun Foo() {
<composable>
}
"""
)
fun testBasicComposable2() = assertComposability(
"""
import androidx.compose.*
val foo = @Composable { <composable> }
@Composable
fun Bar() {
<composable>
fun bam() { <normal> }
val x = { <normal> }
val y = @Composable { <composable> }
@Composable fun z() { <composable> }
}
"""
)
// TODO(b/147250515): get inlined lambdas to analyze correctly
fun xtestBasicComposable3() = assertComposability(
"""
import androidx.compose.*
@Composable
fun Bar() {
<composable>
listOf(1, 2, 3).forEach {
<composable>
}
}
"""
)
fun testBasicComposable4() = assertComposability(
"""
import androidx.compose.*
@Composable fun Wrap(block: @Composable () -> Unit) { block() }
@Composable
fun Bar() {
<composable>
Wrap {
<composable>
Wrap {
<composable>
}
}
}
"""
)
fun testBasicComposable5() = assertComposability(
"""
import androidx.compose.*
@Composable fun Callback(block: () -> Unit) { block() }
@Composable
fun Bar() {
<composable>
Callback {
<normal>
}
}
"""
)
fun testBasicComposable6() = assertComposability(
"""
import androidx.compose.*
fun kickOff(block: @Composable () -> Unit) { }
fun Bar() {
<normal>
kickOff {
<composable>
}
}
"""
)
private fun <T> setup(block: () -> T): T {
return block()
}
fun assertComposability(srcText: String) = setup {
val (text, carets) = extractCarets(srcText)
val environment = myEnvironment ?: error("Environment not initialized")
val ktFile = KtPsiFactory(environment.project).createFile(text)
val bindingContext = JvmResolveUtil.analyze(
ktFile,
environment
).bindingContext
carets.forEachIndexed { index, (offset, marking) ->
val composable = composabiliityAtOffset(bindingContext, ktFile, offset)
when (marking) {
"<composable>" -> assertTrue("index: $index", composable)
"<normal>" -> assertTrue(
"index: $index",
!composable
)
else -> error("Composability of $marking not recognized.")
}
}
}
private val callPattern = Regex("(<marked>)|(<inferred>)|(<normal>)")
private fun extractCarets(text: String): Pair<String, List<Pair<Int, String>>> {
val indices = mutableListOf<Pair<Int, String>>()
var offset = 0
val src = callPattern.replace(text) {
indices.add(it.range.first - offset to it.value)
offset += it.range.last - it.range.first + 1
""
}
return src to indices
}
private fun composabiliityAtOffset(
bindingContext: BindingContext,
jetFile: KtFile,
index: Int
): Boolean {
val element = jetFile.findElementAt(index)!!
return element.getNearestComposability(bindingContext)
}
}
fun PsiElement?.getNearestComposability(
bindingContext: BindingContext
): Boolean {
var node: PsiElement? = this
while (node != null) {
when (node) {
is KtFunctionLiteral -> {
// keep going, as this is a "KtFunction", but we actually want the
// KtLambdaExpression
}
is KtLambdaExpression,
is KtFunction,
is KtPropertyAccessor,
is KtProperty -> {
val descriptor = bindingContext[BindingContext.FUNCTION, node]
if (descriptor == null) {
return false
}
return descriptor.allowsComposableCalls(bindingContext)
}
}
node = node.parent as? KtElement
}
return false
}