| /* |
| * Copyright (C) 2018 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 com.android.tools.metalava |
| |
| import org.junit.Test |
| |
| class KotlinInteropChecksTest : DriverTest() { |
| @Test |
| fun `Hard Kotlin keywords`() { |
| check( |
| extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP), |
| warnings = """ |
| src/test/pkg/Test.java:5: error: Avoid method names that are Kotlin hard keywords ("fun"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword] |
| src/test/pkg/Test.java:6: error: Avoid parameter names that are Kotlin hard keywords ("typealias"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword] |
| src/test/pkg/Test.java:7: error: Avoid field names that are Kotlin hard keywords ("object"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword] |
| """, |
| sourceFiles = arrayOf( |
| java( |
| """ |
| package test.pkg; |
| import androidx.annotation.ParameterName; |
| |
| public class Test { |
| public void fun() { } |
| public void foo(int fun, @ParameterName("typealias") int internalName) { } |
| public Object object = null; |
| } |
| """ |
| ), |
| supportParameterName |
| ) |
| ) |
| } |
| |
| @Test |
| fun `Sam-compatible parameters should be last`() { |
| check( |
| extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP), |
| warnings = """ |
| src/test/pkg/Test.java:18: warning: SAM-compatible parameters (such as parameter 1, "run", in test.pkg.Test.error1) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions [SamShouldBeLast] |
| src/test/pkg/Test.java:19: warning: SAM-compatible parameters (such as parameter 2, "callback", in test.pkg.Test.error2) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions [SamShouldBeLast] |
| 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] |
| """, |
| sourceFiles = arrayOf( |
| java( |
| """ |
| package test.pkg; |
| |
| import java.lang.Runnable; |
| import java.util.concurrent.Executor; |
| import java.util.function.Consumer; |
| |
| public class Test { |
| public void ok1() { } |
| public void ok1(int x) { } |
| public void ok2(int x, int y) { } |
| public void ok3(Runnable run) { } |
| public void ok4(int x, Runnable run) { } |
| public void ok5(Runnable run1, Runnable run2) { } |
| public void ok6(java.util.List list, boolean b) { } |
| // Consumer declares exactly one non-default method (accept), other methods are default. |
| public void ok7(@NonNull String packageName, @NonNull Executor executor, |
| @NonNull Consumer<Boolean> callback) {} |
| public void error1(Runnable run, int x) { } |
| // Executors, while they have a single method are not considered to be SAM that we want to be |
| // the last argument |
| public void error2(@NonNull String packageName, @NonNull Consumer<Boolean> callback, |
| @NonNull Executor executor) {} |
| // Iterables, while they have a single method are not considered to be SAM that we want to be |
| // the last argument |
| public void ok8(Iterable<String> iterable, int x) { } |
| } |
| """ |
| ), |
| kotlin( |
| """ |
| package test.pkg |
| |
| fun ok1(bar: (Int) -> Int) { } |
| fun ok2(foo: Int) { } |
| fun ok3(foo: Int, bar: (Int) -> Int) { } |
| fun ok4(foo: Int, bar: (Int) -> Int, baz: (Int) -> Int) { } |
| fun error(bar: (Int) -> Int, foo: Int) { } |
| """ |
| ) |
| ) |
| ) |
| } |
| |
| @Test |
| fun `Companion object methods should be marked with JvmStatic`() { |
| check( |
| extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP), |
| warnings = """ |
| 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] |
| src/test/pkg/Foo.kt:10: warning: Companion object constants like WRONG2 should be using @JvmField, not @JvmStatic; see https://android.github.io/kotlin-guides/interop.html#companion-constants [MissingJvmstatic] |
| 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] |
| """, |
| sourceFiles = arrayOf( |
| kotlin( |
| """ |
| package test.pkg |
| |
| @SuppressWarnings("all") |
| class Foo { |
| fun ok1() { } |
| companion object { |
| const val INTEGER_ONE = 1 |
| var BIG_INTEGER_ONE = BigInteger.ONE |
| @JvmStatic val WRONG = 2 // not yet flagged |
| @JvmStatic @JvmField val WRONG2 = 2 |
| @JvmField val ok3 = 3 |
| |
| fun missing() { } |
| |
| @JvmStatic |
| fun ok2() { } |
| } |
| } |
| """ |
| ) |
| ) |
| ) |
| } |
| |
| @Test |
| fun `Methods with default parameters should specify JvmOverloads`() { |
| check( |
| extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP), |
| warnings = """ |
| src/test/pkg/Bar.kt:12: 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] |
| """, |
| sourceFiles = arrayOf( |
| kotlin( |
| """ |
| package test.pkg |
| |
| interface Bar { |
| fun ok(int: Int = 0, int2: Int = 0) { } |
| } |
| |
| class Foo { |
| fun ok1() { } |
| fun ok2(int: Int) { } |
| fun ok3(int: Int, int2: Int) { } |
| @JvmOverloads fun ok4(int: Int = 0, int2: Int = 0) { } |
| fun error(int: Int = 0, int2: Int = 0) { } |
| fun String.ok4(int: Int = 0, int2: Int = 0) { } |
| inline fun ok5(int: Int, int2: Int) { } |
| } |
| """ |
| ) |
| ) |
| ) |
| } |
| |
| @Test |
| fun `Methods which throw exceptions should document them`() { |
| check( |
| extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP), |
| warnings = """ |
| 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] |
| 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] |
| 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] |
| 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] |
| """, |
| sourceFiles = arrayOf( |
| kotlin( |
| """ |
| package test.pkg |
| import java.io.FileNotFoundException |
| import java.lang.UnsupportedOperationException |
| |
| class Foo { |
| fun error_throws_multiple_times(x: Int) { |
| if (x < 0) { |
| throw FileNotFoundException("Something") |
| } |
| if (x > 10) { // make sure we don't list this twice |
| throw FileNotFoundException("Something") |
| } |
| } |
| |
| |
| @Throws(Exception::class) |
| fun error_throwsCheckedExceptionWithWrongExceptionClassInThrows(x: Int) { |
| if (x < 0) { |
| throw FileNotFoundException("Something") |
| } |
| } |
| |
| @Throws(FileNotFoundException::class) |
| fun ok_hasThrows1(x: Int) { |
| if (x < 0) { |
| throw FileNotFoundException("Something") |
| } |
| } |
| |
| @Throws(UnsupportedOperationException::class, FileNotFoundException::class) |
| fun ok_hasThrows2(x: Int) { |
| if (x < 0) { |
| throw FileNotFoundException("Something") |
| } |
| } |
| |
| fun error_throwsRuntimeExceptionDocsMissing(x: Int) { |
| if (x < 0) { |
| throw UnsupportedOperationException("Something") |
| } |
| } |
| |
| /** This method throws FileNotFoundException if blah blah blah */ |
| fun error_missingSpecificAnnotation(x: Int) { |
| if (x < 0) { |
| throw UnsupportedOperationException("Something") |
| } |
| } |
| |
| /** This method throws UnsupportedOperationException if blah blah blah */ |
| fun ok_docsPresent(x: Int) { |
| if (x < 0) { |
| throw UnsupportedOperationException("Something") |
| } |
| } |
| |
| fun ok_exceptionCaught(x: Int) { |
| try { |
| if (s.startsWith(" ")) { |
| throw NumberFormatException() |
| } |
| println("Hello") |
| } catch (e: NumberFormatException) {} |
| } |
| |
| fun ok_exceptionCaught2(x: Int) { |
| try { |
| if (s.startsWith(" ")) { |
| throw NumberFormatException() |
| } |
| println("Hello") |
| } catch (e: Exception) {} |
| } |
| |
| // TODO: What about something where you call in Java a method |
| // known to throw something (e.g. Integer.parseInt) and you don't catch it; should you |
| // pass it on? Hard to say; if the logic is complicated it may |
| // be the case that it can never happen, and this might be an annoying false positive. |
| } |
| """ |
| ) |
| ) |
| ) |
| } |
| } |