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 Hard Kotlin keywordsnull23 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 Sam-compatible parameters should be lastnull50 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 Companion object methods should be marked with JvmStaticnull89 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 Methods with default parameters should specify JvmOverloadsnull124 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 Methods which throw exceptions should document themnull151 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