Merge changes Ibbbec9ca,I25cff8e2
* changes:
Adding a sceleton KotlinStubWriter.
Add an option --kotlin-stubs that will gate kotlin stub generation
diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt
index 09f863e..88626fc 100644
--- a/src/main/java/com/android/tools/metalava/Options.kt
+++ b/src/main/java/com/android/tools/metalava/Options.kt
@@ -74,6 +74,7 @@
const val ARG_EXACT_API = "--exact-api"
const val ARG_STUBS = "--stubs"
const val ARG_DOC_STUBS = "--doc-stubs"
+const val ARG_KOTLIN_STUBS = "--kotlin-stubs"
const val ARG_STUBS_SOURCE_LIST = "--write-stubs-source-list"
const val ARG_DOC_STUBS_SOURCE_LIST = "--write-doc-stubs-source-list"
const val ARG_PROGUARD = "--proguard"
@@ -391,6 +392,9 @@
* other tools like javac/javadoc using the special @-syntax. */
var docStubsSourceList: File? = null
+ /** Whether code compiled from Kotlin should be emitted as .kt stubs instead of .java stubs */
+ var kotlinStubs = false
+
/** Proguard Keep list file to write */
var proguard: File? = null
@@ -786,6 +790,7 @@
ARG_STUBS, "-stubs" -> stubsDir = stringToNewDir(getValue(args, ++index))
ARG_DOC_STUBS -> docStubsDir = stringToNewDir(getValue(args, ++index))
+ ARG_KOTLIN_STUBS -> kotlinStubs = true
ARG_STUBS_SOURCE_LIST -> stubsSourceList = stringToNewFile(getValue(args, ++index))
ARG_DOC_STUBS_SOURCE_LIST -> docStubsSourceList = stringToNewFile(getValue(args, ++index))
@@ -2082,6 +2087,8 @@
"indicate that an element is recently marked as non null, whereas in the documentation stubs we'll " +
"just list this as @NonNull. Another difference is that @doconly elements are included in " +
"documentation stubs, but not regular stubs, etc.",
+ ARG_KOTLIN_STUBS, "[CURRENTLY EXPERIMENTAL] If specified, stubs generated from Kotlin source code will " +
+ "be written in Kotlin rather than the Java programming language.",
ARG_INCLUDE_ANNOTATIONS, "Include annotations such as @Nullable in the stub files.",
ARG_EXCLUDE_ANNOTATIONS, "Exclude annotations such as @Nullable from the stub files; the default.",
"$ARG_PASS_THROUGH_ANNOTATION <annotation classes>", "A comma separated list of fully qualified names of" +
diff --git a/src/main/java/com/android/tools/metalava/stub/KotlinStubWriter.kt b/src/main/java/com/android/tools/metalava/stub/KotlinStubWriter.kt
new file mode 100644
index 0000000..c2dc245
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/stub/KotlinStubWriter.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 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.stub
+
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MemberItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.psi.EXPAND_DOCUMENTATION
+import com.android.tools.metalava.model.psi.trimDocIndent
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.options
+import java.io.PrintWriter
+import java.util.function.Predicate
+
+class KotlinStubWriter(
+ private val writer: PrintWriter,
+ private val filterEmit: Predicate<Item>,
+ private val filterReference: Predicate<Item>,
+ private val generateAnnotations: Boolean = false,
+ private val preFiltered: Boolean = true,
+ private val docStubs: Boolean
+) : ItemVisitor() {
+ override fun visitClass(cls: ClassItem) {
+ if (cls.isTopLevelClass()) {
+ val qualifiedName = cls.containingPackage().qualifiedName()
+ if (qualifiedName.isNotBlank()) {
+ writer.println("package $qualifiedName")
+ writer.println()
+ }
+ @Suppress("ConstantConditionIf")
+ if (EXPAND_DOCUMENTATION) {
+ val compilationUnit = cls.getCompilationUnit()
+ compilationUnit?.getImportStatements(filterReference)?.let {
+ for (item in it) {
+ when (item) {
+ is PackageItem ->
+ writer.println("import ${item.qualifiedName()}.*")
+ is ClassItem ->
+ writer.println("import ${item.qualifiedName()}")
+ is MemberItem ->
+ writer.println("import static ${item.containingClass().qualifiedName()}.${item.name()}")
+ }
+ }
+ writer.println()
+ }
+ }
+ }
+ appendDocumentation(cls, writer)
+
+ writer.println("@file:Suppress(\"ALL\")")
+
+ when {
+ cls.isAnnotationType() -> writer.print("annotation class")
+ cls.isInterface() -> writer.print("interface")
+ cls.isEnum() -> writer.print("enum class")
+ else -> writer.print("class")
+ }
+
+ writer.print(" ")
+ writer.print(cls.simpleName())
+
+ writer.print(" {\n")
+ }
+
+ private fun appendDocumentation(item: Item, writer: PrintWriter) {
+ if (options.includeDocumentationInStubs || docStubs) {
+ val documentation = if (docStubs && EXPAND_DOCUMENTATION) {
+ item.fullyQualifiedDocumentation()
+ } else {
+ item.documentation
+ }
+ if (documentation.isNotBlank()) {
+ val trimmed = trimDocIndent(documentation)
+ writer.println(trimmed)
+ writer.println()
+ }
+ }
+ }
+
+ override fun afterVisitClass(cls: ClassItem) {
+ writer.println("}\n\n")
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/stub/StubWriter.kt b/src/main/java/com/android/tools/metalava/stub/StubWriter.kt
index ae7d7bf..c2a9bf1 100644
--- a/src/main/java/com/android/tools/metalava/stub/StubWriter.kt
+++ b/src/main/java/com/android/tools/metalava/stub/StubWriter.kt
@@ -194,7 +194,11 @@
startFile(sourceFile)
- stubWriter = JavaStubWriter(textWriter, filterEmit, filterReference, generateAnnotations, preFiltered, docStubs)
+ stubWriter = if (options.kotlinStubs && cls.isKotlin()) {
+ KotlinStubWriter(textWriter, filterEmit, filterReference, generateAnnotations, preFiltered, docStubs)
+ } else {
+ JavaStubWriter(textWriter, filterEmit, filterReference, generateAnnotations, preFiltered, docStubs)
+ }
// Copyright statements from the original file?
val compilationUnit = cls.getCompilationUnit()
diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt
index 8b585eb..2942245 100644
--- a/src/test/java/com/android/tools/metalava/OptionsTest.kt
+++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt
@@ -206,6 +206,9 @@
recently marked as non null, whereas in the documentation stubs we'll just
list this as @NonNull. Another difference is that @doconly elements are
included in documentation stubs, but not regular stubs, etc.
+--kotlin-stubs
+ [CURRENTLY EXPERIMENTAL] If specified, stubs generated from Kotlin source
+ code will be written in Kotlin rather than the Java programming language.
--include-annotations
Include annotations such as @Nullable in the stub files.
--exclude-annotations
diff --git a/src/test/java/com/android/tools/metalava/stub/StubsTest.kt b/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
index 1d07013..f652d9d 100644
--- a/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
+++ b/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
@@ -23,6 +23,7 @@
import com.android.tools.metalava.ARG_EXCLUDE_ANNOTATIONS
import com.android.tools.metalava.ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS
import com.android.tools.metalava.ARG_HIDE_PACKAGE
+import com.android.tools.metalava.ARG_KOTLIN_STUBS
import com.android.tools.metalava.ARG_PASS_THROUGH_ANNOTATION
import com.android.tools.metalava.ARG_UPDATE_API
import com.android.tools.metalava.DriverTest
@@ -4212,6 +4213,61 @@
)
}
+ @Test
+ fun `Basic Kotlin stubs`() {
+ check(
+ checkCompilation = true,
+ extraArguments = arrayOf(
+ ARG_KOTLIN_STUBS
+ ),
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ /* My file header */
+ // Another comment
+ @file:JvmName("Driver")
+ package test.pkg
+ /** My class doc */
+ class Kotlin(val property1: String = "Default Value", arg2: Int) : Parent() {
+ override fun method() = "Hello World"
+ /** My method doc */
+ fun otherMethod(ok: Boolean, times: Int) {
+ }
+
+ /** property doc */
+ var property2: String? = null
+
+ /** @hide */
+ var hiddenProperty: String? = "hidden"
+
+ private var someField = 42
+ @JvmField
+ var someField2 = 42
+ }
+
+ /** Parent class doc */
+ open class Parent {
+ open fun method(): String? = null
+ open fun method2(value1: Boolean, value2: Boolean?): String? = null
+ open fun method3(value1: Int?, value2: Int): Int = null
+ }
+ """
+ )
+ ),
+ stubs = arrayOf(
+ """
+ /* My file header */
+ // Another comment
+ package test.pkg
+ /** My class doc */
+ @file:Suppress("ALL")
+ class Kotlin {
+ }
+ """
+ )
+ )
+ }
+
// TODO: Test what happens when a class extends a hidden extends a public in separate packages,
// and the hidden has a @hide constructor so the stub in the leaf class doesn't compile -- I should
// check for this and fail build.