[go: nahoru, domu]

blob: f63bcb5a3bf2dd6f5436823ec182d27543dc51b1 [file] [log] [blame]
package androidx.compose.plugins.kotlin.analysis
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import androidx.compose.plugins.kotlin.AbstractComposeDiagnosticsTest
import androidx.compose.plugins.kotlin.newConfiguration
import com.intellij.openapi.util.Disposer
class ComposableCheckerTests : AbstractComposeDiagnosticsTest() {
override fun setUp() {
// intentionally don't call super.setUp() here since we are recreating an environment
// every test
System.setProperty("user.dir",
homeDir
)
}
fun doTest(text: String, expectPass: Boolean) {
val disposable = TestDisposable()
val classPath = createClasspath()
val configuration = newConfiguration()
configuration.addJvmClasspathRoots(classPath)
val environment =
KotlinCoreEnvironment.createForTests(
disposable,
configuration,
EnvironmentConfigFiles.JVM_CONFIG_FILES
)
setupEnvironment(environment)
try {
doTest(text, environment)
if (!expectPass) {
throw ExpectedFailureException(
"Test unexpectedly passed, but SHOULD FAIL"
)
}
} catch (e: ExpectedFailureException) {
throw e
} catch (e: Exception) {
if (expectPass) throw Exception(e)
} finally {
Disposer.dispose(disposable)
}
}
class ExpectedFailureException(message: String) : Exception(message)
fun check(expectedText: String) {
doTest(expectedText, true)
}
fun checkFail(expectedText: String) {
doTest(expectedText, false)
}
fun testCfromNC() = check("""
import androidx.compose.*
@Composable fun C() {}
fun <!COMPOSABLE_EXPECTED!>NC<!>() { <!COMPOSABLE_INVOCATION!>C<!>() }
""")
fun testNCfromC() = check("""
import androidx.compose.*
fun NC() {}
@Composable fun C() { NC() }
""")
fun testCfromC() = check("""
import androidx.compose.*
@Composable fun C() {}
@Composable fun C2() { C() }
""")
fun testCinCLambdaArg() = check("""
import androidx.compose.*
@Composable fun C() { }
@Composable fun C2(lambda: @Composable () -> Unit) { lambda() }
@Composable fun C3() {
C2 {
C()
}
}
""")
fun testCinInlinedNCLambdaArg() = check("""
import androidx.compose.*
@Composable fun C() { }
inline fun InlineNC(lambda: () -> Unit) { lambda() }
@Composable fun C3() {
InlineNC {
C()
}
}
""")
fun testCinLambdaArgOfNC() = check("""
import androidx.compose.*
@Composable fun C() { }
fun NC(lambda: () -> Unit) { lambda() }
@Composable fun C3() {
NC {
<!COMPOSABLE_INVOCATION!>C<!>()
}
}
""")
fun testCinLambdaArgOfC() = check("""
import androidx.compose.*
@Composable fun C() { }
@Composable fun C2(lambda: () -> Unit) { lambda() }
@Composable fun C3() {
C2 {
<!COMPOSABLE_INVOCATION!>C<!>()
}
}
""")
fun testCinCPropGetter() = check("""
import androidx.compose.*
@Composable fun C(): Int { return 123 }
@Composable val cProp: Int get() = C()
""")
fun testCinNCPropGetter() = check("""
import androidx.compose.*
@Composable fun C(): Int { return 123 }
val <!COMPOSABLE_EXPECTED!>ncProp<!>: Int get() = <!COMPOSABLE_INVOCATION!>C<!>()
""")
fun testCinTopLevelInitializer() = check("""
import androidx.compose.*
@Composable fun C(): Int { return 123 }
val ncProp: Int = <!COMPOSABLE_INVOCATION!>C<!>()
@Composable val <!COMPOSABLE_PROPERTY_BACKING_FIELD!>cProp<!>: Int = <!COMPOSABLE_INVOCATION!>C<!>()
""")
fun testCTypeAlias() = check("""
import androidx.compose.*
typealias Content = @Composable () -> Unit
@Composable fun C() {}
@Composable fun C2(content: Content) { content() }
@Composable fun C3() {
val inner: Content = { C() }
C2 { C() }
C2 { inner() }
}
""")
fun testPreventedCaptureOnInlineLambda() = check("""
import androidx.compose.*
@Composable inline fun A(
lambda: @ComposableContract(preventCapture=true) () -> Unit
) { if (Math.random() > 0.5) lambda() }
@Composable fun B() {}
@Composable fun C() {
A { <!CAPTURED_COMPOSABLE_INVOCATION!>B<!>() }
}
""")
fun testComposableReporting001() {
checkFail("""
import androidx.compose.*;
@Composable
fun Leaf() {}
fun myStatelessFunctionalComponent() {
Leaf()
}
""")
check("""
import androidx.compose.*;
@Composable
fun Leaf() {}
@Composable
fun myStatelessFunctionalComponent() {
Leaf()
}
@Composable
fun foo() {
myStatelessFunctionalComponent()
}
""")
}
fun testComposableReporting002() {
checkFail("""
import androidx.compose.*;
@Composable
fun Leaf() {}
val myLambda1 = { Leaf() }
val myLambda2: () -> Unit = { Leaf() }
""")
check("""
import androidx.compose.*;
@Composable
fun Leaf() {}
val myLambda1 = @Composable { Leaf() }
val myLambda2: @Composable ()->Unit = { Leaf() }
""")
}
fun testComposableReporting006() {
checkFail("""
import androidx.compose.*;
@Composable
fun Leaf() {}
fun foo() {
val bar = {
Leaf()
}
bar()
System.out.println(bar)
}
""")
check("""
import androidx.compose.*;
@Composable
fun Leaf() {}
@Composable
fun foo() {
val bar = @Composable {
Leaf()
}
bar()
System.out.println(bar)
}
""")
}
fun testComposableReporting007() {
checkFail("""
import androidx.compose.*;
fun foo(children: @Composable ()->Unit) {
<!SVC_INVOCATION!>children<!>()
}
""")
}
fun testComposableReporting008() {
checkFail("""
import androidx.compose.*;
@Composable fun Leaf() {}
fun foo() {
val bar: @Composable ()->Unit = @Composable {
Leaf()
}
<!COMPOSABLE_INVOCATION!>bar<!>()
System.out.println(bar)
}
""")
}
fun testComposableReporting009() {
check("""
import androidx.compose.*;
@Composable fun Leaf() {}
@Composable
fun myStatelessFunctionalComponent() {
Leaf()
}
fun <!COMPOSABLE_EXPECTED!>noise<!>() {
<!COMPOSABLE_INVOCATION!>myStatelessFunctionalComponent<!>()
}
""")
}
fun testComposableReporting017() {
checkFail("""
import androidx.compose.*;
@Composable fun Leaf() {}
@Composable
fun Foo(children: ()->Unit) {
children()
}
@Composable
fun main() {
Foo { Leaf() }
}
""")
check("""
import androidx.compose.*;
@Composable fun Leaf() {}
@Composable
fun Foo(children: ()->Unit) {
children()
}
@Composable
fun main() {
Foo { <!COMPOSABLE_INVOCATION!>Leaf<!>() }
}
""")
}
fun testComposableReporting018() {
checkFail("""
import androidx.compose.*;
@Composable
fun Leaf() {}
fun foo() {
val myVariable: ()->Unit = @Composable { Leaf() }
System.out.println(myVariable)
}
""")
check("""
import androidx.compose.*;
@Composable
fun Leaf() {}
fun foo() {
val myVariable: ()->Unit = <!TYPE_MISMATCH!>@Composable { Leaf() }<!>
System.out.println(myVariable)
}
""")
}
fun testComposableReporting021() {
check("""
import androidx.compose.*;
@Composable
fun Leaf() {}
@Composable
fun foo() {
val myList = listOf(1,2,3,4,5)
myList.forEach @Composable { value: Int ->
Leaf()
System.out.println(value)
}
}
""")
}
fun testComposableReporting022() {
check("""
import androidx.compose.*;
@Composable
fun Leaf() {}
fun <!COMPOSABLE_EXPECTED!>foo<!>() {
val myList = listOf(1,2,3,4,5)
myList.forEach { value: Int ->
<!COMPOSABLE_INVOCATION!>Leaf<!>()
println(value)
}
}
""")
}
fun testComposableReporting023() {
check("""
import androidx.compose.*;
@Composable
fun Leaf() {}
@Composable
fun foo() {
val myList = listOf(1,2,3,4,5)
myList.forEach { value: Int ->
Leaf()
println(value)
}
}
""")
}
fun testComposableReporting024() {
check("""
import androidx.compose.*
var x: (@Composable () -> Unit)? = null
class Foo
fun Foo.setContent(content: @Composable () -> Unit) {
x = content
}
@Composable
fun Leaf() {}
fun Example(foo: Foo) {
foo.setContent { Leaf() }
}
""")
}
fun testComposableReporting024x() {
check("""
import androidx.compose.*
var x: (@Composable () -> Unit)? = null
fun <!COMPOSABLE_EXPECTED!>Example<!>(content: @Composable () -> Unit) {
x = content
<!COMPOSABLE_INVOCATION!>content<!>()
}
""")
}
fun testComposableReporting025() {
check("""
import androidx.compose.*;
@Composable
fun Leaf() {}
@Composable
fun foo() {
listOf(1,2,3,4,5).forEach { Leaf() }
}
""")
}
fun testComposableReporting026() {
check("""
import androidx.compose.*;
@Composable
fun Leaf() {}
@Composable
fun Group(content: @Composable () -> Unit) { content() }
@Composable
fun foo() {
Group {
Leaf()
}
}
""")
}
fun testComposableReporting027() {
check("""
import androidx.compose.*;
@Composable
fun Leaf() {}
@Composable
fun Group(content: @Composable () -> Unit) { content() }
@Composable
fun foo() {
Group {
listOf(1,2,3).forEach {
Leaf()
}
}
}
""")
}
fun testComposableReporting028() {
checkFail("""
import androidx.compose.*;
fun foo(v: @Composable ()->Unit) {
val myVariable: ()->Unit = v
myVariable()
}
""")
check("""
import androidx.compose.*;
fun foo(v: @Composable ()->Unit) {
val myVariable: ()->Unit = <!TYPE_MISMATCH!>v<!>
myVariable()
}
""")
}
fun testComposableReporting030() {
check("""
import androidx.compose.*;
@Composable
fun foo() {
val myVariable: @Composable ()->Unit = {};
myVariable()
}
""")
}
fun testComposableReporting032() {
check("""
import androidx.compose.*;
@Composable
fun MyComposable(children: @Composable ()->Unit) { children() }
@Composable
fun Leaf() {}
@Composable
fun foo() {
MyComposable { Leaf() }
}
""")
}
fun testComposableReporting033() {
check("""
import androidx.compose.*;
@Composable
fun MyComposable(children: @Composable ()->Unit) { children() }
@Composable
fun Leaf() {}
@Composable
fun foo() {
MyComposable(children={ Leaf() })
}
""")
}
fun testComposableReporting034() {
checkFail("""
import androidx.compose.*;
fun identity(f: ()->Unit): ()->Unit { return f; }
@Composable
fun test(f: @Composable ()->Unit) {
val f2: @Composable ()->Unit = identity(f);
f2()
}
""")
check("""
import androidx.compose.*;
fun identity(f: ()->Unit): ()->Unit { return f; }
@Composable
fun test(f: @Composable ()->Unit) {
val f2: @Composable ()->Unit = <!TYPE_MISMATCH!>identity (<!TYPE_MISMATCH!>f<!>)<!>;
f2()
}
""")
}
fun testComposableReporting035() {
check("""
import androidx.compose.*
@Composable
fun Foo(x: String) {
@Composable operator fun String.invoke() {}
x()
}
""")
}
fun testComposableReporting039() {
check(
"""
import androidx.compose.*
fun composeInto(l: @Composable ()->Unit) { System.out.println(l) }
fun Foo() {
composeInto {
Baz()
}
}
fun Bar() {
Foo()
}
@Composable fun Baz() {}
"""
)
}
fun testComposableReporting041() {
check("""
import androidx.compose.*
typealias COMPOSABLE_UNIT_LAMBDA = @Composable () -> Unit
@Composable
fun ComposeWrapperComposable(children: COMPOSABLE_UNIT_LAMBDA) {
MyComposeWrapper {
children()
}
}
@Composable fun MyComposeWrapper(children: COMPOSABLE_UNIT_LAMBDA) {
print(children.hashCode())
}
""")
}
fun testComposableReporting043() {
check("""
import androidx.compose.*
@Composable
fun FancyButton() {}
fun <!COMPOSABLE_EXPECTED!>Noise<!>() {
<!COMPOSABLE_INVOCATION!>FancyButton<!>()
}
""")
}
fun testComposableReporting044() {
check("""
import androidx.compose.*
typealias UNIT_LAMBDA = () -> Unit
@Composable
fun FancyButton() {}
@Composable
fun Noise() {
FancyButton()
}
""")
}
fun testComposableReporting045() {
check("""
import androidx.compose.*;
@Composable
fun foo() {
val bar = @Composable {}
bar()
System.out.println(bar)
}
""")
}
fun testComposableReporting048() {
// Type inference for non-null @Composable lambdas
checkFail("""
import androidx.compose.*
val lambda: @Composable (() -> Unit)? = null
@Composable
fun Foo() {
// Should fail as null cannot be coerced to non-null
Bar(lambda)
Bar(null)
Bar {}
}
@Composable
fun Bar(child: @Composable () -> Unit) {
child()
}
""")
// Type inference for nullable @Composable lambdas, with no default value
check("""
import androidx.compose.*
val lambda: @Composable (() -> Unit)? = null
@Composable
fun Foo() {
Bar(lambda)
Bar(null)
Bar {}
}
@Composable
fun Bar(child: @Composable (() -> Unit)?) {
child?.invoke()
}
""")
// Type inference for nullable @Composable lambdas, with a nullable default value
check("""
import androidx.compose.*
val lambda: @Composable (() -> Unit)? = null
@Composable
fun Foo() {
Bar()
Bar(lambda)
Bar(null)
Bar {}
}
@Composable
fun Bar(child: @Composable (() -> Unit)? = null) {
child?.invoke()
}
""")
// Type inference for nullable @Composable lambdas, with a non-null default value
check("""
import androidx.compose.*
val lambda: @Composable (() -> Unit)? = null
@Composable
fun Foo() {
Bar()
Bar(lambda)
Bar(null)
Bar {}
}
@Composable
fun Bar(child: @Composable (() -> Unit)? = {}) {
child?.invoke()
}
""")
}
fun testComposableReporting049() {
check("""
import androidx.compose.*
fun foo(<!WRONG_ANNOTATION_TARGET!>@Composable<!> bar: ()->Unit) {
println(bar)
}
""")
}
fun testComposableReporting050() {
check("""
import androidx.compose.*;
@Composable val foo: Int get() = 123
fun <!COMPOSABLE_EXPECTED!>App<!>() {
<!COMPOSABLE_INVOCATION!>foo<!>
}
""")
check("""
import androidx.compose.*;
@Composable val foo: Int get() = 123
@Composable
fun App() {
println(foo)
}
""")
}
fun testComposableReporting051() {
checkFail("""
import androidx.compose.*;
class A {
@Composable val bar get() = 123
}
@Composable val A.bam get() = 123
fun App() {
val a = A()
a.bar
}
""")
checkFail("""
import androidx.compose.*;
class A {
@Composable val bar get() = 123
}
@Composable val A.bam get() = 123
fun App() {
val a = A()
a.bam
}
""")
check("""
import androidx.compose.*;
class A {
@Composable val bar get() = 123
}
@Composable val A.bam get() = 123
@Composable
fun App() {
val a = A()
a.bar
a.bam
with(a) {
bar
bam
}
}
""")
}
fun testComposableReporting052() {
check("""
import androidx.compose.*;
@Composable fun Foo() {}
val <!COMPOSABLE_EXPECTED!>bam<!>: Int get() {
<!COMPOSABLE_INVOCATION!>Foo<!>()
return 123
}
""")
check("""
import androidx.compose.*;
@Composable fun Foo() {}
@Composable val bam: Int get() {
Foo()
return 123
}
""")
}
fun testComposableReporting053() {
check("""
import androidx.compose.*;
@Composable fun foo(): Int = 123
fun <!COMPOSABLE_EXPECTED!>App<!>() {
val x = <!COMPOSABLE_INVOCATION!>foo<!>()
print(x)
}
""")
}
fun testComposableReporting054() {
check("""
import androidx.compose.*;
@Composable fun Foo() {}
val <!COMPOSABLE_EXPECTED!>y<!>: Any get() =
<!COMPOSABLE_INVOCATION!>state<!> { 1 }
fun App() {
val x = object {
val <!COMPOSABLE_EXPECTED!>a<!> get() =
<!COMPOSABLE_INVOCATION!>state<!> { 2 }
@Composable val c get() = state { 4 }
@Composable fun bar() { Foo() }
fun <!COMPOSABLE_EXPECTED!>foo<!>() {
<!COMPOSABLE_INVOCATION!>Foo<!>()
}
}
class Bar {
val <!COMPOSABLE_EXPECTED!>b<!> get() =
<!COMPOSABLE_INVOCATION!>state<!> { 6 }
@Composable val c get() = state { 7 }
}
fun <!COMPOSABLE_EXPECTED!>Bam<!>() {
<!COMPOSABLE_INVOCATION!>Foo<!>()
}
@Composable fun Boo() {
Foo()
}
print(x)
}
""")
}
fun testComposableReporting055() {
check("""
import androidx.compose.*;
@Composable fun Foo() {}
@Composable fun App() {
val x = object {
val <!COMPOSABLE_EXPECTED!>a<!> get() = <!COMPOSABLE_INVOCATION!>state<!> { 2 }
@Composable val c get() = state { 4 }
fun <!COMPOSABLE_EXPECTED!>foo<!>() {
<!COMPOSABLE_INVOCATION!>Foo<!>()
}
@Composable fun bar() { Foo() }
}
class Bar {
val <!COMPOSABLE_EXPECTED!>b<!> get() = <!COMPOSABLE_INVOCATION!>state<!> { 6 }
@Composable val c get() = state { 7 }
}
fun <!COMPOSABLE_EXPECTED!>Bam<!>() {
<!COMPOSABLE_INVOCATION!>Foo<!>()
}
@Composable fun Boo() {
Foo()
}
print(x)
}
""")
}
fun testComposableReporting057() {
check("""
import androidx.compose.*;
@Composable fun App() {
val x = object {
val b = state { 3 }
}
class Bar {
val a = <!COMPOSABLE_INVOCATION!>state<!> { 5 }
}
print(x)
}
""")
}
}