Home | History | Annotate | Download | only in metalava
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tools.metalava
     18 
     19 import org.junit.Test
     20 
     21 class KotlinInteropChecksTest : DriverTest() {
     22     @Test
     23     fun `Hard Kotlin keywords`() {
     24         check(
     25             extraArguments = arrayOf("--check-kotlin-interop"),
     26             warnings = """
     27                 src/test/pkg/Test.java:5: warning: Avoid method names that are Kotlin hard keywords ("fun"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword:141]
     28                 src/test/pkg/Test.java:6: warning: Avoid parameter names that are Kotlin hard keywords ("typealias"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword:141]
     29                 src/test/pkg/Test.java:7: warning: Avoid field names that are Kotlin hard keywords ("object"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword:141]
     30                 """,
     31             sourceFiles = *arrayOf(
     32                 java(
     33                     """
     34                     package test.pkg;
     35                     import androidx.annotation.ParameterName;
     36 
     37                     public class Test {
     38                         public void fun() { }
     39                         public void foo(int fun, @ParameterName("typealias") int internalName) { }
     40                         public Object object = null;
     41                     }
     42                     """
     43                 ),
     44                 supportParameterName
     45             )
     46         )
     47     }
     48 
     49     @Test
     50     fun `Sam-compatible parameters should be last`() {
     51         check(
     52             extraArguments = arrayOf("--check-kotlin-interop"),
     53             warnings = """
     54                 src/test/pkg/Test.java:10: warning: SAM-compatible parameters (such as parameter 1, "run", in test.pkg.Test.error) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions [SamShouldBeLast:142]
     55                 src/test/pkg/test.kt:7: warning: lambda parameters (such as parameter 1, "bar", in test.pkg.TestKt.error) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions [SamShouldBeLast:142]
     56                 """,
     57             sourceFiles = *arrayOf(
     58                 java(
     59                     """
     60                     package test.pkg;
     61                     public class Test {
     62                         public void ok1() { }
     63                         public void ok1(int x) { }
     64                         public void ok2(int x, int y) { }
     65                         public void ok3(Runnable run) { }
     66                         public void ok4(int x, Runnable run) { }
     67                         public void ok5(Runnable run1, Runnable run2) { }
     68                         public void ok6(java.util.List list, boolean b) { }
     69                         public void error(Runnable run, int x) { }
     70                     }
     71                     """
     72                 ),
     73                 kotlin(
     74                     """
     75                     package test.pkg
     76 
     77                     fun ok1(bar: (Int) -> Int) { }
     78                     fun ok2(foo: Int) { }
     79                     fun ok3(foo: Int, bar: (Int) -> Int) { }
     80                     fun ok4(foo: Int, bar: (Int) -> Int, baz: (Int) -> Int) { }
     81                     fun error(bar: (Int) -> Int, foo: Int) { }
     82                 """
     83                 )
     84             )
     85         )
     86     }
     87 
     88     @Test
     89     fun `Companion object methods should be marked with JvmStatic`() {
     90         check(
     91             extraArguments = arrayOf("--check-kotlin-interop"),
     92             warnings = """
     93                 src/test/pkg/Foo.kt:7: warning: Companion object constants like INTEGER_ONE should be marked @JvmField for Java interoperability; see https://android.github.io/kotlin-guides/interop.html#companion-constants [MissingJvmstatic:143]
     94                 src/test/pkg/Foo.kt:13: warning: Companion object methods like missing should be marked @JvmStatic for Java interoperability; see https://android.github.io/kotlin-guides/interop.html#companion-functions [MissingJvmstatic:143]
     95                 """,
     96             sourceFiles = *arrayOf(
     97                 kotlin(
     98                     """
     99                     package test.pkg
    100 
    101                     @SuppressWarnings("all")
    102                     class Foo {
    103                         fun ok1() { }
    104                         companion object {
    105                             const val INTEGER_ONE = 1
    106                             var BIG_INTEGER_ONE = BigInteger.ONE
    107                             @JvmStatic val WRONG = 2 // not yet flagged
    108                             @JvmStatic @JvmField val WRONG2 = 2 // not yet flagged
    109                             @JvmField val ok3 = 3
    110 
    111                             fun missing() { }
    112 
    113                             @JvmStatic
    114                             fun ok2() { }
    115                         }
    116                     }
    117                     """
    118                 )
    119             )
    120         )
    121     }
    122 
    123     @Test
    124     fun `Methods with default parameters should specify JvmOverloads`() {
    125         check(
    126             extraArguments = arrayOf("--check-kotlin-interop"),
    127             warnings = """
    128                 src/test/pkg/Foo.kt:8: warning: A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults [MissingJvmstatic:143]
    129                 """,
    130             sourceFiles = *arrayOf(
    131                 kotlin(
    132                     """
    133                     package test.pkg
    134 
    135                     class Foo {
    136                         fun ok1() { }
    137                         fun ok2(int: Int) { }
    138                         fun ok3(int: Int, int2: Int) { }
    139                         @JvmOverloads fun ok4(int: Int = 0, int2: Int = 0) { }
    140                         fun error(int: Int = 0, int2: Int = 0) { }
    141                         fun String.ok4(int: Int = 0, int2: Int = 0) { }
    142                         inline fun ok5(int: Int, int2: Int) { }
    143                     }
    144                     """
    145                 )
    146             )
    147         )
    148     }
    149 
    150     @Test
    151     fun `Methods which throw exceptions should document them`() {
    152         check(
    153             extraArguments = arrayOf("--check-kotlin-interop"),
    154             warnings = """
    155                 src/test/pkg/Foo.kt:6: error: Method Foo.error_throws_multiple_times appears to be throwing java.io.FileNotFoundException; this should be recorded with a @Throws annotation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions [DocumentExceptions:145]
    156                 src/test/pkg/Foo.kt:16: error: Method Foo.error_throwsCheckedExceptionWithWrongExceptionClassInThrows appears to be throwing java.io.FileNotFoundException; this should be recorded with a @Throws annotation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions [DocumentExceptions:145]
    157                 src/test/pkg/Foo.kt:37: error: Method Foo.error_throwsRuntimeExceptionDocsMissing appears to be throwing java.lang.UnsupportedOperationException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions [DocumentExceptions:145]
    158                 src/test/pkg/Foo.kt:43: error: Method Foo.error_missingSpecificAnnotation appears to be throwing java.lang.UnsupportedOperationException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions [DocumentExceptions:145]
    159                 """,
    160             sourceFiles = *arrayOf(
    161                 kotlin(
    162                     """
    163                     package test.pkg
    164                     import java.io.FileNotFoundException
    165                     import java.lang.UnsupportedOperationException
    166 
    167                     class Foo {
    168                         fun error_throws_multiple_times(x: Int) {
    169                             if (x < 0) {
    170                                 throw java.io.FileNotFoundException("Something")
    171                             }
    172                             if (x > 10) { // make sure we don't list this twice
    173                                 throw FileNotFoundException("Something")
    174                             }
    175                         }
    176 
    177 
    178                         @Throws(Exception::class)
    179                         fun error_throwsCheckedExceptionWithWrongExceptionClassInThrows(x: Int) {
    180                             if (x < 0) {
    181                                 throw java.io.FileNotFoundException("Something")
    182                             }
    183                         }
    184 
    185                         @Throws(FileNotFoundException::class)
    186                         fun ok_hasThrows1(x: Int) {
    187                             if (x < 0) {
    188                                 throw java.io.FileNotFoundException("Something")
    189                             }
    190                         }
    191 
    192                         @Throws(UnsupportedOperationException::class, FileNotFoundException::class)
    193                         fun ok_hasThrows2(x: Int) {
    194                             if (x < 0) {
    195                                 throw java.io.FileNotFoundException("Something")
    196                             }
    197                         }
    198 
    199                         fun error_throwsRuntimeExceptionDocsMissing(x: Int) {
    200                             if (x < 0) {
    201                                 throw UnsupportedOperationException("Something")
    202                             }
    203                         }
    204 
    205                         /** This method throws FileNotFoundException if blah blah blah */
    206                         fun error_missingSpecificAnnotation(x: Int) {
    207                             if (x < 0) {
    208                                 throw UnsupportedOperationException("Something")
    209                             }
    210                         }
    211 
    212                         /** This method throws UnsupportedOperationException if blah blah blah */
    213                         fun ok_docsPresent(x: Int) {
    214                             if (x < 0) {
    215                                 throw UnsupportedOperationException("Something")
    216                             }
    217                         }
    218 
    219                         fun ok_exceptionCaught(x: Int) {
    220                             try {
    221                                 if (s.startsWith(" ")) {
    222                                     throw NumberFormatException()
    223                                 }
    224                                 println("Hello")
    225                             } catch (e: NumberFormatException) {}
    226                         }
    227 
    228                         fun ok_exceptionCaught2(x: Int) {
    229                             try {
    230                                 if (s.startsWith(" ")) {
    231                                     throw NumberFormatException()
    232                                 }
    233                                 println("Hello")
    234                             } catch (e: Exception) {}
    235                         }
    236 
    237                         // TODO: What about something where you call in Java a method
    238                         // known to throw something (e.g. Integer.parseInt) and you don't catch it; should you
    239                         // pass it on? Hard to say; if the logic is complicated it may
    240                         // be the case that it can never happen, and this might be an annoying false positive.
    241                     }
    242                     """
    243                 )
    244             )
    245         )
    246     }
    247 }
    248