Snap for 6838321 from cdd38ca8739f7288be0886c0c8aee888c0e06e49 to mainline-release
Change-Id: I2e94811b9d962cd3b239084d617336ea968f3668
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b63075d..75ecf04 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,15 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
- <bytecodeTargetLevel>
+ <bytecodeTargetLevel target="1.8">
<module name="com.android.metalava.main" target="1.8" />
<module name="com.android.metalava.test" target="1.8" />
- <module name="metalava.stub-annotations.main" target="1.8" />
- <module name="metalava.stub-annotations.test" target="1.8" />
- <module name="metalava_main" target="1.8" />
- <module name="metalava_test" target="1.8" />
- <module name="stub-annotations_main" target="1.8" />
- <module name="stub-annotations_test" target="1.8" />
</bytecodeTargetLevel>
</component>
</project>
\ No newline at end of file
diff --git a/.idea/dictionaries/metalava.xml b/.idea/dictionaries/metalava.xml
index e458e6a..f2c64e0 100644
--- a/.idea/dictionaries/metalava.xml
+++ b/.idea/dictionaries/metalava.xml
@@ -12,6 +12,7 @@
<w>canonicalized</w>
<w>cherrypick</w>
<w>clinit</w>
+ <w>codebase's</w>
<w>codebases</w>
<w>compat</w>
<w>ctor</w>
@@ -19,6 +20,7 @@
<w>devsite</w>
<w>dimen</w>
<w>doclava</w>
+ <w>doclava's</w>
<w>doclet</w>
<w>docletpath</w>
<w>doconly</w>
@@ -45,14 +47,19 @@
<w>jaif</w>
<w>javadocs</w>
<w>jdiff</w>
+ <w>jvmargs</w>
<w>jvmstatic</w>
<w>knowntags</w>
+ <w>kotlinc</w>
<w>kotlinx</w>
+ <w>layoutlib</w>
<w>lerror</w>
<w>libcore</w>
<w>libraryroot</w>
<w>loggable</w>
<w>metalava</w>
+ <w>metalava's</w>
+ <w>mipmap</w>
<w>mmodule</w>
<w>navtree</w>
<w>navtreeonly</w>
@@ -74,6 +81,7 @@
<w>samplecode</w>
<w>samplegroup</w>
<w>samplesdir</w>
+ <w>sandboxing</w>
<w>sdkvalues</w>
<w>skipnative</w>
<w>skippable</w>
@@ -82,6 +90,7 @@
<w>sourcepath</w>
<w>spannable</w>
<w>srcjar</w>
+ <w>stacktraces</w>
<w>staticonly</w>
<w>strippable</w>
<w>stubimportpackages</w>
@@ -91,12 +100,14 @@
<w>templatedir</w>
<w>testroot</w>
<w>throwables</w>
+ <w>tmpdir</w>
<w>toroot</w>
<w>typelist</w>
<w>typemap</w>
<w>unescape</w>
<w>unhide</w>
<w>uninstantiable</w>
+ <w>unmark</w>
<w>unshorten</w>
<w>usecase</w>
<w>werror</w>
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index c8de220..697a2b6 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -9,6 +9,7 @@
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
+ <option value="$PROJECT_DIR$/buildSrc" />
<option value="$PROJECT_DIR$/stub-annotations" />
</set>
</option>
diff --git a/.idea/misc.xml b/.idea/misc.xml
index f6d5d5f..8c572d5 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
+ <option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
+ <option name="myDefaultNotNull" value="org.jetbrains.annotations.NotNull" />
<option name="myNullables">
<value>
- <list size="9">
+ <list size="14">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
@@ -13,12 +15,17 @@
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
+ <item index="9" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
+ <item index="10" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
+ <item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
+ <item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
+ <item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
- <list size="9">
+ <list size="14">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="javax.validation.constraints.NotNull" />
@@ -28,11 +35,16 @@
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
+ <item index="9" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
+ <item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
+ <item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
+ <item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
+ <item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
</list>
</value>
</option>
</component>
- <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" />
</component>
</project>
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index f56ff28..7e90b4c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-java_binary {
+java_binary_host {
name: "metalava",
- host_supported: true,
srcs: [
"src/main/java/**/*.java",
"src/main/java/**/*.kt",
@@ -33,6 +32,14 @@
},
},
},
+ visibility: [
+ // Metalava is linked by //vendor/xts/gts-tests/hostsidetests/api, but because it's a
+ // make project, soong's visibility won't apply. Soong also won't let you specify a
+ // subpackage of //vendor/ here. So let's just make it private.
+ // When //vendor/xts/gts-tests/hostsidetests/api migrates to Android.bp, we need to
+ // open metalava to //vendor:__subpackages__.
+ "//visibility:private",
+ ],
}
java_library {
@@ -75,6 +82,9 @@
":private-stub-annotations",
],
sdk_version: "core_current",
+ // private-stub-annotations-jar ends up in android.jar in the SDK and should
+ // use -target 8.
+ java_version: "1.8",
}
droiddoc_exported_dir {
diff --git a/FORMAT.md b/FORMAT.md
index d27c867..4e7e515 100644
--- a/FORMAT.md
+++ b/FORMAT.md
@@ -12,18 +12,18 @@
and methods until they start appearing), and some were deliberate changes,
such as dropping the "final" modifier in front of every member if the
containing class is final.
-
+
2. The "new" format, which is described below, and is used in Android Q. This
format adds new information, such as annotations, parameter names and default
values, as well as cleans up a number of things (such as dropping
java.lang. prefixes on types, etc)
-
-3. This is format v2, but will all nullness annotations replaced by a
+
+3. This is format v2, but with all nullness annotations replaced by a
Kotlin-syntax, e.g. "?" for nullable types, "!" for unknown/platform types,
and no suffix for non-nullable types. The initial plan was to include this
in format v2, but it was deferred since type-use annotations introduces
- somple complexities in the implementation.
-
+ some complexities in the implementation.
+
## Motivation
@@ -146,7 +146,7 @@
```
-### Clean Up Terminology
+### Clean Up Terminology
Format v2 also cleans up some of the terminology used to describe the class
structure in the signature file. For example, in v1, an interface is called an
@@ -175,7 +175,7 @@
-### Use Generics Everywhere
+### Use Generics Everywhere
The v1 signature files uses raw types in some places but not others. Note that
in the above it was missing from super interface Collection:
@@ -246,7 +246,7 @@
```
-### Support Kotlin Modifiers
+### Support Kotlin Modifiers
This doesn't currently apply to the SDK, but the signature files are also used
in the support library, and some of these are written in Kotlin and exposes
@@ -260,7 +260,7 @@
method public static infix android.graphics.Rect and(android.graphics.Rect, android.graphics.Rect r);
```
-### Support Kotlin Properties
+### Support Kotlin Properties
Kotlin's Java support means that it wil take a Kotlin property and compile it
into getters and setters which you can call from Java. But you cannot calls
@@ -286,7 +286,7 @@
the getters and setters away from the defaults), but it's helpful to be explicit
(and this allows us to specify the default value).
-### Support Named Parameters
+### Support Named Parameters
Kotlin supports default values for parameters, and these are a part of the API
contract, so we need to include them in the signature format.
@@ -316,7 +316,7 @@
(Note how the implementation parameter doesn't have to match the public, API
name of the parameter.)
-### Support Default Values
+### Support Default Values
In addition to named parameters, Kotlin also supports default values. These are
also be part of the v2 signature since (as an example) removing a default value
@@ -453,7 +453,7 @@
method public void setTitleTextColor(@androidx.annotation.ColorInt int);
```
-in v2 we have simply
+in v2 we have simply
```
method public void setTitleTextColor(@ColorInt int);
diff --git a/androidx-studio-integration.sh b/androidx-studio-integration.sh
new file mode 100755
index 0000000..964503e
--- /dev/null
+++ b/androidx-studio-integration.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+set -e
+
+cd "$(dirname $0)/../../"
+SCRIPT_DIR="$(pwd)"
+echo "Script running from $(pwd)"
+
+# resolve DIST_DIR
+if [ -z "$DIST_DIR" ]; then
+ DIST_DIR="$SCRIPT_DIR/out/dist"
+fi
+mkdir -p "$DIST_DIR"
+
+export OUT_DIR=out
+export DIST_DIR="$DIST_DIR"
+
+JAVA_HOME="$(pwd)/prebuilts/studio/jdk/linux" tools/gradlew -p tools/ publishLocal --stacktrace
+
+export LINT_VERSION=`grep -oP "(?<=baseVersion = ).*" tools/buildSrc/base/version.properties`
+export LINT_REPO="$(pwd)/out/repo"
+
+# Disable building metalava because lint upgraded to kotlin 1.4 and we are not compatible with it.
+# tools/gradlew -p tools/metalava --no-daemon --stacktrace -PlintRepo=$LINT_REPO -PlintVersion=$LINT_VERSION
diff --git a/build.gradle.kts b/build.gradle.kts
index 57fa7ee..2d18ae2 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,36 +1,47 @@
-import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import com.android.tools.metalava.CREATE_ARCHIVE_TASK
+import com.android.tools.metalava.CREATE_BUILD_INFO_TASK
+import com.android.tools.metalava.configureBuildInfoTask
+import com.android.tools.metalava.configurePublishingArchive
+import org.gradle.api.tasks.testing.logging.TestLogEvent
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.util.Properties
-buildscript {
- repositories {
- jcenter()
- }
- dependencies {
- classpath("com.github.jengelman.gradle.plugins:shadow:4.0.4")
- }
+if (JavaVersion.current() != JavaVersion.VERSION_1_8) {
+ throw GradleException("You are using Java ${JavaVersion.current()}, but this build only supports Java 8. Please set your JAVA_HOME to JDK 8")
}
buildDir = getBuildDirectory()
-defaultTasks = listOf("installDist", "test", "shadowJar", "createArchive", "ktlint")
+defaultTasks = mutableListOf(
+ "installDist",
+ "test",
+ CREATE_ARCHIVE_TASK,
+ CREATE_BUILD_INFO_TASK,
+ "ktlint"
+)
repositories {
google()
jcenter()
+ val lintRepo = project.findProperty("lintRepo") as String?
+ if (lintRepo != null) {
+ logger.warn("Building using custom $lintRepo maven repository")
+ maven {
+ url = uri(lintRepo)
+ }
+ }
}
plugins {
- kotlin("jvm") version "1.3.20"
+ kotlin("jvm") version "1.3.72"
id("application")
id("java")
- id("com.github.johnrengelman.shadow") version "4.0.4"
id("maven-publish")
}
-group = "com.android"
+group = "com.android.tools.metalava"
version = getMetalavaVersion()
application {
@@ -51,11 +62,18 @@
jvmTarget = "1.8"
apiVersion = "1.3"
languageVersion = "1.3"
+ allWarningsAsErrors = true
}
}
-val studioVersion: String = "26.5.0"
-val kotlinVersion: String = "1.3.20"
+val customLintVersion = findProperty("lintVersion") as String?
+val studioVersion: String = if (customLintVersion != null) {
+ logger.warn("Building using custom $customLintVersion version of Android Lint")
+ customLintVersion
+} else {
+ "27.2.0-alpha07"
+}
+val kotlinVersion: String = "1.3.72"
dependencies {
implementation("com.android.tools.external.org-jetbrains:uast:$studioVersion")
@@ -70,15 +88,14 @@
testImplementation("junit:junit:4.11")
}
-tasks.withType(ShadowJar::class.java) {
- archiveBaseName.set("metalava-full-${project.version}")
- archiveClassifier.set(null as String?)
- archiveVersion.set(null as String?)
- setZip64(true)
- destinationDirectory.set(getDistributionDirectory())
-}
-
tasks.withType(Test::class.java) {
+ testLogging.events = hashSetOf(
+ TestLogEvent.FAILED,
+ TestLogEvent.PASSED,
+ TestLogEvent.SKIPPED,
+ TestLogEvent.STANDARD_OUT,
+ TestLogEvent.STANDARD_ERROR
+ )
val zipTask = project.tasks.register("zipResultsOf${name.capitalize()}", Zip::class.java) {
destinationDirectory.set(File(getDistributionDirectory(), "host-test-reports"))
archiveFileName.set("metalava-tests.zip")
@@ -169,12 +186,12 @@
args = listOf("-F", "src/**/*.kt", "build.gradle.kts")
}
-val libraryName = "Metalava"
+val publicationName = "Metalava"
val repositoryName = "Dist"
publishing {
publications {
- create<MavenPublication>(libraryName) {
+ create<MavenPublication>(publicationName) {
from(components["java"])
pom {
licenses {
@@ -204,12 +221,24 @@
}
}
-tasks.register("createArchive", Zip::class.java) {
- description = "Create a zip of the library in a maven format"
- group = "publishing"
-
- from("${getDistributionDirectory().canonicalPath}/repo")
- archiveFileName.set("top-of-tree-m2repository-all-${getBuildId()}.zip")
- destinationDirectory.set(getDistributionDirectory())
- dependsOn("publish${libraryName}PublicationTo${repositoryName}Repository")
+// Workaround for https://github.com/gradle/gradle/issues/11717
+tasks.withType(GenerateModuleMetadata::class.java).configureEach {
+ doLast {
+ val metadata = outputFile.asFile.get()
+ var text = metadata.readText()
+ metadata.writeText(
+ text.replace(
+ "\"buildId\": .*".toRegex(),
+ "\"buildId:\": \"${getBuildId()}\"")
+ )
+ }
}
+
+configureBuildInfoTask(project, isBuildingOnServer(), getDistributionDirectory())
+configurePublishingArchive(
+ project,
+ publicationName,
+ repositoryName,
+ getBuildId(),
+ getDistributionDirectory()
+)
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
new file mode 100644
index 0000000..5f94ee6
--- /dev/null
+++ b/buildSrc/build.gradle.kts
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+plugins {
+ kotlin("jvm") version "1.3.72"
+}
+
+repositories {
+ jcenter()
+}
+
+dependencies {
+ implementation("com.google.code.gson:gson:2.8.6")
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/tools/metalava/LibraryBuildInfo.kt b/buildSrc/src/main/kotlin/com/android/tools/metalava/LibraryBuildInfo.kt
new file mode 100644
index 0000000..807621f
--- /dev/null
+++ b/buildSrc/src/main/kotlin/com/android/tools/metalava/LibraryBuildInfo.kt
@@ -0,0 +1,159 @@
+/*
+ * 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
+
+import com.android.tools.metalava.LibraryBuildInfoFile.Check
+import com.google.gson.GsonBuilder
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.TaskProvider
+import java.io.File
+import java.util.concurrent.TimeUnit
+
+const val CREATE_BUILD_INFO_TASK = "createBuildInfo"
+
+abstract class CreateLibraryBuildInfoTask : DefaultTask() {
+ @get:Input
+ abstract val artifactId: Property<String>
+ @get:Input
+ abstract val groupId: Property<String>
+ @get:Input
+ abstract val version: Property<String>
+ @get:Input
+ abstract val sha: Property<String>
+
+ @get:OutputFile
+ abstract val outputFile: Property<File>
+
+ @get:OutputFile
+ abstract val aggregateOutputFile: Property<File>
+
+ @TaskAction
+ fun createFile() {
+ val info = LibraryBuildInfoFile()
+ info.artifactId = artifactId.get()
+ info.groupId = groupId.get()
+ info.groupIdRequiresSameVersion = false
+ info.version = version.get()
+ info.path = "/"
+ info.sha = sha.get()
+ info.dependencies = arrayListOf()
+ info.checks = arrayListOf()
+ val gson = GsonBuilder().setPrettyPrinting().create()
+ val serializedInfo: String = gson.toJson(info)
+ outputFile.get().writeText(serializedInfo)
+
+ aggregateOutputFile.get().let {
+ it.writeText("{ \"artifacts\": [\n")
+ it.appendText(serializedInfo)
+ it.appendText("]}")
+ }
+ }
+}
+
+fun configureBuildInfoTask(
+ project: Project,
+ inCI: Boolean,
+ distributionDirectory: File
+): TaskProvider<CreateLibraryBuildInfoTask> {
+ return project.tasks.register(CREATE_BUILD_INFO_TASK, CreateLibraryBuildInfoTask::class.java) {
+ it.artifactId.set(project.provider<String> { project.name })
+ it.groupId.set(project.provider<String> { project.group as String })
+ it.version.set(project.provider<String> { project.version as String })
+ // Only set sha when in CI to keep local builds faster
+ it.sha.set(project.provider<String> { if (inCI) getGitSha(project.projectDir) else "" })
+ it.outputFile.set(project.provider<File> {
+ File(
+ distributionDirectory,
+ "build-info/${project.group}_${project.name}_build_info.txt"
+ )
+ })
+ it.aggregateOutputFile.set(project.provider<File> {
+ File(distributionDirectory, "androidx_aggregate_build_info.txt")}
+ )
+ }
+}
+
+fun getGitSha(directory: File): String {
+ val process = ProcessBuilder("git", "rev-parse", "--verify", "HEAD")
+ .directory(directory)
+ .redirectOutput(ProcessBuilder.Redirect.PIPE)
+ .redirectError(ProcessBuilder.Redirect.PIPE)
+ .start()
+ // Read output, waiting for process to finish, as needed
+ val stdout = process.inputStream.bufferedReader().readText()
+ val stderr = process.errorStream.bufferedReader().readText()
+ val message = stdout + stderr
+ // wait potentially a little bit longer in case Git was waiting for us to
+ // read its response before it exited
+ process.waitFor(10, TimeUnit.SECONDS)
+ if (stderr != "") {
+ throw GradleException("Unable to call git. Response was: $message")
+ }
+ check(process.exitValue() == 0) { "Nonzero exit value running git command." }
+ return stdout.trim()
+}
+
+/**
+ * Object outlining the format of a library's build info file.
+ * This object will be serialized to json.
+ * This file should match the corresponding class in Jetpad because
+ * this object will be serialized to json and the result will be parsed by Jetpad.
+ * DO NOT TOUCH.
+ *
+ * @property groupId library maven group Id
+ * @property artifactId library maven artifact Id
+ * @property version library maven version
+ * @property path local project directory path used for development, rooted at framework/support
+ * @property sha the sha of the latest commit to modify the library (aka a commit that
+ * touches a file within [path])
+ * @property groupIdRequiresSameVersion boolean that determines if all libraries with [groupId]
+ * have the same version
+ * @property dependencies a list of dependencies on other androidx libraries
+ * @property checks arraylist of [Check]s that is used by Jetpad
+ */
+@Suppress("UNUSED")
+class LibraryBuildInfoFile {
+ var groupId: String? = null
+ var artifactId: String? = null
+ var version: String? = null
+ var path: String? = null
+ var sha: String? = null
+ var groupIdRequiresSameVersion: Boolean? = null
+ var dependencies: ArrayList<Dependency> = arrayListOf()
+ var checks: ArrayList<Check> = arrayListOf()
+
+ /**
+ * @property isTipOfTree boolean that specifies whether the dependency is tip-of-tree
+ */
+ inner class Dependency {
+ var groupId: String? = null
+ var artifactId: String? = null
+ var version: String? = null
+ var isTipOfTree = false
+ }
+
+ inner class Check {
+ var name: String? = null
+ var passing = false
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/tools/metalava/Publishing.kt b/buildSrc/src/main/kotlin/com/android/tools/metalava/Publishing.kt
new file mode 100644
index 0000000..4d7ad0f
--- /dev/null
+++ b/buildSrc/src/main/kotlin/com/android/tools/metalava/Publishing.kt
@@ -0,0 +1,43 @@
+/*
+ * 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
+
+import org.gradle.api.Project
+import org.gradle.api.tasks.bundling.Zip
+import java.io.File
+
+const val CREATE_ARCHIVE_TASK = "createArchive"
+
+fun configurePublishingArchive(
+ project: Project,
+ publicationName: String,
+ repositoryName: String,
+ buildId: String,
+ distributionDirectory: File
+) {
+ project.tasks.register(CREATE_ARCHIVE_TASK, Zip::class.java) {
+ it.description = "Create a zip of the library in a maven format"
+ it.group = "publishing"
+
+ it.from("${distributionDirectory.canonicalPath}/repo")
+ it.archiveFileName.set(project.provider<String> {
+ "per-project-zips/${project.group}-${project.name}-all-$buildId-${project.version}.zip"
+ })
+ it.destinationDirectory.set(distributionDirectory)
+ it.dependsOn("publish${publicationName}PublicationTo${repositoryName}Repository")
+ }
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index eb78b8e..28aa708 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -4,4 +4,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
diff --git a/manual/android/support/design/widget/annotations.xml b/manual/android/support/design/widget/annotations.xml
deleted file mode 100644
index 2aedd81..0000000
--- a/manual/android/support/design/widget/annotations.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<root>
- <item name="android.support.design.widget.Snackbar.Duration">
- <annotation name="android.support.annotation.IntDef">
- <val name="value" val="{android.support.design.widget.Snackbar.LENGTH_INDEFINITE, android.support.design.widget.Snackbar.LENGTH_SHORT, android.support.design.widget.Snackbar.LENGTH_LONG}" />
- </annotation>
- </item>
-</root>
-
diff --git a/manual/master.txt b/manual/master.txt
deleted file mode 100644
index ea443b2..0000000
--- a/manual/master.txt
+++ /dev/null
@@ -1,69 +0,0 @@
-// Signature format: 2.0
-// APIs annotated in master that haven't been annotated in pi-dev
-package android.content.pm {
- public abstract class PackageManager {
- method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(String, int, int);
- method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
- }
-}
-package android.graphics {
- public class Path {
- method @NonNull public android.graphics.Path.FillType getFillType();
- }
- public final class Rect implements android.os.Parcelable {
- method @NonNull public String flattenToString();
- method @NonNull public String toShortString();
- method @Nullable public static android.graphics.Rect unflattenFromString(@Nullable String);
- }
- public class RectF implements android.os.Parcelable {
- method @NonNull public String toShortString();
- }
-}
-package android.os {
- public class DropBoxManager {
- method @RequiresPermission(allOf={android.Manifest.permission.READ_LOGS, android.Manifest.permission.PACKAGE_USAGE_STATS}) @Nullable public android.os.DropBoxManager.Entry getNextEntry(String, long);
- }
- public final class Parcel {
- method @Nullable public android.os.IBinder[] createBinderArray();
- method @Nullable public java.util.ArrayList<android.os.IBinder> createBinderArrayList();
- method @Nullable public boolean[] createBooleanArray();
- method @Nullable public byte[] createByteArray();
- method @Nullable public char[] createCharArray();
- method @Nullable public double[] createDoubleArray();
- method @Nullable public float[] createFloatArray();
- method @Nullable public int[] createIntArray();
- method @Nullable public long[] createLongArray();
- method @Nullable public String[] createStringArray();
- method @Nullable public java.util.ArrayList<java.lang.String> createStringArrayList();
- method @Nullable public <T> T[] createTypedArray(@NonNull android.os.Parcelable.Creator<T>);
- method @Nullable public <T> java.util.ArrayList<T> createTypedArrayList(@NonNull android.os.Parcelable.Creator<T>);
- method @NonNull public static android.os.Parcel obtain();
- method @Nullable public Object[] readArray(@Nullable ClassLoader);
- method @Nullable public java.util.ArrayList readArrayList(@Nullable ClassLoader);
- method @Nullable public android.os.Bundle readBundle();
- method @Nullable public android.os.Bundle readBundle(@Nullable ClassLoader);
- method @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader);
- method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
- method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
- method @Nullable public android.os.PersistableBundle readPersistableBundle();
- method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
- method @Nullable public java.io.Serializable readSerializable();
- method @NonNull public android.util.Size readSize();
- method @NonNull public android.util.SizeF readSizeF();
- method @Nullable public android.util.SparseArray readSparseArray(@Nullable ClassLoader);
- method @Nullable public android.util.SparseBooleanArray readSparseBooleanArray();
- method @Nullable public String readString();
- method @Nullable public <T> T readTypedObject(@NonNull android.os.Parcelable.Creator<T>);
- method @Nullable public Object readValue(@Nullable ClassLoader);
- }
-}
-package android.util {
- public final class Log {
- method @NonNull public static String getStackTraceString(@Nullable Throwable);
- }
-}
-package android.view {
- public abstract class Window {
- method @NonNull public abstract android.view.View getDecorView();
- }
-}
diff --git a/src/main/java/com/android/resources/ResourceType.java b/src/main/java/com/android/resources/ResourceType.java
index 9277059..a6943a5 100644
--- a/src/main/java/com/android/resources/ResourceType.java
+++ b/src/main/java/com/android/resources/ResourceType.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-// Copied from tools/base/laoyutlib-api
+// Copied from tools/base/layoutlib-api
package com.android.resources;
diff --git a/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt b/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt
index 3402a1f..2eafd4a 100644
--- a/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt
+++ b/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt
@@ -51,8 +51,7 @@
val IN_STRING_ESCAPE = 6
val IN_CHAR = 7
val AFTER_CHAR = 8
- for (i in 0 until source.length) {
- val c = source[i]
+ for (c in source) {
when (state) {
INIT -> {
when (c) {
diff --git a/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt b/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt
index 65ca749..b9614a8 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt
@@ -246,7 +246,7 @@
)
separator(printer, CLASS_COLUMN_WIDTH + 2, COUNT_COLUMN_WIDTH + 2, rightJustify = true)
- for (i in 0 until items.size) {
+ for (i in items.indices) {
val item = items[i]
val label = getLabel(item)
val count = getCount(item)
diff --git a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
index 73cefd6..cf91fa3 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
@@ -45,8 +45,8 @@
import com.android.tools.lint.annotations.Extractor.SUPPORT_NULLABLE
import com.android.tools.lint.checks.AnnotationDetector
import com.android.tools.lint.detector.api.getChildren
-import com.android.tools.metalava.doclava1.ApiFile
-import com.android.tools.metalava.doclava1.ApiParseException
+import com.android.tools.metalava.model.text.ApiFile
+import com.android.tools.metalava.model.text.ApiParseException
import com.android.tools.metalava.model.AnnotationAttribute
import com.android.tools.metalava.model.AnnotationAttributeValue
import com.android.tools.metalava.model.AnnotationItem
@@ -122,9 +122,10 @@
extractRoots(options.sources, roots)
roots.addAll(options.classpath)
roots.addAll(options.sourcePath)
- val classpath = roots.distinct().toList()
- val javaStubsCodebase = parseSources(javaStubFiles, "Codebase loaded from stubs",
- classpath = classpath)
+ val javaStubsCodebase = parseSources(
+ javaStubFiles,
+ "Codebase loaded from stubs",
+ sourcePath = roots)
mergeJavaStubsCodebase(javaStubsCodebase)
}
}
@@ -164,7 +165,7 @@
val xml = Files.asCharSource(file, UTF_8).read()
mergeAnnotationsXml(file.path, xml)
} catch (e: IOException) {
- error("Aborting: I/O problem during transform: $e")
+ error("I/O problem during transform: $e")
}
} else if (file.path.endsWith(".txt") ||
file.path.endsWith(".signatures") ||
@@ -175,7 +176,7 @@
// Others: new signature files (e.g. kotlin-style nullness info)
mergeAnnotationsSignatureFile(file.path)
} catch (e: IOException) {
- error("Aborting: I/O problem during transform: $e")
+ error("I/O problem during transform: $e")
}
}
}
@@ -197,7 +198,7 @@
entry = zis.nextEntry
}
} catch (e: IOException) {
- error("Aborting: I/O problem during transform: $e")
+ error("I/O problem during transform: $e")
} finally {
try {
Closeables.close(zis, true /* swallowIOException */)
@@ -512,7 +513,7 @@
assert(tagName == "annotation") { tagName }
val qualifiedName = element.getAttribute(ATTR_NAME)
- assert(qualifiedName != null && !qualifiedName.isEmpty())
+ assert(qualifiedName != null && qualifiedName.isNotEmpty())
return qualifiedName
}
@@ -563,7 +564,7 @@
val tagName = annotationElement.tagName
assert(tagName == "annotation") { tagName }
val name = annotationElement.getAttribute(ATTR_NAME)
- assert(name != null && !name.isEmpty())
+ assert(name != null && name.isNotEmpty())
when {
name == "org.jetbrains.annotations.Range" -> {
val children = getChildren(annotationElement)
@@ -705,7 +706,7 @@
val valueElement = children[0]
val value = valueElement.getAttribute(ATTR_VAL)
val pure = valueElement.getAttribute(ATTR_PURE)
- return if (pure != null && !pure.isEmpty()) {
+ return if (pure != null && pure.isNotEmpty()) {
PsiAnnotationItem.create(
codebase, XmlBackedAnnotationItem(
codebase, name,
diff --git a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
index 65710f3..d3a6821 100644
--- a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
@@ -30,7 +30,6 @@
import com.android.tools.metalava.model.ParameterItem
import com.android.tools.metalava.model.TypeItem
import com.android.tools.metalava.model.VisibilityLevel
-import com.android.tools.metalava.model.psi.EXPAND_DOCUMENTATION
import com.android.tools.metalava.model.visitors.ApiVisitor
import com.android.tools.metalava.model.visitors.ItemVisitor
import java.util.ArrayList
@@ -196,7 +195,7 @@
// Try and use a publicly accessible constructor first.
val constructors = cls.filteredConstructors(filter).toList()
- if (!constructors.isEmpty()) {
+ if (constructors.isNotEmpty()) {
// Try to pick the constructor, select first by fewest throwables, then fewest parameters,
// then based on order in listFilter.test(cls)
cls.stubConstructor = constructors.reduce { first, second -> pickBest(first, second) }
@@ -463,6 +462,17 @@
}
}
+ val existingMethodMap = HashMap<String, MutableList<MethodItem>>()
+ for (method in cls.methods()) {
+ val name = method.name()
+ val list = existingMethodMap[name] ?: run {
+ val newList = ArrayList<MethodItem>()
+ existingMethodMap[name] = newList
+ newList
+ }
+ list.add(method)
+ }
+
// We're now left with concrete methods in hidden parents that are implementing methods in public
// interfaces that are listed in this class. Create stubs for them:
map.values.flatten().forEach {
@@ -475,12 +485,20 @@
method.inheritedMethod = true
method.inheritedFrom = it.containingClass()
- // The documentation may use relative references to classes in import statements
- // in the original class, so expand the documentation to be fully qualified.
- @Suppress("ConstantConditionIf")
- if (!EXPAND_DOCUMENTATION) {
- method.documentation = it.fullyQualifiedDocumentation()
+ val name = method.name()
+ val candidates = existingMethodMap[name]
+ if (candidates != null) {
+ val iterator = candidates.listIterator()
+ while (iterator.hasNext()) {
+ val inheritedMethod = iterator.next()
+ if (method.matches(inheritedMethod)) {
+ // If we already have an override of this method, do not add it to the
+ // methods list
+ return@forEach
+ }
+ }
}
+
cls.addMethod(method)
}
}
@@ -490,7 +508,6 @@
for (pkgName in options.hidePackages) {
val pkg = codebase.findPackage(pkgName) ?: continue
pkg.hidden = true
- pkg.included = false // because included has already been initialized
}
}
@@ -507,14 +524,14 @@
* from all configured sources.
*/
fun mergeExternalQualifierAnnotations() {
- if (!options.mergeQualifierAnnotations.isEmpty()) {
+ if (options.mergeQualifierAnnotations.isNotEmpty()) {
AnnotationsMerger(codebase).mergeQualifierAnnotations(options.mergeQualifierAnnotations)
}
}
/** Merge in external show/hide annotations from all configured sources */
fun mergeExternalInclusionAnnotations() {
- if (!options.mergeInclusionAnnotations.isEmpty()) {
+ if (options.mergeInclusionAnnotations.isNotEmpty()) {
AnnotationsMerger(codebase).mergeInclusionAnnotations(options.mergeInclusionAnnotations)
}
}
@@ -642,15 +659,25 @@
private fun ensureParentVisible(item: Item) {
val parent = item.parent() ?: return
- if (parent.hidden && item.modifiers.hasShowSingleAnnotation()) {
- val annotation = item.modifiers.annotations().find {
+ if (!parent.hidden) {
+ return
+ }
+ val violatingAnnotation = if (item.modifiers.hasShowAnnotation()) {
+ item.modifiers.annotations().find {
+ options.showAnnotations.matches(it)
+ } ?: options.showAnnotations.firstQualifiedName()
+ } else if (item.modifiers.hasShowSingleAnnotation()) {
+ item.modifiers.annotations().find {
options.showSingleAnnotations.matches(it)
} ?: options.showSingleAnnotations.firstQualifiedName()
+ } else {
+ null
+ }
+ if (violatingAnnotation != null) {
reporter.report(
Issues.SHOWING_MEMBER_IN_HIDDEN_CLASS, item,
"Attempting to unhide ${item.describe()}, but surrounding ${parent.describe()} is " +
- "hidden and should also be annotated with $annotation"
- )
+ "hidden and should also be annotated with $violatingAnnotation")
}
}
})
@@ -717,7 +744,7 @@
if (system.isEmpty() && nonSystem.isEmpty()) {
hasAnnotation = false
- } else if (any && !nonSystem.isEmpty() || !any && system.isEmpty()) {
+ } else if (any && nonSystem.isNotEmpty() || !any && system.isEmpty()) {
reporter.report(
Issues.REQUIRES_PERMISSION, method, "Method '" + method.name() +
"' must be protected with a system permission; it currently" +
@@ -767,7 +794,7 @@
if (checkHiddenShowAnnotations &&
item.hasShowAnnotation() &&
- !item.documentation.contains("@hide") &&
+ !item.originallyHidden &&
!item.modifiers.hasShowSingleAnnotation()
) {
val annotationName = (item.modifiers.annotations().firstOrNull { annotation ->
@@ -865,10 +892,20 @@
// but iterating through the type argument classes below will find and
// check the component class
if (cls != null && !filterReference.test(cls) && !cls.isFromClassPath()) {
- reporter.report(
- Issues.HIDDEN_TYPE_PARAMETER, item,
- "${item.toString().capitalize()} references hidden type $type."
- )
+ if (cls.modifiers.isCompanion() &&
+ cls.isPrivate &&
+ cls.containingClass()?.isInterface() == true
+ ) {
+ reporter.report(
+ Issues.PRIVATE_COMPANION, cls, "Do not use private companion " +
+ "objects inside interfaces as these become public if targeting " +
+ "Java 8 or older.")
+ } else {
+ reporter.report(
+ Issues.HIDDEN_TYPE_PARAMETER, item,
+ "${item.toString().capitalize()} references hidden type $type."
+ )
+ }
}
type.typeArgumentClasses()
@@ -1022,12 +1059,6 @@
} else if (cl.deprecated) {
// not hidden, but deprecated
reporter.report(Issues.DEPRECATED, cl, "Class ${cl.qualifiedName()} is deprecated")
- } else if (reporter.isSuppressed(Issues.REFERENCES_HIDDEN, cl)) {
- // If we're not reporting hidden references, bring the type back
- // Bring this class back
- cl.hidden = false
- cl.removed = false
- cl.notStrippable = true
}
}
}
@@ -1056,7 +1087,6 @@
false
)}"
)
- cl.notStrippable = true
}
if (!notStrippable.add(cl)) {
diff --git a/src/main/java/com/android/tools/metalava/ApiLint.kt b/src/main/java/com/android/tools/metalava/ApiLint.kt
index 61b84cb..c6fc9ed 100644
--- a/src/main/java/com/android/tools/metalava/ApiLint.kt
+++ b/src/main/java/com/android/tools/metalava/ApiLint.kt
@@ -72,10 +72,10 @@
import com.android.tools.metalava.doclava1.Issues.EXCEPTION_NAME
import com.android.tools.metalava.doclava1.Issues.EXECUTOR_REGISTRATION
import com.android.tools.metalava.doclava1.Issues.EXTENDS_ERROR
-import com.android.tools.metalava.doclava1.Issues.Issue
import com.android.tools.metalava.doclava1.Issues.FORBIDDEN_SUPER_CLASS
import com.android.tools.metalava.doclava1.Issues.FRACTION_FLOAT
import com.android.tools.metalava.doclava1.Issues.GENERIC_EXCEPTION
+import com.android.tools.metalava.doclava1.Issues.GETTER_ON_BUILDER
import com.android.tools.metalava.doclava1.Issues.GETTER_SETTER_NAMES
import com.android.tools.metalava.doclava1.Issues.HEAVY_BIT_SET
import com.android.tools.metalava.doclava1.Issues.ILLEGAL_STATE_EXCEPTION
@@ -84,6 +84,7 @@
import com.android.tools.metalava.doclava1.Issues.INTERFACE_CONSTANT
import com.android.tools.metalava.doclava1.Issues.INTERNAL_CLASSES
import com.android.tools.metalava.doclava1.Issues.INTERNAL_FIELD
+import com.android.tools.metalava.doclava1.Issues.Issue
import com.android.tools.metalava.doclava1.Issues.KOTLIN_OPERATOR
import com.android.tools.metalava.doclava1.Issues.LISTENER_INTERFACE
import com.android.tools.metalava.doclava1.Issues.LISTENER_LAST
@@ -94,12 +95,15 @@
import com.android.tools.metalava.doclava1.Issues.METHOD_NAME_UNITS
import com.android.tools.metalava.doclava1.Issues.MIN_MAX_CONSTANT
import com.android.tools.metalava.doclava1.Issues.MISSING_BUILD_METHOD
+import com.android.tools.metalava.doclava1.Issues.MISSING_GETTER_MATCHING_BUILDER
import com.android.tools.metalava.doclava1.Issues.MISSING_NULLABILITY
import com.android.tools.metalava.doclava1.Issues.MUTABLE_BARE_FIELD
import com.android.tools.metalava.doclava1.Issues.NOT_CLOSEABLE
import com.android.tools.metalava.doclava1.Issues.NO_BYTE_OR_SHORT
import com.android.tools.metalava.doclava1.Issues.NO_CLONE
+import com.android.tools.metalava.doclava1.Issues.NO_SETTINGS_PROVIDER
import com.android.tools.metalava.doclava1.Issues.ON_NAME_EXPECTED
+import com.android.tools.metalava.doclava1.Issues.OPTIONAL_BUILDER_CONSTRUCTOR_ARGUMENT
import com.android.tools.metalava.doclava1.Issues.OVERLAPPING_CONSTANTS
import com.android.tools.metalava.doclava1.Issues.PACKAGE_LAYERING
import com.android.tools.metalava.doclava1.Issues.PAIRED_REGISTRATION
@@ -123,6 +127,7 @@
import com.android.tools.metalava.doclava1.Issues.SINGULAR_CALLBACK
import com.android.tools.metalava.doclava1.Issues.START_WITH_LOWER
import com.android.tools.metalava.doclava1.Issues.START_WITH_UPPER
+import com.android.tools.metalava.doclava1.Issues.STATIC_FINAL_BUILDER
import com.android.tools.metalava.doclava1.Issues.STATIC_UTILS
import com.android.tools.metalava.doclava1.Issues.STREAM_FILES
import com.android.tools.metalava.doclava1.Issues.TOP_LEVEL_BUILDER
@@ -143,8 +148,8 @@
import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.PackageItem
import com.android.tools.metalava.model.ParameterItem
-import com.android.tools.metalava.model.TypeItem
import com.android.tools.metalava.model.SetMinSdkVersion
+import com.android.tools.metalava.model.TypeItem
import com.android.tools.metalava.model.psi.PsiMethodItem
import com.android.tools.metalava.model.visitors.ApiVisitor
import com.intellij.psi.JavaRecursiveElementVisitor
@@ -169,7 +174,9 @@
// Sort by source order such that warnings follow source line number order
methodComparator = MethodItem.sourceOrderComparator,
fieldComparator = FieldItem.comparator,
- ignoreShown = options.showUnannotated
+ ignoreShown = options.showUnannotated,
+ // No need to check "for stubs only APIs" (== "implicit" APIs)
+ includeApisForStubPurposes = false
) {
private fun report(id: Issue, item: Item, message: String, element: PsiElement? = null) {
// Don't flag api warnings on deprecated APIs; these are obviously already known to
@@ -234,12 +241,13 @@
}
override fun skip(item: Item): Boolean {
- return super.skip(item) || item is ClassItem && !isInteresting(item)
+ return super.skip(item) ||
+ item is ClassItem && !isInteresting(item) ||
+ item is MethodItem && !isInteresting(item.containingClass()) ||
+ item is FieldItem && !isInteresting(item.containingClass())
}
- // The previous Kotlin interop tests are also part of API lint now (though they can be
- // run independently as well; therefore, only run them here if not running separately)
- private val kotlinInterop = if (!options.checkKotlinInterop) KotlinInteropChecks(reporter) else null
+ private val kotlinInterop = KotlinInteropChecks(reporter)
override fun visitClass(cls: ClassItem) {
val methods = cls.filteredMethods(filterReference).asSequence()
@@ -263,13 +271,13 @@
for (parameter in method.parameters()) {
checkType(parameter.type(), parameter)
}
- kotlinInterop?.checkMethod(method)
+ kotlinInterop.checkMethod(method)
}
override fun visitField(field: FieldItem) {
checkField(field)
checkType(field.type(), field)
- kotlinInterop?.checkField(field)
+ kotlinInterop.checkField(field)
}
private fun checkType(type: TypeItem, item: Item) {
@@ -299,12 +307,12 @@
checkEquals(methods)
checkEnums(cls)
checkClassNames(cls)
- checkCallbacks(cls, methods)
+ checkCallbacks(cls)
checkListeners(cls, methods)
checkParcelable(cls, methods, constructors, fields)
checkRegistrationMethods(cls, methods)
checkHelperClasses(cls, methods, fields)
- checkBuilder(cls, methods, superClass)
+ checkBuilder(cls, methods, constructors, superClass)
checkAidl(cls, superClass, interfaces)
checkInternal(cls)
checkLayering(cls, methodsAndConstructors, fields)
@@ -344,6 +352,7 @@
checkProtected(field)
checkServices(field)
checkFieldName(field)
+ checkSettingKeys(field)
}
private fun checkMethod(
@@ -358,6 +367,7 @@
checkUnits(method)
checkTense(method)
checkClone(method)
+ checkCallbackOrListenerMethod(method)
}
checkExceptions(method, filterReference)
checkContextFirst(method)
@@ -401,7 +411,14 @@
return
}
- val name = method.name()
+ val name = if (method.isKotlin() && method.name().contains("-")) {
+ // Kotlin renames certain methods in binary, e.g. fun foo(bar: Bar) where Bar is an
+ // inline class becomes foo-HASHCODE. We only want to consider the original name for
+ // this API lint check
+ method.name().substringBefore("-")
+ } else {
+ method.name()
+ }
val first = name[0]
when {
@@ -519,7 +536,7 @@
}
}
- private fun checkCallbacks(cls: ClassItem, methods: Sequence<MethodItem>) {
+ private fun checkCallbacks(cls: ClassItem) {
/*
def verify_callbacks(clazz):
"""Verify Callback classes.
@@ -570,21 +587,38 @@
CALLBACK_INTERFACE, cls,
"Callbacks must be abstract class instead of interface to enable extension in future API levels: $name"
)
- } else {
- for (method in methods) {
- val methodName = method.name()
- if (!onCallbackNamePattern.matches(methodName)) {
- report(
- CALLBACK_METHOD_NAME, cls,
- "Callback method names must follow the on<Something> style: $methodName"
- )
- }
- }
}
}
}
}
+ private fun checkCallbackOrListenerMethod(method: MethodItem) {
+ if (method.isConstructor() || method.modifiers.isStatic() || method.modifiers.isFinal()) {
+ return
+ }
+ val cls = method.containingClass()
+
+ // These are not listeners or callbacks despite their name.
+ when {
+ cls.modifiers.isFinal() -> return
+ cls.qualifiedName() == "android.telephony.ims.ImsCallSessionListener" -> return
+ }
+
+ val containingClassSimpleName = cls.simpleName()
+ val kind = when {
+ containingClassSimpleName.endsWith("Callback") -> "Callback"
+ containingClassSimpleName.endsWith("Listener") -> "Listener"
+ else -> return
+ }
+ val methodName = method.name()
+ if (!onCallbackNamePattern.matches(methodName)) {
+ report(
+ CALLBACK_METHOD_NAME, method,
+ "$kind method names must follow the on<Something> style: $methodName"
+ )
+ }
+ }
+
private fun checkListeners(cls: ClassItem, methods: Sequence<MethodItem>) {
/*
def verify_listeners(clazz):
@@ -616,16 +650,6 @@
"Listeners should be an interface, or otherwise renamed Callback: $name"
)
} else {
- for (method in methods) {
- val methodName = method.name()
- if (!onCallbackNamePattern.matches(methodName)) {
- report(
- CALLBACK_METHOD_NAME, cls,
- "Listener method names must follow the on<Something> style: $methodName"
- )
- }
- }
-
if (methods.count() == 1) {
val method = methods.first()
val methodName = method.name()
@@ -693,8 +717,7 @@
)
return
}
- val className = field.containingClass().qualifiedName()
- val prefix = when (className) {
+ val prefix = when (field.containingClass().qualifiedName()) {
"android.content.Intent" -> "android.intent.action"
"android.provider.Settings" -> "android.settings"
"android.app.admin.DevicePolicyManager", "android.app.admin.DeviceAdminReceiver" -> "android.app.action"
@@ -989,6 +1012,17 @@
}
}
+ private fun checkSettingKeys(field: FieldItem) {
+ val className = field.containingClass().qualifiedName()
+ val modifiers = field.modifiers
+ val type = field.type()
+
+ if (modifiers.isFinal() && modifiers.isStatic() && type.isString() && className in settingsKeyClasses) {
+ report(NO_SETTINGS_PROVIDER, field,
+ "New setting keys are not allowed (Field: ${field.name()}); use getters/setters in relevant manager class")
+ }
+ }
+
private fun checkRegistrationMethods(cls: ClassItem, methods: Sequence<MethodItem>) {
/*
def verify_register(clazz):
@@ -1026,6 +1060,7 @@
/** Make sure that there is a corresponding method */
fun ensureMatched(cls: ClassItem, methods: Sequence<MethodItem>, method: MethodItem, name: String) {
+ if (method.superMethods().isNotEmpty()) return // Do not report for override methods
for (candidate in methods) {
if (candidate.name() == name) {
return
@@ -1274,7 +1309,12 @@
}
}
- private fun checkBuilder(cls: ClassItem, methods: Sequence<MethodItem>, superClass: ClassItem?) {
+ private fun checkBuilder(
+ cls: ClassItem,
+ methods: Sequence<MethodItem>,
+ constructors: Sequence<ConstructorItem>,
+ superClass: ClassItem?
+ ) {
/*
def verify_builder(clazz):
"""Verify builder classes.
@@ -1316,35 +1356,100 @@
"Builder should be defined as inner class: ${cls.qualifiedName()}"
)
}
- var hasBuild = false
- for (method in methods) {
- val name = method.name()
- if (name == "build") {
- hasBuild = true
- continue
- } else if (name.startsWith("get") || name.startsWith("clear")) {
- continue
- } else if (name.startsWith("with")) {
- report(
- BUILDER_SET_STYLE, method,
- "Builder methods names should use setFoo() style: ${method.describe()}"
- )
- } else if (name.startsWith("set")) {
- val returnType = method.returnType()?.toTypeString() ?: ""
- if (returnType != cls.toType().toTypeString()) {
+ if (!cls.modifiers.isFinal()) {
+ report(
+ STATIC_FINAL_BUILDER, cls,
+ "Builder must be final: ${cls.qualifiedName()}"
+ )
+ }
+ if (!cls.modifiers.isStatic() && !cls.isTopLevelClass()) {
+ report(
+ STATIC_FINAL_BUILDER, cls,
+ "Builder must be static: ${cls.qualifiedName()}"
+ )
+ }
+ for (constructor in constructors) {
+ for (arg in constructor.parameters()) {
+ if (arg.modifiers.isNullable()) {
report(
- SETTER_RETURNS_THIS, method,
- "Methods must return the builder object (return type ${cls.toType().toTypeString()} instead of $returnType): ${method.describe()}"
+ OPTIONAL_BUILDER_CONSTRUCTOR_ARGUMENT, arg,
+ "Builder constructor arguments must be mandatory (i.e. not @Nullable): ${arg.describe()}"
)
}
}
}
- if (!hasBuild) {
+ val expectedGetters = mutableListOf<Pair<Item, String>>()
+ var builtType: TypeItem? = null
+ val clsType = cls.toType().toTypeString()
+
+ for (method in methods) {
+ val name = method.name()
+ if (name == "build") {
+ builtType = method.type()
+ continue
+ } else if (name.startsWith("get") || name.startsWith("is")) {
+ report(
+ GETTER_ON_BUILDER, method,
+ "Getter should be on the built object, not the builder: ${method.describe()}"
+ )
+ } else if (name.startsWith("set") || name.startsWith("add") || name.startsWith("clear")) {
+ val returnType = method.returnType()?.toTypeString() ?: ""
+ val returnTypeBounds = method.returnType()?.asTypeParameter(context = method)?.bounds()?.map {
+ it.toType().toTypeString()
+ } ?: listOf()
+
+ if (returnType != clsType && !returnTypeBounds.contains(clsType)) {
+ report(
+ SETTER_RETURNS_THIS, method,
+ "Methods must return the builder object (return type $clsType instead of $returnType): ${method.describe()}"
+ )
+ }
+ if (method.modifiers.isNullable()) {
+ report(
+ SETTER_RETURNS_THIS, method,
+ "Builder setter must be @NonNull: ${method.describe()}"
+ )
+ }
+ when {
+ name.startsWith("set") -> name.removePrefix("set")
+ name.startsWith("add") -> "${name.removePrefix("add")}s"
+ else -> null
+ }?.let { getterSuffix ->
+ val isBool = when (method.parameters().firstOrNull()?.type()?.toTypeString()) {
+ "boolean", "java.lang.Boolean" -> true
+ else -> false
+ }
+ val expectedGetter = if (isBool && name.startsWith("set")) {
+ val pattern = goodBooleanGetterSetterPrefixes.match(name, GetterSetterPattern::setter)!!
+ "${pattern.getter}${name.removePrefix(pattern.setter)}"
+ } else {
+ "get$getterSuffix"
+ }
+ expectedGetters.add(method to expectedGetter)
+ }
+ } else {
+ report(
+ BUILDER_SET_STYLE, method,
+ "Builder methods names should use setFoo() / addFoo() / clearFoo() style: ${method.describe()}"
+ )
+ }
+ }
+ if (builtType == null) {
report(
MISSING_BUILD_METHOD, cls,
"${cls.qualifiedName()} does not declare a `build()` method, but builder classes are expected to"
)
}
+ builtType?.asClass()?.let { builtClass ->
+ val builtMethods = builtClass.filteredMethods(filterReference).map { it.name() }.toSet()
+ for ((setter, expectedGetterName) in expectedGetters) {
+ if (!builtMethods.contains(expectedGetterName))
+ report(
+ MISSING_GETTER_MATCHING_BUILDER, setter,
+ "${builtClass.qualifiedName()} does not declare a `$expectedGetterName()` method matching ${setter.describe()}"
+ )
+ }
+ }
}
private fun checkAidl(cls: ClassItem, superClass: ClassItem?, interfaces: Sequence<TypeItem>) {
@@ -1559,20 +1664,6 @@
}
}
- data class GetterSetterPattern(val getter: String, val setter: String)
- val goodPrefixes = listOf(
- GetterSetterPattern("has", "setHas"),
- GetterSetterPattern("can", "setCan"),
- GetterSetterPattern("should", "setShould"),
- GetterSetterPattern("is", "set")
- )
- fun List<GetterSetterPattern>.match(name: String, prop: (GetterSetterPattern) -> String) = firstOrNull {
- name.startsWith(prop(it)) && name.getOrNull(prop(it).length)?.isUpperCase() ?: false
- }
-
- val badGetterPrefixes = listOf("isHas", "isCan", "isShould", "get", "is")
- val badSetterPrefixes = listOf("setIs", "set")
-
fun isGetter(method: MethodItem): Boolean {
val returnType = method.returnType() ?: return false
return method.parameters().isEmpty() && returnType.primitive && returnType.toTypeString() == "boolean"
@@ -1585,22 +1676,22 @@
for (method in methods) {
val name = method.name()
if (isGetter(method)) {
- val pattern = goodPrefixes.match(name, GetterSetterPattern::getter) ?: continue
+ val pattern = goodBooleanGetterSetterPrefixes.match(name, GetterSetterPattern::getter) ?: continue
val target = name.substring(pattern.getter.length)
val expectedSetter = "${pattern.setter}$target"
- badSetterPrefixes.forEach {
+ badBooleanSetterPrefixes.forEach {
val actualSetter = "${it}$target"
if (actualSetter != expectedSetter) {
errorIfExists(methods, name, expectedSetter, actualSetter)
}
}
} else if (isSetter(method)) {
- val pattern = goodPrefixes.match(name, GetterSetterPattern::setter) ?: continue
+ val pattern = goodBooleanGetterSetterPrefixes.match(name, GetterSetterPattern::setter) ?: continue
val target = name.substring(pattern.setter.length)
val expectedGetter = "${pattern.getter}$target"
- badGetterPrefixes.forEach {
+ badBooleanGetterPrefixes.forEach {
val actualGetter = "${it}$target"
if (actualGetter != expectedGetter) {
errorIfExists(methods, name, expectedGetter, actualGetter)
@@ -1633,8 +1724,7 @@
return
}
- val raw = type.asClass()?.qualifiedName()
- when (raw) {
+ when (type.asClass()?.qualifiedName()) {
"java.util.Vector",
"java.util.LinkedList",
"java.util.ArrayList",
@@ -1737,8 +1827,7 @@
warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
*/
for (exception in method.filteredThrowsTypes(filterReference)) {
- val qualifiedName = exception.qualifiedName()
- when (qualifiedName) {
+ when (val qualifiedName = exception.qualifiedName()) {
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Error" -> {
@@ -1862,6 +1951,15 @@
if (item.requiresNullnessInfo() && !item.hasNullnessInfo() &&
getImplicitNullness(item) == null) {
val type = item.type()
+ val inherited = when (item) {
+ is ParameterItem -> item.containingMethod().inheritedMethod
+ is FieldItem -> item.inheritedField
+ is MethodItem -> item.inheritedMethod
+ else -> false
+ }
+ if (inherited) {
+ return // Do not enforce nullability on inherited items (non-overridden)
+ }
if (type != null && type.isTypeParameter()) {
// Generic types should have declarations of nullability set at the site of where
// the type is set, so that for Foo<T>, T does not need to specify nullability, but
@@ -2518,8 +2616,7 @@
var hasStream: MutableSet<String>? = null
for (method in methodsAndConstructors) {
for (parameter in method.parameters()) {
- val type = parameter.type().toTypeString()
- when (type) {
+ when (parameter.type().toTypeString()) {
"java.io.File" -> {
val set = hasFile ?: run {
val new = mutableSetOf<MethodItem>()
@@ -2652,6 +2749,9 @@
*/
for (method in methodsAndConstructors) {
+ if (method.synthetic) {
+ continue
+ }
for (throws in method.filteredThrowsTypes(filterReference)) {
when (throws.qualifiedName()) {
"java.lang.NullPointerException",
@@ -2903,8 +3003,7 @@
if (method.modifiers.isStatic() || method.modifiers.isOperator() || method.superMethods().isNotEmpty()) {
continue
}
- val name = method.name()
- when (name) {
+ when (val name = method.name()) {
// https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
"unaryPlus", "unaryMinus", "not" -> {
if (method.parameters().isEmpty()) {
@@ -3022,6 +3121,10 @@
if (item.name() == "values" && item.containingClass().isEnum()) {
return
}
+ if (item.containingClass().extends("java.lang.annotation.Annotation")) {
+ // Annotation are allowed to use arrays
+ return
+ }
"Method should return"
}
is FieldItem -> "Field should be"
@@ -3488,6 +3591,23 @@
companion object {
+ private data class GetterSetterPattern(val getter: String, val setter: String)
+ private val goodBooleanGetterSetterPrefixes = listOf(
+ GetterSetterPattern("has", "setHas"),
+ GetterSetterPattern("can", "setCan"),
+ GetterSetterPattern("should", "setShould"),
+ GetterSetterPattern("is", "set")
+ )
+ private fun List<GetterSetterPattern>.match(
+ name: String,
+ prop: (GetterSetterPattern) -> String
+ ) = firstOrNull {
+ name.startsWith(prop(it)) && name.getOrNull(prop(it).length)?.isUpperCase() ?: false
+ }
+
+ private val badBooleanGetterPrefixes = listOf("isHas", "isCan", "isShould", "get", "is")
+ private val badBooleanSetterPrefixes = listOf("setIs", "set")
+
private val badParameterClassNames = listOf(
"Param", "Parameter", "Parameters", "Args", "Arg", "Argument", "Arguments", "Options", "Bundle"
)
@@ -3532,6 +3652,15 @@
"android.system.StructPollfd"
)
+ /**
+ * Classes containing setting provider keys.
+ */
+ private val settingsKeyClasses = listOf(
+ "android.provider.Settings.Global",
+ "android.provider.Settings.Secure",
+ "android.provider.Settings.System"
+ )
+
private val badUnits = mapOf(
"Ns" to "Nanos",
"Ms" to "Millis or Micros",
@@ -3585,18 +3714,18 @@
private fun hasAcronyms(name: String): Boolean {
// Require 3 capitals, or 2 if it's at the end of a word.
val result = acronymPattern2.find(name) ?: return false
- return result.range.start == name.length - 2 || acronymPattern3.find(name) != null
+ return result.range.first == name.length - 2 || acronymPattern3.find(name) != null
}
private fun getFirstAcronym(name: String): String? {
// Require 3 capitals, or 2 if it's at the end of a word.
val result = acronymPattern2.find(name) ?: return null
- if (result.range.start == name.length - 2) {
+ if (result.range.first == name.length - 2) {
return name.substring(name.length - 2)
}
val result2 = acronymPattern3.find(name)
return if (result2 != null) {
- name.substring(result2.range.start, result2.range.endInclusive + 1)
+ name.substring(result2.range.first, result2.range.last + 1)
} else {
null
}
diff --git a/src/main/java/com/android/tools/metalava/ApiType.kt b/src/main/java/com/android/tools/metalava/ApiType.kt
index 7ca3694..c9372c2 100644
--- a/src/main/java/com/android/tools/metalava/ApiType.kt
+++ b/src/main/java/com/android/tools/metalava/ApiType.kt
@@ -34,7 +34,8 @@
}
override fun getEmitFilter(): Predicate<Item> {
- val apiFilter = FilterPredicate(ApiPredicate())
+ // This filter is for API signature files, where we don't need the "for stub purposes" APIs.
+ val apiFilter = FilterPredicate(ApiPredicate(includeApisForStubPurposes = false))
val apiReference = ApiPredicate(ignoreShown = true)
return apiFilter.and(ElidingPredicate(apiReference))
}
@@ -51,7 +52,8 @@
}
override fun getEmitFilter(): Predicate<Item> {
- val removedFilter = FilterPredicate(ApiPredicate(matchRemoved = true))
+ // This filter is for API signature files, where we don't need the "for stub purposes" APIs.
+ val removedFilter = FilterPredicate(ApiPredicate(includeApisForStubPurposes = false, matchRemoved = true))
val removedReference = ApiPredicate(ignoreShown = true, ignoreRemoved = true)
return removedFilter.and(ElidingPredicate(removedReference))
}
@@ -61,23 +63,6 @@
}
},
- /** The private API */
- PRIVATE("private", "private") {
- override fun getOptionFile(): File? {
- return options.privateApiFile
- }
-
- override fun getEmitFilter(): Predicate<Item> {
- val apiFilter = FilterPredicate(ApiPredicate())
- val memberIsNotCloned: Predicate<Item> = Predicate { !it.isCloned() }
- return memberIsNotCloned.and(apiFilter.negate())
- }
-
- override fun getReferenceFilter(): Predicate<Item> {
- return Predicate { true }
- }
- },
-
/** Everything */
ALL("all", "all") {
override fun getOptionFile(): File? {
diff --git a/src/main/java/com/android/tools/metalava/ArtifactTagger.kt b/src/main/java/com/android/tools/metalava/ArtifactTagger.kt
index 59becd3..3dfb49c 100644
--- a/src/main/java/com/android/tools/metalava/ArtifactTagger.kt
+++ b/src/main/java/com/android/tools/metalava/ArtifactTagger.kt
@@ -16,10 +16,10 @@
package com.android.tools.metalava
-import com.android.tools.metalava.doclava1.ApiFile
-import com.android.tools.metalava.doclava1.ApiParseException
+import com.android.tools.metalava.model.text.ApiFile
+import com.android.tools.metalava.model.text.ApiParseException
import com.android.tools.metalava.doclava1.Issues
-import com.android.tools.metalava.doclava1.TextCodebase
+import com.android.tools.metalava.model.text.TextCodebase
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.Codebase
import com.android.tools.metalava.model.visitors.ApiVisitor
diff --git a/src/main/java/com/android/tools/metalava/Baseline.kt b/src/main/java/com/android/tools/metalava/Baseline.kt
index 2ad1638..6328976 100644
--- a/src/main/java/com/android/tools/metalava/Baseline.kt
+++ b/src/main/java/com/android/tools/metalava/Baseline.kt
@@ -32,6 +32,9 @@
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiPackage
import com.intellij.psi.PsiParameter
+import org.jetbrains.kotlin.psi.KtClass
+import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import java.io.File
import java.io.PrintWriter
@@ -130,6 +133,7 @@
private fun getBaselineKey(element: PsiElement): String {
return when (element) {
is PsiClass -> element.qualifiedName ?: element.name ?: "?"
+ is KtClass -> element.fqName?.asString() ?: element.name ?: "?"
is PsiMethod -> {
val containingClass = element.containingClass
val name = element.name
@@ -149,6 +153,15 @@
name
}
}
+ is KtProperty -> {
+ val containingClass = element.containingClass()
+ val name = element.nameAsSafeName.asString()
+ if (containingClass != null) {
+ getBaselineKey(containingClass) + "#" + name
+ } else {
+ name
+ }
+ }
is PsiPackage -> element.qualifiedName
is PsiParameter -> {
val method = element.declarationScope.parent
@@ -222,7 +235,7 @@
private fun write(): Boolean {
val updateFile = this.updateFile ?: return false
- if (!map.isEmpty() || !options.deleteEmptyBaselines) {
+ if (map.isNotEmpty() || !options.deleteEmptyBaselines) {
val sb = StringBuilder()
sb.append(format.header())
sb.append(headerComment)
diff --git a/src/main/java/com/android/tools/metalava/CommandArgsPreprocessor.kt b/src/main/java/com/android/tools/metalava/CommandArgsPreprocessor.kt
index 8325188..a4c7fa8 100644
--- a/src/main/java/com/android/tools/metalava/CommandArgsPreprocessor.kt
+++ b/src/main/java/com/android/tools/metalava/CommandArgsPreprocessor.kt
@@ -16,6 +16,7 @@
package com.android.tools.metalava
+import com.android.SdkConstants.VALUE_FALSE
import com.intellij.util.execution.ParametersListUtil
import java.io.File
import java.io.IOException
@@ -26,7 +27,7 @@
import kotlin.random.Random
/**
- * Proprocess command line arguments.
+ * Preprocess command line arguments.
* 1. Prepend/append {@code ENV_VAR_METALAVA_PREPEND_ARGS} and {@code ENV_VAR_METALAVA_PREPEND_ARGS}
* 2. Reflect --verbose to {@link options#verbose}.
*/
@@ -97,16 +98,16 @@
modifiedArgs: Array<String>
) {
val dumpOption = System.getenv(ENV_VAR_METALAVA_DUMP_ARGV)
- if (dumpOption == null || isUnderTest()) {
+ if (dumpOption == null || dumpOption == VALUE_FALSE || isUnderTest()) {
return
}
// Generate a rerun script, if needed, with the original args.
- if ("script".equals(dumpOption)) {
+ if ("script" == dumpOption) {
generateRerunScript(out, originalArgs)
}
- val fullDump = "full".equals(dumpOption) // Dump rsp file contents too?
+ val fullDump = "full" == dumpOption // Dump rsp file contents too?
dumpArgv(out, "Original args", originalArgs, fullDump)
dumpArgv(out, "Modified args", modifiedArgs, fullDump)
@@ -168,13 +169,13 @@
// Metalava's jar path.
val jar = ApiLint::class.java.protectionDomain?.codeSource?.location?.toURI()?.path
// JVM options.
- val jvmOptions = ManagementWrapper.getVmArguments()
+ val jvmOptions = runtimeMXBean.inputArguments
if (jvmOptions == null || jar == null) {
stdout.println("$PROGRAM_NAME unable to get my jar file path.")
return
}
- val script = File(scriptBaseName + ".sh")
+ val script = File("$scriptBaseName.sh")
var optFileIndex = 0
script.printWriter().use { out ->
out.println("""
diff --git a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
index 6b9498c..f6198e7 100644
--- a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
+++ b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
@@ -28,7 +28,6 @@
import com.android.tools.metalava.model.PropertyItem
import com.android.tools.metalava.model.VisitCandidate
import com.android.tools.metalava.model.visitors.ApiVisitor
-import com.android.tools.metalava.model.visitors.VisibleItemVisitor
import com.intellij.util.containers.Stack
import java.util.Comparator
import java.util.function.Predicate
@@ -212,7 +211,7 @@
private fun visitAdded(visitor: ComparisonVisitor, new: Item) {
if (visitor.visitAddedItemsRecursively) {
- new.accept(object : VisibleItemVisitor() {
+ new.accept(object : ApiVisitor() {
override fun visitItem(item: Item) {
doVisitAdded(visitor, item)
}
diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
index d10c684..db2c4e9 100644
--- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
+++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
@@ -21,7 +21,7 @@
import com.android.tools.metalava.doclava1.ApiPredicate
import com.android.tools.metalava.doclava1.Issues
import com.android.tools.metalava.doclava1.Issues.Issue
-import com.android.tools.metalava.doclava1.TextCodebase
+import com.android.tools.metalava.model.text.TextCodebase
import com.android.tools.metalava.model.AnnotationItem
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.Codebase
@@ -517,9 +517,12 @@
}
for (exec in new.filteredThrowsTypes(filterReference)) {
- // exclude 'throws' changes to finalize() overrides with no arguments
if (!old.throws(exec.qualifiedName())) {
- if (old.name() != "finalize" || old.parameters().isNotEmpty()) {
+ // exclude 'throws' changes to finalize() overrides with no arguments
+ if (!(old.name() == "finalize" && old.parameters().isEmpty()) &&
+ // exclude cases where throws clause was missing in signatures from
+ // old enum methods
+ !old.isEnumSyntheticMethod()) {
val message = "${describe(new, capitalize = true)} added thrown exception ${exec.qualifiedName()}"
report(Issues.CHANGED_THROWS, new, message)
}
@@ -529,7 +532,7 @@
if (new.modifiers.isInline()) {
val oldTypes = old.typeParameterList().typeParameters()
val newTypes = new.typeParameterList().typeParameters()
- for (i in 0 until oldTypes.size) {
+ for (i in oldTypes.indices) {
if (i == newTypes.size) {
break
}
@@ -717,8 +720,8 @@
is ClassItem -> base.findClass(item.qualifiedName())
is MethodItem -> base.findClass(item.containingClass().qualifiedName())?.findMethod(
item,
- true,
- true
+ includeSuperClasses = true,
+ includeInterfaces = true
)
is FieldItem -> base.findClass(item.containingClass().qualifiedName())?.findField(item.name())
else -> null
@@ -772,9 +775,7 @@
}
// Builtin annotation methods: just a difference in signature file
- if ((new.name() == "values" && new.parameters().isEmpty() || new.name() == "valueOf" &&
- new.parameters().size == 1) && new.containingClass().isEnum()
- ) {
+ if (new.isEnumSyntheticMethod()) {
return
}
@@ -879,8 +880,8 @@
configuration = previousConfiguration
}
- val message = "Aborting: Found compatibility problems checking " +
- "the ${apiType.displayName} API against the API in ${previous.location}"
+ val message = "Found compatibility problems checking " +
+ "the ${apiType.displayName} API (${codebase.location}) against the API in ${previous.location}"
if (checker.foundProblems) {
throw DriverException(exitCode = -1, stderr = message)
diff --git a/src/main/java/com/android/tools/metalava/Constants.kt b/src/main/java/com/android/tools/metalava/Constants.kt
index dd071a2..a763b56 100644
--- a/src/main/java/com/android/tools/metalava/Constants.kt
+++ b/src/main/java/com/android/tools/metalava/Constants.kt
@@ -39,6 +39,10 @@
const val ATTR_OTHERWISE = "otherwise"
const val CARRIER_PRIVILEGES_MARKER = "carrier privileges"
+const val ANDROID_SUPPRESS_LINT = "android.annotation.SuppressLint"
+const val JAVA_LANG_SUPPRESS_WARNINGS = "java.lang.SuppressWarnings"
+const val KOTLIN_SUPPRESS = "kotlin.Suppress"
+
const val ENV_VAR_METALAVA_TESTS_RUNNING = "METALAVA_TESTS_RUNNING"
const val ENV_VAR_METALAVA_DUMP_ARGV = "METALAVA_DUMP_ARGV"
const val ENV_VAR_METALAVA_PREPEND_ARGS = "METALAVA_PREPEND_ARGS"
diff --git a/src/main/java/com/android/tools/metalava/DexApiWriter.kt b/src/main/java/com/android/tools/metalava/DexApiWriter.kt
index b779a03..fc20f6b 100644
--- a/src/main/java/com/android/tools/metalava/DexApiWriter.kt
+++ b/src/main/java/com/android/tools/metalava/DexApiWriter.kt
@@ -27,29 +27,19 @@
class DexApiWriter(
private val writer: PrintWriter,
filterEmit: Predicate<Item>,
- filterReference: Predicate<Item>,
- inlineInheritedFields: Boolean = true,
- private val membersOnly: Boolean = false,
- private val includePositions: Boolean = false
+ filterReference: Predicate<Item>
) : ApiVisitor(
visitConstructorsAsMethods = true,
nestInnerClasses = false,
- inlineInheritedFields = inlineInheritedFields,
+ inlineInheritedFields = true,
filterEmit = filterEmit,
filterReference = filterReference
) {
override fun visitClass(cls: ClassItem) {
- if (membersOnly) {
- return
- }
-
if (filterEmit.test(cls)) {
writer.print(cls.toType().internalName())
writer.print("\n")
}
- if (includePositions) {
- writeLocation(cls)
- }
}
override fun visitMethod(method: MethodItem) {
@@ -72,9 +62,6 @@
writer.print(returnType?.internalName() ?: "V")
}
writer.print("\n")
- if (includePositions) {
- writeLocation(method)
- }
}
override fun visitField(field: FieldItem) {
@@ -86,15 +73,5 @@
writer.print(":")
writer.print(field.type().internalName())
writer.print("\n")
- if (includePositions) {
- writeLocation(field)
- }
- }
-
- private fun writeLocation(item: Item) {
- val psiItem =
- item.psi() ?: throw DriverException(stderr = "$ARG_DEX_API_MAPPING should only be used on source trees")
- val location = reporter.elementToLocation(psiItem, false)
- writer.println(location ?: "<unknown>:-1")
}
}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/Diff.kt b/src/main/java/com/android/tools/metalava/Diff.kt
index cd7f2e4..e1227f7 100644
--- a/src/main/java/com/android/tools/metalava/Diff.kt
+++ b/src/main/java/com/android/tools/metalava/Diff.kt
@@ -22,6 +22,8 @@
import com.android.utils.LineCollector
import com.android.utils.StdLogger
import java.io.File
+import kotlin.math.max
+import kotlin.math.min
// Copied from lint's test suite: TestUtils.diff in tools/base with
// some changes to allow running native diff.
@@ -81,7 +83,7 @@
if (before[i] == after[j]) {
lcs[i][j] = lcs[i + 1][j + 1] + 1
} else {
- lcs[i][j] = Math.max(lcs[i + 1][j], lcs[i][j + 1])
+ lcs[i][j] = max(lcs[i + 1][j], lcs[i][j + 1])
}
}
}
@@ -94,13 +96,13 @@
j++
} else {
sb.append("@@ -")
- sb.append(Integer.toString(i + 1))
+ sb.append((i + 1).toString())
sb.append(" +")
- sb.append(Integer.toString(j + 1))
+ sb.append((j + 1).toString())
sb.append('\n')
if (windowSize > 0) {
- for (context in Math.max(0, i - windowSize) until i) {
+ for (context in max(0, i - windowSize) until i) {
sb.append(" ")
sb.append(before[context])
sb.append("\n")
@@ -110,7 +112,7 @@
while (i < n && j < m && before[i] != after[j]) {
if (lcs[i + 1][j] >= lcs[i][j + 1]) {
sb.append('-')
- if (!before[i].trim { it <= ' ' }.isEmpty()) {
+ if (before[i].trim().isNotEmpty()) {
sb.append(' ')
}
sb.append(before[i])
@@ -118,7 +120,7 @@
i++
} else {
sb.append('+')
- if (!after[j].trim { it <= ' ' }.isEmpty()) {
+ if (after[j].trim().isNotEmpty()) {
sb.append(' ')
}
sb.append(after[j])
@@ -128,7 +130,7 @@
}
if (windowSize > 0) {
- for (context in i until Math.min(n, i + windowSize)) {
+ for (context in i until min(n, i + windowSize)) {
sb.append(" ")
sb.append(before[context])
sb.append("\n")
@@ -140,13 +142,13 @@
if (i < n || j < m) {
assert(i == n || j == m)
sb.append("@@ -")
- sb.append(Integer.toString(i + 1))
+ sb.append((i + 1).toString())
sb.append(" +")
- sb.append(Integer.toString(j + 1))
+ sb.append((j + 1).toString())
sb.append('\n')
while (i < n) {
sb.append('-')
- if (!before[i].trim { it <= ' ' }.isEmpty()) {
+ if (before[i].trim().isNotEmpty()) {
sb.append(' ')
}
sb.append(before[i])
@@ -155,7 +157,7 @@
}
while (j < m) {
sb.append('+')
- if (!after[j].trim { it <= ' ' }.isEmpty()) {
+ if (after[j].trim().isNotEmpty()) {
sb.append(' ')
}
sb.append(after[j])
diff --git a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
index b65a6f5..fd7fb6d 100644
--- a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
@@ -20,7 +20,6 @@
import com.android.tools.metalava.model.ParameterItem
import com.android.tools.metalava.model.psi.containsLinkTags
import com.android.tools.metalava.model.visitors.ApiVisitor
-import com.android.tools.metalava.model.visitors.VisibleItemVisitor
import com.google.common.io.Files
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiField
@@ -28,6 +27,7 @@
import java.io.File
import java.util.HashMap
import java.util.regex.Pattern
+import kotlin.math.min
/**
* Whether to include textual descriptions of the API requirements instead
@@ -80,7 +80,7 @@
artifacts.tag(codebase)
- codebase.accept(object : VisibleItemVisitor() {
+ codebase.accept(object : ApiVisitor() {
override fun visitClass(cls: ClassItem) {
cls.artifact?.let {
cls.appendDocumentation(it, "@artifactId")
@@ -423,7 +423,7 @@
if (filterReference.test(field)) {
sb.append("{@link ${field.containingClass().qualifiedName()}#${field.name()}}")
} else {
- // Typdef annotation references field which isn't part of the API: don't
+ // Typedef annotation references field which isn't part of the API: don't
// try to link to it.
reporter.report(
Issues.HIDDEN_TYPEDEF_CONSTANT, item,
@@ -625,6 +625,7 @@
}
/** Replacements to perform in documentation */
+ @Suppress("SpellCheckingInspection")
val typos = mapOf(
"JetPack" to "Jetpack",
"Andriod" to "Android",
@@ -637,7 +638,7 @@
)
private fun tweakGrammar() {
- codebase.accept(object : VisibleItemVisitor() {
+ codebase.accept(object : ApiVisitor() {
override fun visitItem(item: Item) {
var doc = item.documentation
if (doc.isBlank()) {
@@ -738,7 +739,7 @@
// Compute since version for the package: it's the min of all the classes in the package
val pkg = cls.containingPackage()
- pkgApi[pkg] = Math.min(pkgApi[pkg] ?: Integer.MAX_VALUE, since)
+ pkgApi[pkg] = min(pkgApi[pkg] ?: Integer.MAX_VALUE, since)
}
addDeprecatedDocumentation(apiLookup.getClassDeprecatedIn(psiClass), cls)
}
@@ -839,7 +840,11 @@
fun ApiLookup.getMethodVersion(method: PsiMethod): Int {
val containingClass = method.containingClass ?: return -1
val owner = containingClass.qualifiedName ?: return -1
- val desc = defaultEvaluator.getMethodDescription(method, false, false)
+ val desc = defaultEvaluator.getMethodDescription(
+ method,
+ includeName = false,
+ includeReturn = false
+ )
return getMethodVersion(owner, if (method.isConstructor) "<init>" else method.name, desc)
}
@@ -857,7 +862,11 @@
fun ApiLookup.getMethodDeprecatedIn(method: PsiMethod): Int {
val containingClass = method.containingClass ?: return -1
val owner = containingClass.qualifiedName ?: return -1
- val desc = defaultEvaluator.getMethodDescription(method, false, false)
+ val desc = defaultEvaluator.getMethodDescription(
+ method,
+ includeName = false,
+ includeReturn = false
+ )
return getMethodDeprecatedIn(owner, method.name, desc)
}
diff --git a/src/main/java/com/android/tools/metalava/DocReplacement.kt b/src/main/java/com/android/tools/metalava/DocReplacement.kt
index bfe587a..6ff46b4 100644
--- a/src/main/java/com/android/tools/metalava/DocReplacement.kt
+++ b/src/main/java/com/android/tools/metalava/DocReplacement.kt
@@ -18,17 +18,17 @@
import com.android.tools.metalava.model.Item
import com.android.tools.metalava.model.PackageItem
-import com.android.tools.metalava.model.visitors.VisibleItemVisitor
+import com.android.tools.metalava.model.visitors.ApiVisitor
/**
- * A {@link VisibleItemVisitor} that applies a regex replacement to the documentation of
- * {@link Item}s from the given packages and their sub-packages.
+ * An [ApiVisitor] that applies a regex replacement to the documentation of
+ * [Item]s from the given packages and their sub-packages.
*/
class DocReplacement(
private val packageNames: List<String>,
private val regex: Regex,
private val replacement: String
-) : VisibleItemVisitor() {
+) : ApiVisitor() {
private fun appliesToPackage(packageItem: PackageItem): Boolean {
val fqn = packageItem.qualifiedName()
diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt
index 1241a16..6198796 100644
--- a/src/main/java/com/android/tools/metalava/Driver.kt
+++ b/src/main/java/com/android/tools/metalava/Driver.kt
@@ -26,24 +26,22 @@
import com.android.ide.common.process.ProcessInfoBuilder
import com.android.ide.common.process.ProcessOutput
import com.android.ide.common.process.ProcessOutputHandler
-import com.android.tools.lint.KotlinLintAnalyzerFacade
-import com.android.tools.lint.LintCoreApplicationEnvironment
-import com.android.tools.lint.LintCoreProjectEnvironment
+import com.android.tools.lint.UastEnvironment
import com.android.tools.lint.annotations.Extractor
import com.android.tools.lint.checks.infrastructure.ClassName
import com.android.tools.lint.detector.api.assertionsEnabled
import com.android.tools.metalava.CompatibilityCheck.CheckRequest
import com.android.tools.metalava.apilevels.ApiGenerator
import com.android.tools.metalava.doclava1.ApiPredicate
-import com.android.tools.metalava.doclava1.Issues
import com.android.tools.metalava.doclava1.FilterPredicate
-import com.android.tools.metalava.doclava1.TextCodebase
+import com.android.tools.metalava.doclava1.Issues
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.Codebase
import com.android.tools.metalava.model.Item
import com.android.tools.metalava.model.PackageDocs
import com.android.tools.metalava.model.psi.PsiBasedCodebase
import com.android.tools.metalava.model.psi.packageHtmlToJavadoc
+import com.android.tools.metalava.model.text.TextCodebase
import com.android.tools.metalava.model.visitors.ApiVisitor
import com.android.tools.metalava.stub.StubWriter
import com.android.utils.StdLogger
@@ -53,20 +51,20 @@
import com.google.common.io.Files
import com.intellij.core.CoreApplicationEnvironment
import com.intellij.openapi.diagnostic.DefaultLogger
-import com.intellij.openapi.extensions.Extensions
-import com.intellij.openapi.roots.LanguageLevelProjectExtension
import com.intellij.openapi.util.Disposer
import com.intellij.pom.java.LanguageLevel
import com.intellij.psi.javadoc.CustomJavadocTagProvider
import com.intellij.psi.javadoc.JavadocTagInfo
+import org.jetbrains.kotlin.config.CommonConfigurationKeys.MODULE_NAME
+import org.jetbrains.kotlin.config.LanguageVersionSettings
import java.io.File
import java.io.IOException
import java.io.OutputStream
import java.io.OutputStreamWriter
import java.io.PrintWriter
-import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.SECONDS
import java.util.function.Predicate
+import kotlin.system.exitProcess
import kotlin.text.Charsets.UTF_8
const val PROGRAM_NAME = "metalava"
@@ -118,26 +116,36 @@
processFlags()
if (options.allReporters.any { it.hasErrors() } && !options.passBaselineUpdates) {
+ // Repeat the errors at the end to make it easy to find the actual problems.
+ if (options.repeatErrorsMax > 0) {
+ repeatErrors(stderr, options.allReporters, options.repeatErrorsMax)
+ }
exitCode = -1
}
if (hasFileReadViolations) {
- stderr.println("$PROGRAM_NAME detected access to files that are not explicitly specified. See ${options.strictInputViolationsFile} for details.")
- if (options.strictInputFiles == Options.StrictInputFileMode.STRICT) {
+ if (options.strictInputFiles.shouldFail) {
+ stderr.print("Error: ")
exitCode = -1
+ } else {
+ stderr.print("Warning: ")
}
+ stderr.println("$PROGRAM_NAME detected access to files that are not explicitly specified. See ${options.strictInputViolationsFile} for details.")
}
} catch (e: DriverException) {
stdout.flush()
stderr.flush()
+
+ val prefix = if (e.exitCode != 0) { "Aborting: " } else { "" }
+
if (e.stderr.isNotBlank()) {
- stderr.println("\n${e.stderr}")
+ stderr.println("\n${prefix}${e.stderr}")
}
if (e.stdout.isNotBlank()) {
- stdout.println("\n${e.stdout}")
+ stdout.println("\n${prefix}${e.stdout}")
}
exitCode = e.exitCode
} finally {
- Disposer.dispose(LintCoreApplicationEnvironment.get().parentDisposable)
+ disposeUastEnvironment()
}
// Update and close all baseline files.
@@ -176,7 +184,7 @@
}
options.stdout.flush()
options.stderr.flush()
- System.exit(exitCode)
+ exitProcess(exitCode)
}
private fun maybeActivateSandbox() {
@@ -214,6 +222,21 @@
})
}
+private fun repeatErrors(writer: PrintWriter, reporters: List<Reporter>, max: Int) {
+ writer.println("Error: $PROGRAM_NAME detected the following problems:")
+ val totalErrors = reporters.sumBy { it.errorCount }
+ var remainingCap = max
+ var totalShown = 0
+ reporters.forEach {
+ var numShown = it.printErrors(writer, remainingCap)
+ remainingCap -= numShown
+ totalShown += numShown
+ }
+ if (totalShown < totalErrors) {
+ writer.println("${totalErrors - totalShown} more error(s) omitted. Search the log for 'error:' to find all of them.")
+ }
+}
+
private fun processFlags() {
val stopwatch = Stopwatch.createStarted()
@@ -221,7 +244,7 @@
val sources = options.sources
val codebase =
- if (sources.size >= 1 && sources[0].path.endsWith(DOT_TXT)) {
+ if (sources.isNotEmpty() && sources[0].path.endsWith(DOT_TXT)) {
// Make sure all the source files have .txt extensions.
sources.firstOrNull { !it.path.endsWith(DOT_TXT) }?. let {
throw DriverException("Inconsistent input file types: The first file is of $DOT_TXT, but detected different extension in ${it.path}")
@@ -239,7 +262,7 @@
options.manifest?.let { codebase.manifest = it }
if (options.verbose) {
- progress("$PROGRAM_NAME analyzed API in ${stopwatch.elapsed(TimeUnit.SECONDS)} seconds\n")
+ progress("$PROGRAM_NAME analyzed API in ${stopwatch.elapsed(SECONDS)} seconds\n")
}
options.subtractApi?.let {
@@ -286,17 +309,6 @@
}
}
- options.dexApiFile?.let { apiFile ->
- val apiFilter = FilterPredicate(ApiPredicate())
- val memberIsNotCloned: Predicate<Item> = Predicate { !it.isCloned() }
- val apiReference = ApiPredicate(ignoreShown = true)
- val dexApiEmit = memberIsNotCloned.and(apiFilter)
-
- createReportFile(
- codebase, apiFile, "DEX API"
- ) { printWriter -> DexApiWriter(printWriter, dexApiEmit, apiReference) }
- }
-
options.apiXmlFile?.let { apiFile ->
val apiType = ApiType.PUBLIC_API
val apiEmit = apiType.getEmitFilter()
@@ -307,22 +319,6 @@
}
}
- options.dexApiMappingFile?.let { apiFile ->
- val apiType = ApiType.ALL
- val apiEmit = apiType.getEmitFilter()
- val apiReference = apiType.getReferenceFilter()
-
- createReportFile(
- codebase, apiFile, "DEX API Mapping"
- ) { printWriter ->
- DexApiWriter(
- printWriter, apiEmit, apiReference,
- membersOnly = true,
- includePositions = true
- )
- }
- }
-
options.removedApiFile?.let { apiFile ->
val unfiltered = codebase.original ?: codebase
@@ -348,38 +344,6 @@
) { printWriter -> DexApiWriter(printWriter, removedDexEmit, removedReference) }
}
- options.privateApiFile?.let { apiFile ->
- val apiType = ApiType.PRIVATE
- val privateEmit = apiType.getEmitFilter()
- val privateReference = apiType.getReferenceFilter()
-
- createReportFile(codebase, apiFile, "private API") { printWriter ->
- SignatureWriter(printWriter, privateEmit, privateReference, codebase.original != null)
- }
- }
-
- options.privateDexApiFile?.let { apiFile ->
- val apiFilter = FilterPredicate(ApiPredicate())
- val privateEmit = apiFilter.negate()
- val privateReference = Predicate<Item> { true }
-
- createReportFile(
- codebase, apiFile, "private DEX API"
- ) { printWriter ->
- DexApiWriter(
- printWriter, privateEmit, privateReference, inlineInheritedFields = false
- )
- }
- }
-
- options.proguard?.let { proguard ->
- val apiEmit = FilterPredicate(ApiPredicate())
- val apiReference = ApiPredicate(ignoreShown = true)
- createReportFile(
- codebase, proguard, "Proguard file"
- ) { printWriter -> ProguardWriter(printWriter, apiEmit, apiReference) }
- }
-
options.sdkValueDir?.let { dir ->
dir.mkdirs()
SdkFileWriter(codebase, dir).generate()
@@ -480,7 +444,6 @@
CodebaseComparator().compare(object : ComparisonVisitor() {
override fun compare(old: ClassItem, new: ClassItem) {
- new.included = false
new.emit = false
}
}, oldCodebase, codebase, ApiType.ALL.getReferenceFilter())
@@ -673,7 +636,7 @@
""
val message =
"""
- Aborting: Your changes have resulted in differences in the signature file
+ Your changes have resulted in differences in the signature file
for the ${apiType.displayName} API.
The changes may be compatible, but the signature file needs to be updated.
@@ -845,12 +808,6 @@
options.nullabilityAnnotationsValidator?.report()
analyzer.handleStripping()
- val apiLintReporter = options.reporterApiLint
-
- if (options.checkKotlinInterop) {
- KotlinInteropChecks(apiLintReporter).check(codebase)
- }
-
// General API checks for Android APIs
AndroidApiChecks().check(codebase)
@@ -868,6 +825,7 @@
kotlinStyleNulls = options.inputKotlinStyleNulls
)
}
+ val apiLintReporter = options.reporterApiLint
ApiLint.check(codebase, previous, apiLintReporter)
progress("$PROGRAM_NAME ran api-lint in ${localTimer.elapsed(SECONDS)} seconds with ${apiLintReporter.getBaselineDescription()}")
}
@@ -897,59 +855,51 @@
sourcePath: List<File> = options.sourcePath,
classpath: List<File> = options.classpath,
javaLanguageLevel: LanguageLevel = options.javaLanguageLevel,
+ kotlinLanguageLevel: LanguageVersionSettings = options.kotlinLanguageLevel,
manifest: File? = options.manifest,
currentApiLevel: Int = options.currentApiLevel + if (options.currentCodeName != null) 1 else 0
): PsiBasedCodebase {
- val projectEnvironment = createProjectEnvironment()
- val project = projectEnvironment.project
-
- // Push language level to PSI handler
- project.getComponent(LanguageLevelProjectExtension::class.java)?.languageLevel = javaLanguageLevel
-
- val joined = mutableListOf<File>()
- joined.addAll(sourcePath.mapNotNull { if (it.path.isNotBlank()) it.absoluteFile else null })
- joined.addAll(classpath.map { it.absoluteFile })
-
- // Add in source roots implied by the source files
val sourceRoots = mutableListOf<File>()
+ sourcePath.filterTo(sourceRoots) { it.path.isNotBlank() }
+ // Add in source roots implied by the source files
if (options.allowImplicitRoot) {
extractRoots(sources, sourceRoots)
- joined.addAll(sourceRoots)
}
- // Create project environment with those paths
- projectEnvironment.registerPaths(joined)
+ val config = UastEnvironment.Configuration.create()
+ config.javaLanguageLevel = javaLanguageLevel
+ config.kotlinLanguageLevel = kotlinLanguageLevel
+ config.addSourceRoots(sourceRoots.map { it.absoluteFile })
+ config.addClasspathRoots(classpath.map { it.absoluteFile })
+
+ val environment = createProjectEnvironment(config)
val kotlinFiles = sources.filter { it.path.endsWith(DOT_KT) }
- val trace = KotlinLintAnalyzerFacade().analyze(kotlinFiles, joined, project)
+ environment.analyzeFiles(kotlinFiles)
val rootDir = sourceRoots.firstOrNull() ?: sourcePath.firstOrNull() ?: File("").canonicalFile
- val units = Extractor.createUnitsForFiles(project, sources)
+ val units = Extractor.createUnitsForFiles(environment.ideaProject, sources)
val packageDocs = gatherHiddenPackagesFromJavaDocs(sourcePath)
val codebase = PsiBasedCodebase(rootDir, description)
- codebase.initialize(project, units, packageDocs)
+ codebase.initialize(environment, units, packageDocs)
codebase.manifest = manifest
codebase.apiLevel = currentApiLevel
- codebase.bindingContext = trace.bindingContext
return codebase
}
fun loadFromJarFile(apiJar: File, manifest: File? = null, preFiltered: Boolean = false): Codebase {
- val projectEnvironment = createProjectEnvironment()
-
progress("Processing jar file: ")
- // Create project environment with those paths
- val project = projectEnvironment.project
- projectEnvironment.registerPaths(listOf(apiJar))
+ val config = UastEnvironment.Configuration.create()
+ config.addClasspathRoots(listOf(apiJar))
- val kotlinFiles = emptyList<File>()
- val trace = KotlinLintAnalyzerFacade().analyze(kotlinFiles, listOf(apiJar), project)
+ val environment = createProjectEnvironment(config)
+ environment.analyzeFiles(emptyList()) // Initializes PSI machinery.
val codebase = PsiBasedCodebase(apiJar, "Codebase loaded from $apiJar")
- codebase.initialize(project, apiJar, preFiltered)
+ codebase.initialize(environment, apiJar, preFiltered)
if (manifest != null) {
codebase.manifest = options.manifest
}
@@ -962,48 +912,55 @@
options.nullabilityAnnotationsValidator?.validateAllFrom(codebase, options.validateNullabilityFromList)
options.nullabilityAnnotationsValidator?.report()
analyzer.generateInheritedStubs(apiEmit, apiReference)
- codebase.bindingContext = trace.bindingContext
return codebase
}
-private fun loadFromApiSignatureFiles(files: List<File>, kotlinStyleNulls: Boolean? = null): Codebase {
- // Make sure all the source files have .txt extensions.
- files.forEach { file ->
- if (!file.path.endsWith(DOT_TXT)) {
- throw DriverException("Inconsistent input file types: The first file is of .$DOT_TXT, but detected different extension in ${file.path}")
- }
- }
- return SignatureFileLoader.loadFiles(files, kotlinStyleNulls)
-}
+internal const val METALAVA_SYNTHETIC_SUFFIX = "metalava_module"
-private fun createProjectEnvironment(): LintCoreProjectEnvironment {
+private fun createProjectEnvironment(config: UastEnvironment.Configuration): UastEnvironment {
ensurePsiFileCapacity()
- val appEnv = LintCoreApplicationEnvironment.get()
- val parentDisposable = appEnv.parentDisposable
+
+ // Note: the Kotlin module name affects the naming of certain synthetic methods.
+ config.kotlinCompilerConfig.put(MODULE_NAME, METALAVA_SYNTHETIC_SUFFIX)
+
+ val environment = UastEnvironment.create(config)
+ uastEnvironments.add(environment)
if (!assertionsEnabled() &&
System.getenv(ENV_VAR_METALAVA_DUMP_ARGV) == null &&
!isUnderTest()
) {
- DefaultLogger.disableStderrDumping(parentDisposable)
+ DefaultLogger.disableStderrDumping(environment.ideaProject)
}
- val environment = LintCoreProjectEnvironment.create(parentDisposable, appEnv)
-
// Missing service needed in metalava but not in lint: javadoc handling
- environment.project.registerService(
+ environment.ideaProject.registerService(
com.intellij.psi.javadoc.JavadocManager::class.java,
com.intellij.psi.impl.source.javadoc.JavadocManagerImpl::class.java
)
- environment.registerProjectExtensionPoint(JavadocTagInfo.EP_NAME,
- com.intellij.psi.javadoc.JavadocTagInfo::class.java)
CoreApplicationEnvironment.registerExtensionPoint(
- Extensions.getRootArea(), CustomJavadocTagProvider.EP_NAME, CustomJavadocTagProvider::class.java
+ environment.ideaProject.extensionArea, JavadocTagInfo.EP_NAME, JavadocTagInfo::class.java
+ )
+ CoreApplicationEnvironment.registerApplicationExtensionPoint(
+ CustomJavadocTagProvider.EP_NAME, CustomJavadocTagProvider::class.java
)
return environment
}
+private val uastEnvironments = mutableListOf<UastEnvironment>()
+
+private fun disposeUastEnvironment() {
+ // Codebase.dispose() is not consistently called, so we dispose the environments here too.
+ for (env in uastEnvironments) {
+ if (!Disposer.isDisposed(env.ideaProject)) {
+ env.dispose()
+ }
+ }
+ uastEnvironments.clear()
+ UastEnvironment.disposeApplicationEnvironment()
+}
+
private fun ensurePsiFileCapacity() {
val fileSize = System.getProperty("idea.max.intellisense.filesize")
if (fileSize == null) {
@@ -1153,7 +1110,7 @@
}
addSourceFiles(sources, file.absoluteFile)
}
- return sources.sortedWith(compareBy({ it.name }))
+ return sources.sortedWith(compareBy { it.name })
}
private fun addHiddenPackages(
@@ -1163,7 +1120,7 @@
file: File,
pkg: String
) {
- if (file.isDirectory) {
+ if (FileReadSandbox.isDirectory(file)) {
if (skippableDirectory(file)) {
return
}
@@ -1179,7 +1136,7 @@
if (files != null) {
for (child in files) {
var subPkg =
- if (child.isDirectory)
+ if (FileReadSandbox.isDirectory(child))
if (pkg.isEmpty())
child.name
else pkg + "." + child.name
@@ -1194,13 +1151,13 @@
addHiddenPackages(packageToDoc, packageToOverview, hiddenPackages, child, subPkg)
}
}
- } else if (file.isFile) {
+ } else if (FileReadSandbox.isFile(file)) {
var javadoc = false
- val map = when {
- file.name == "package.html" -> {
+ val map = when (file.name) {
+ "package.html" -> {
javadoc = true; packageToDoc
}
- file.name == "overview.html" -> {
+ "overview.html" -> {
packageToOverview
}
else -> return
@@ -1216,7 +1173,7 @@
// passed in (and is instead some directory containing the source path)
// then we compute the wrong package here. Instead, look for an adjacent
// java class and pick the package from it
- for (sibling in file.parentFile.listFiles()) {
+ for (sibling in file.parentFile?.listFiles() ?: emptyArray()) {
if (sibling.path.endsWith(DOT_JAVA)) {
val javaPkg = ClassName(sibling.readText()).packageName
if (javaPkg != null) {
diff --git a/src/main/java/com/android/tools/metalava/FileFormat.kt b/src/main/java/com/android/tools/metalava/FileFormat.kt
index 7e895e0..ed0c4c5 100644
--- a/src/main/java/com/android/tools/metalava/FileFormat.kt
+++ b/src/main/java/com/android/tools/metalava/FileFormat.kt
@@ -48,7 +48,7 @@
return this >= V3
}
- fun signatureFormatAsInt(): Int {
+ private fun signatureFormatAsInt(): Int {
return when (this) {
V1 -> 1
V2 -> 2
@@ -88,7 +88,7 @@
return prefix + version + "\n"
}
- fun headerPrefix(): String? {
+ private fun headerPrefix(): String? {
return when (this) {
V1 -> null
V2, V3 -> "// Signature format: "
@@ -132,7 +132,7 @@
return UNKNOWN
}
// Both JDiff and API-level files use <api> as the root tag (unfortunate but too late to
- // change) so distinguish on whether the file contains any since elementss
+ // change) so distinguish on whether the file contains any since elements
if (fileContents.contains("since=")) {
return SINCE_XML
}
diff --git a/src/main/java/com/android/tools/metalava/FileReadSandbox.kt b/src/main/java/com/android/tools/metalava/FileReadSandbox.kt
index a4ebb98..7c5542b 100644
--- a/src/main/java/com/android/tools/metalava/FileReadSandbox.kt
+++ b/src/main/java/com/android/tools/metalava/FileReadSandbox.kt
@@ -25,13 +25,13 @@
* Detect access to files that not explicitly specified in the command line.
*
* This class detects reads on both files and directories. Directory accesses are logged by
- * [Driver], which only logs it, but doesn't consider it an error.
+ * the driver in [run], which only logs it, but doesn't consider it an error.
*
* We do not prevent reads on directories that are not explicitly listed in the command line because
* metalava (or JVM, really) seems to read some system directories such as /usr/, etc., but this
- * behavior may be JVM dependent so we do not want to have to explicitly whitelist them.
+ * behavior may be JVM dependent so we do not want to have to explicitly include them.
* (Because, otherwise, when we update JVM, it may access different directories and we end up
- * having to update the implicit whitelist.) As long as we don't read files, reading directories
+ * having to update the implicit allowed list.) As long as we don't read files, reading directories
* shouldn't (normally) affect the result, so we simply allow any directory reads.
*/
internal object FileReadSandbox {
@@ -88,8 +88,8 @@
"JAVA_HOME",
"ANDROID_JAVA_HOME"
).forEach {
- System.getenv(it)?.let {
- allowAccess(File(it))
+ System.getenv(it)?.let { path ->
+ allowAccess(File(path))
}
}
// JVM seems to use ~/.cache/
@@ -109,7 +109,7 @@
this.listener = listener
}
- /** Deactivate the sandbox. This also resets [violationCount]. */
+ /** Deactivate the sandbox. */
fun deactivate() {
if (!installed) {
throw IllegalStateException("Not activated")
@@ -151,7 +151,7 @@
return file
}
- // Whitelist all parent directories. But don't allow prefix accesses (== access to the
+ // Allow all parent directories. But don't allow prefix accesses (== access to the
// directory itself is okay, but don't grant access to any files/directories under it).
var parent = file.parentFile
while (true) {
@@ -188,6 +188,24 @@
return false
}
+ fun isDirectory(file: File): Boolean {
+ try {
+ temporaryExempt.set(true)
+ return file.isDirectory()
+ } finally {
+ temporaryExempt.set(false)
+ }
+ }
+
+ fun isFile(file: File): Boolean {
+ try {
+ temporaryExempt.set(true)
+ return file.isFile()
+ } finally {
+ temporaryExempt.set(false)
+ }
+ }
+
/** Used to skip all checks on any filesystem access made within the [check] method. */
private val temporaryExempt = ThreadLocal<Boolean>()
@@ -211,9 +229,11 @@
/**
* Reading files that are created by metalava should be allowed, so we detect file writes to
- * new files, and whitelist it.
+ * new files, and add them to the allowed path list.
*/
private fun writeDetected(origPath: String?) {
+ origPath ?: return
+
if (temporaryExempt.getOrSet { false }) {
return
}
diff --git a/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt b/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
index 47bda71..1b51857 100644
--- a/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
+++ b/src/main/java/com/android/tools/metalava/JDiffXmlWriter.kt
@@ -66,6 +66,9 @@
writer.print("\"")
}
+ // Specify metalava schema used for metalava:enumConstant
+ writer.print(" xmlns:metalava=\"http://www.android.com/metalava/\"")
+
writer.println(">")
}
diff --git a/src/main/java/com/android/tools/metalava/JavaManagement.kt b/src/main/java/com/android/tools/metalava/JavaManagement.kt
new file mode 100644
index 0000000..c5d1a50
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/JavaManagement.kt
@@ -0,0 +1,28 @@
+/*
+ * 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
+
+import java.lang.management.ManagementFactory
+
+/** Returns the [ThreadMXBean] singleton. */
+val threadMXBean = ManagementFactory.getThreadMXBean()
+
+/** Returns the [RuntimeMXBean] singleton. */
+val runtimeMXBean = ManagementFactory.getRuntimeMXBean()
+
+/** Returns the [ThreadMXBean] singleton. */
+val memoryMXBean = ManagementFactory.getMemoryMXBean()
diff --git a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
index 50a49ad..04f644e 100644
--- a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
+++ b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt
@@ -30,6 +30,7 @@
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
+import org.jetbrains.kotlin.psi.psiUtil.isPublic
import org.jetbrains.uast.kotlin.KotlinUField
// Enforces the interoperability guidelines outlined in
@@ -42,7 +43,9 @@
codebase.accept(object : ApiVisitor(
// Sort by source order such that warnings follow source line number order
methodComparator = MethodItem.sourceOrderComparator,
- fieldComparator = FieldItem.comparator
+ fieldComparator = FieldItem.comparator,
+ // No need to check "for stubs only APIs" (== "implicit" APIs)
+ includeApisForStubPurposes = false
) {
private var isKotlin = false
@@ -155,13 +158,16 @@
// object that are not annotated with @JvmField or annotated with @JvmStatic
// https://developer.android.com/kotlin/interop#companion_constants
val ktProperties = sourcePsi.declarations.filter { declaration ->
- declaration is KtProperty && !declaration.isVar && !declaration.hasModifier(
- KtTokens.CONST_KEYWORD
- ) && declaration.annotationEntries.filter {
- annotationEntry -> annotationEntry.shortName!!.asString() == "JvmField"
- }.isEmpty() }
+ declaration is KtProperty && declaration.isPublic && !declaration.isVar &&
+ !declaration.hasModifier(KtTokens.CONST_KEYWORD) &&
+ declaration.annotationEntries.none { annotationEntry ->
+ annotationEntry.shortName?.asString() == "JvmField"
+ }
+ }
for (ktProperty in ktProperties) {
- if (ktProperty.annotationEntries.filter { annotationEntry -> annotationEntry.shortName!!.asString() == "JvmStatic" }.isEmpty()) {
+ if (ktProperty.annotationEntries.none { annotationEntry ->
+ annotationEntry.shortName?.asString() == "JvmStatic"
+ }) {
reporter.report(
Issues.MISSING_JVMSTATIC, ktProperty,
"Companion object constants like ${ktProperty.name} should be marked @JvmField for Java interoperability; see https://developer.android.com/kotlin/interop#companion_constants"
diff --git a/src/main/java/com/android/tools/metalava/ManagementWrapper.java b/src/main/java/com/android/tools/metalava/ManagementWrapper.java
deleted file mode 100644
index 510181f..0000000
--- a/src/main/java/com/android/tools/metalava/ManagementWrapper.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * 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;
-
-import java.lang.reflect.Method;
-import java.util.List;
-
-/**
- * A collection of wrapper functions for java.lang.management APIs. Unfortunately we need to
- * use reflections for them due to b/151224432.
- *
- * TODO(b/151224432) Resolve the device-side dependency and stop using reflections here.
- *
- * To avoid extra possible allocations around casts in kotlin, it's written in Java.
- */
-final class ManagementWrapper {
- private static Class<?>[] EMPTY_CLASSES = new Class[]{};
- private static Object[] EMPTY_ARGS = new Object[]{};
-
- private static void onReflectionException(Exception e) {
- throw new RuntimeException(e);
- }
-
- private abstract static class Lazy<T> {
- private boolean mInitialized;
- private T mValue;
-
- abstract T init();
-
- public T get() {
- if (!mInitialized) {
- mValue = init();
- mInitialized = true;
- }
- return mValue;
- }
- }
-
- private static class LazyClass extends Lazy<Class<?>> {
- private final String mName;
-
- private LazyClass(String name) {
- mName = name;
- }
-
- @Override
- Class<?> init() {
- try {
- return Class.forName(mName);
- } catch (Exception e) {
- onReflectionException(e);
- return null;
- }
- }
- }
-
- private static class LazyMethod<TV> extends Lazy<Method> {
- private final LazyClass mClazz;
- private final String mName;
- private TV mCachedInvokeResult;
- private TV mDefault;
-
- private LazyMethod(LazyClass clazz, String name) {
- this(clazz, name, null);
- }
-
- private LazyMethod(LazyClass clazz, String name, TV defaultValue) {
- mClazz = clazz;
- mName = name;
- mDefault = defaultValue;
- }
-
- @Override
- Method init() {
- try {
- final Class<?> clazz = mClazz.get();
- final Method m = clazz.getMethod(mName, EMPTY_CLASSES);
- m.setAccessible(true);
- return m;
- } catch (Exception e) {
- onReflectionException(e);
- return null;
- }
- }
-
- @SuppressWarnings("unchecked")
- public TV invoke(Object target) {
- try {
- return (TV) get().invoke(target, EMPTY_ARGS);
- } catch (Exception e) {
- onReflectionException(e);
- return mDefault;
- }
- }
-
- public TV invokeOnce(Object target) {
- if (mCachedInvokeResult == null) {
- mCachedInvokeResult = invoke(target);
- }
- return mCachedInvokeResult;
- }
- }
-
- private static LazyClass sFactoryClazz = new LazyClass("java.lang.management.ManagementFactory");
- private static LazyClass sThreadMxClazz = new LazyClass("java.lang.management.ThreadMXBean");
- private static LazyClass sRuntimeMxClazz = new LazyClass("java.lang.management.RuntimeMXBean");
- private static LazyClass sMemoryMxClazz = new LazyClass("java.lang.management.MemoryMXBean");
- private static LazyClass sMemoryUsageClazz = new LazyClass("java.lang.management.MemoryUsage");
-
- private static LazyMethod<Object> sRuntimeMxGetter = new LazyMethod<>(sFactoryClazz, "getRuntimeMXBean");
- private static LazyMethod<Object> sThreadMxGetter = new LazyMethod<>(sFactoryClazz, "getThreadMXBean");
- private static LazyMethod<Object> sMemoryMxGetter = new LazyMethod<>(sFactoryClazz, "getMemoryMXBean");
-
- // RuntimeMxBean functions
- private static LazyMethod<List<String>> sGetInputArguments = new LazyMethod<>(sRuntimeMxClazz, "getInputArguments");
-
- /** This corresponds to RuntimeMxBean.getInputArguments() */
- public static List<String> getVmArguments() {
- return sGetInputArguments.invoke(sRuntimeMxGetter.invokeOnce(null));
- }
-
- // ThreadMxBean functions
- private static LazyMethod<Long> sUserTime = new LazyMethod<>(sThreadMxClazz, "getCurrentThreadUserTime", 0L);
- private static LazyMethod<Long> sCpuTime = new LazyMethod<>(sThreadMxClazz, "getCurrentThreadCpuTime", 0L);
-
- /** This corresponds to ThreadMxBean.getCurrentThreadUserTime() */
- public static long getUserTimeNano() {
- return sUserTime.invoke(sThreadMxGetter.invokeOnce(null));
- }
-
- /** This corresponds to ThreadMxBean.getCurrentThreadCpuTime() */
- public static long getCpuTimeNano() {
- return sCpuTime.invoke(sThreadMxGetter.invokeOnce(null));
- }
-
- // MemoryMxBean functions
-
- /** Corresponds to java.lang.management.MemoryUsage */
- public static final class MemoryUsage {
- public final long init;
- public final long used;
- public final long committed;
- public final long max;
-
- public MemoryUsage(long init, long used, long committed, long max) {
- this.init = init;
- this.used = used;
- this.committed = committed;
- this.max = max;
- }
- }
-
- // This returns a java.lang.management.MemoryUsage instance.
- private static LazyMethod<Object> sGetHeapMemoryUsage = new LazyMethod<>(sMemoryMxClazz, "getHeapMemoryUsage");
- private static LazyMethod<Long> sGetInitMemory = new LazyMethod<>(sMemoryUsageClazz, "getInit", 0L);
- private static LazyMethod<Long> sGetUsedMemory = new LazyMethod<>(sMemoryUsageClazz, "getUsed", 0L);
- private static LazyMethod<Long> sGetCommittedMemory = new LazyMethod<>(sMemoryUsageClazz, "getCommitted", 0L);
- private static LazyMethod<Long> sGetMaxMemory = new LazyMethod<>(sMemoryUsageClazz, "getMax", 0L);
-
- /** Corresponds to java.lang.management.MemoryUsage.getHeapMemoryUsage() */
- public static MemoryUsage getHeapUsage() {
- final Object heapUsage = sGetHeapMemoryUsage.invoke(sMemoryMxGetter.invokeOnce(null));
-
- return new MemoryUsage(
- sGetInitMemory.invoke(heapUsage),
- sGetUsedMemory.invoke(heapUsage),
- sGetCommittedMemory.invoke(heapUsage),
- sGetMaxMemory.invoke(heapUsage)
- );
- }
-}
diff --git a/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt b/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt
index 6783b26..6ac6491 100644
--- a/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt
+++ b/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt
@@ -118,8 +118,8 @@
if (type == null) {
throw DriverException("Missing type on $method item $label")
}
- if (method.isEnumValueOfString()) {
- // Don't validate an enum's valueOf(String) method, which doesn't exist in source.
+ if (method.synthetic) {
+ // Don't validate items which don't exist in source such as an enum's valueOf(String)
return
}
val annotations = item.modifiers.annotations()
@@ -222,8 +222,3 @@
}
}
}
-
-private fun MethodItem.isEnumValueOfString() =
- containingClass().isEnum() && name() == "valueOf" && parameters().map {
- it.type().toTypeString()
- } == listOf("java.lang.String")
diff --git a/src/main/java/com/android/tools/metalava/NullnessMigration.kt b/src/main/java/com/android/tools/metalava/NullnessMigration.kt
index b567a21..77c9a83 100644
--- a/src/main/java/com/android/tools/metalava/NullnessMigration.kt
+++ b/src/main/java/com/android/tools/metalava/NullnessMigration.kt
@@ -75,6 +75,7 @@
}
private fun hasNullnessInformation(type: TypeItem): Boolean {
+ @Suppress("ConstantConditionIf")
return if (SUPPORT_TYPE_USE_ANNOTATIONS) {
val typeString = type.toTypeString(outerAnnotations = false, innerAnnotations = true)
typeString.contains(".Nullable") || typeString.contains(".NonNull")
diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt
index 1636985..dde8558 100644
--- a/src/main/java/com/android/tools/metalava/Options.kt
+++ b/src/main/java/com/android/tools/metalava/Options.kt
@@ -17,7 +17,9 @@
package com.android.tools.metalava
import com.android.SdkConstants
+import com.android.SdkConstants.FN_FRAMEWORK_LIBRARY
import com.android.sdklib.SdkVersionInfo
+import com.android.tools.lint.detector.api.isJdkFolder
import com.android.tools.metalava.CompatibilityCheck.CheckRequest
import com.android.tools.metalava.doclava1.Issues
import com.android.tools.metalava.model.defaultConfiguration
@@ -26,12 +28,16 @@
import com.google.common.base.Splitter
import com.google.common.io.Files
import com.intellij.pom.java.LanguageLevel
+import org.jetbrains.jps.model.java.impl.JavaSdkUtil
+import org.jetbrains.kotlin.config.ApiVersion
+import org.jetbrains.kotlin.config.LanguageVersion
+import org.jetbrains.kotlin.config.LanguageVersionSettings
+import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import java.io.File
import java.io.IOException
import java.io.OutputStreamWriter
import java.io.PrintWriter
import java.io.StringWriter
-import java.lang.NumberFormatException
import java.util.Locale
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.memberProperties
@@ -60,9 +66,6 @@
const val ARG_CONVERT_TO_V2 = "--convert-to-v2"
const val ARG_CONVERT_NEW_TO_V1 = "--convert-new-to-v1"
const val ARG_CONVERT_NEW_TO_V2 = "--convert-new-to-v2"
-const val ARG_PRIVATE_API = "--private-api"
-const val ARG_DEX_API = "--dex-api"
-const val ARG_PRIVATE_DEX_API = "--private-dex-api"
const val ARG_SDK_VALUES = "--sdk-values"
const val ARG_REMOVED_API = "--removed-api"
const val ARG_REMOVED_DEX_API = "--removed-dex-api"
@@ -73,12 +76,11 @@
const val ARG_NULLABILITY_WARNINGS_TXT = "--nullability-warnings-txt"
const val ARG_NULLABILITY_ERRORS_NON_FATAL = "--nullability-errors-non-fatal"
const val ARG_INPUT_API_JAR = "--input-api-jar"
-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"
const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations"
const val ARG_EXCLUDE_ANNOTATIONS = "--exclude-annotations"
const val ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS = "--exclude-documentation-from-stubs"
@@ -105,6 +107,7 @@
const val ARG_SHOW_SINGLE_ANNOTATION = "--show-single-annotation"
const val ARG_HIDE_ANNOTATION = "--hide-annotation"
const val ARG_HIDE_META_ANNOTATION = "--hide-meta-annotation"
+const val ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION = "--show-for-stub-purposes-annotation"
const val ARG_SHOW_UNANNOTATED = "--show-unannotated"
const val ARG_COLOR = "--color"
const val ARG_NO_COLOR = "--no-color"
@@ -115,9 +118,6 @@
const val ARG_WARNING = "--warning"
const val ARG_LINT = "--lint"
const val ARG_HIDE = "--hide"
-const val ARG_UNHIDE_CLASSPATH_CLASSES = "--unhide-classpath-classes"
-const val ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES = "--allow-referencing-unknown-classes"
-const val ARG_NO_UNKNOWN_CLASSES = "--no-unknown-classes"
const val ARG_APPLY_API_LEVELS = "--apply-api-levels"
const val ARG_GENERATE_API_LEVELS = "--generate-api-levels"
const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern"
@@ -134,6 +134,10 @@
const val ARG_HIDDEN = "--hidden"
const val ARG_NO_DOCS = "--no-docs"
const val ARG_JAVA_SOURCE = "--java-source"
+const val ARG_KOTLIN_SOURCE = "--kotlin-source"
+const val ARG_SDK_HOME = "--sdk-home"
+const val ARG_JDK_HOME = "--jdk-home"
+const val ARG_COMPILE_SDK_VERSION = "--compile-sdk-version"
const val ARG_REGISTER_ARTIFACT = "--register-artifact"
const val ARG_INCLUDE_ANNOTATIONS = "--include-annotations"
const val ARG_COPY_ANNOTATIONS = "--copy-annotations"
@@ -145,7 +149,6 @@
const val ARG_UPDATE_API = "--only-update-api"
const val ARG_CHECK_API = "--only-check-api"
const val ARG_PASS_BASELINE_UPDATES = "--pass-baseline-updates"
-const val ARG_DEX_API_MAPPING = "--dex-api-mapping"
const val ARG_GENERATE_DOCUMENTATION = "--generate-documentation"
const val ARG_REPLACE_DOCUMENTATION = "--replace-documentation"
const val ARG_BASELINE = "--baseline"
@@ -171,6 +174,7 @@
const val ARG_STRICT_INPUT_FILES_STACK = "--strict-input-files:stack"
const val ARG_STRICT_INPUT_FILES_WARN = "--strict-input-files:warn"
const val ARG_STRICT_INPUT_FILES_EXEMPT = "--strict-input-files-exempt"
+const val ARG_REPEAT_ERRORS_MAX = "--repeat-errors-max"
class Options(
private val args: Array<String>,
@@ -194,6 +198,8 @@
private val mutableHideAnnotations = MutableAnnotationFilter()
/** Internal list backing [hideMetaAnnotations] */
private val mutableHideMetaAnnotations: MutableList<String> = mutableListOf()
+ /** Internal list backing [showForStubPurposesAnnotations] */
+ private val mutableShowForStubPurposesAnnotation = MutableAnnotationFilter()
/** Internal list backing [stubImportPackages] */
private val mutableStubImportPackages: MutableSet<String> = mutableSetOf()
/** Internal list backing [mergeQualifierAnnotations] */
@@ -329,7 +335,11 @@
/** All source files to parse */
var sources: List<File> = mutableSources
- /** Whether to include APIs with annotations (intended for documentation purposes) */
+ /**
+ * Whether to include APIs with annotations (intended for documentation purposes).
+ * This includes [ARG_SHOW_ANNOTATION], [ARG_SHOW_SINGLE_ANNOTATION] and
+ * [ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION].
+ */
var showAnnotations: AnnotationFilter = mutableShowAnnotations
/**
@@ -353,9 +363,6 @@
/** If non null, an API file to use to hide for controlling what parts of the API are new */
var checkApiBaselineApiFile: File? = null
- /** Whether to validate the API for Kotlin interop */
- var checkKotlinInterop = false
-
/** Packages to include (if null, include all) */
var stubPackages: PackageFilter? = null
@@ -376,6 +383,13 @@
/** Meta-annotations to hide */
var hideMetaAnnotations = mutableHideMetaAnnotations
+ /**
+ * Annotations that defines APIs that are implicitly included in the API surface. These APIs
+ * will be included in included in certain kinds of output such as stubs, but others (e.g.
+ * API lint and the API signature file) ignore them.
+ */
+ var showForStubPurposesAnnotations: AnnotationFilter = mutableShowForStubPurposesAnnotation
+
/** Whether the generated API can contain classes that are not present in the source but are present on the
* classpath. Defaults to true for backwards compatibility but is set to false if any API signatures are imported
* as they must provide a complete set of all classes required but not provided by the generated API.
@@ -404,8 +418,8 @@
* other tools like javac/javadoc using the special @-syntax. */
var docStubsSourceList: File? = null
- /** Proguard Keep list file to write */
- var proguard: File? = null
+ /** Whether code compiled from Kotlin should be emitted as .kt stubs instead of .java stubs */
+ var kotlinStubs = false
/** If set, a file to write an API file to. Corresponds to the --api/-api flag. */
var apiFile: File? = null
@@ -413,18 +427,6 @@
/** Like [apiFile], but with JDiff xml format. */
var apiXmlFile: File? = null
- /** If set, a file to write the private API file to. Corresponds to the --private-api/-privateApi flag. */
- var privateApiFile: File? = null
-
- /** If set, a file to write the DEX signatures to. Corresponds to [ARG_DEX_API]. */
- var dexApiFile: File? = null
-
- /** If set, a file to write all DEX signatures and file locations to. Corresponds to [ARG_DEX_API_MAPPING]. */
- var dexApiMappingFile: File? = null
-
- /** If set, a file to write the private DEX signatures to. Corresponds to --private-dex-api. */
- var privateDexApiFile: File? = null
-
/** Path to directory to write SDK values to */
var sdkValueDir: File? = null
@@ -527,24 +529,6 @@
/** Whether to emit coverage statistics for annotations in the API surface */
var dumpAnnotationStatistics = false
- /** Only used for tests: Normally we want to treat classes not found as source (e.g. supplied via
- * classpath) as hidden, but for the unit tests (where we're not passing in
- * a complete API surface) this makes the tests more cumbersome.
- * This option lets the testing infrastructure treat these classes differently.
- * To see the what this means in practice, try turning it back on for the tests
- * and see what it does to the results :)
- */
- var hideClasspathClasses = true
-
- /** Only used for tests: Whether during code filtering we allow referencing super classes
- * etc that are unknown (because they're not included in the codebase) */
- var allowReferencingUnknownClasses = true
-
- /** Reverse of [allowReferencingUnknownClasses]: Require all classes to be known. This
- * is used when compiling the main SDK itself (which includes definitions for everything,
- * including java.lang.Object.) */
- var noUnknownClasses = false
-
/**
* mapping from API level to android.jar files, if computing API levels
*/
@@ -576,7 +560,7 @@
/**
* A baseline to check against, specifically used for "check-compatibility:*:released"
- * (i.e. [ARG_CHECK_COMPATIBILITY_API_RELEASEED] and [ARG_CHECK_COMPATIBILITY_REMOVED_RELEASEED])
+ * (i.e. [ARG_CHECK_COMPATIBILITY_API_RELEASED] and [ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED])
*/
var baselineCompatibilityReleased: Baseline? = null
@@ -587,7 +571,7 @@
/**
* If set, metalava will show this error message when "check-compatibility:*:released" fails.
- * (i.e. [ARG_CHECK_COMPATIBILITY_API_RELEASEED] and [ARG_CHECK_COMPATIBILITY_REMOVED_RELEASEED])
+ * (i.e. [ARG_CHECK_COMPATIBILITY_API_RELEASED] and [ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED])
*/
var errorMessageCompatibilityReleased: String? = null
@@ -602,7 +586,7 @@
/**
* [Reporter] for "check-compatibility:*:released".
- * (i.e. [ARG_CHECK_COMPATIBILITY_API_RELEASEED] and [ARG_CHECK_COMPATIBILITY_REMOVED_RELEASEED])
+ * (i.e. [ARG_CHECK_COMPATIBILITY_API_RELEASED] and [ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED])
*/
var reporterCompatibilityReleased: Reporter
@@ -647,6 +631,31 @@
*/
var javaLanguageLevel: LanguageLevel = LanguageLevel.JDK_1_8
+ /**
+ * The language level to use for Java files, set with [ARG_KOTLIN_SOURCE]
+ */
+ var kotlinLanguageLevel: LanguageVersionSettings = LanguageVersionSettingsImpl.DEFAULT
+
+ /**
+ * The JDK to use as a platform, if set with [ARG_JDK_HOME]. This is only set
+ * when metalava is used for non-Android projects.
+ */
+ var jdkHome: File? = null
+
+ /**
+ * The JDK to use as a platform, if set with [ARG_SDK_HOME]. If this is set
+ * along with [ARG_COMPILE_SDK_VERSION], metalava will automatically add
+ * the platform's android.jar file to the classpath if it does not already
+ * find the android.jar file in the classpath.
+ */
+ var sdkHome: File? = null
+
+ /**
+ * The compileSdkVersion, set by [ARG_COMPILE_SDK_VERSION]. For example,
+ * for R it would be "29". For R preview, if would be "R".
+ */
+ var compileSdkVersion: String? = null
+
/** Map from XML API descriptor file to corresponding artifact id name */
val artifactRegistrations = ArtifactTagger()
@@ -667,9 +676,15 @@
enum class StrictInputFileMode {
PERMISSIVE,
- STRICT,
+ STRICT {
+ override val shouldFail = true
+ },
STRICT_WARN,
- STRICT_WITH_STACK;
+ STRICT_WITH_STACK {
+ override val shouldFail = true
+ };
+
+ open val shouldFail = false
companion object {
fun fromArgument(arg: String): StrictInputFileMode {
@@ -705,6 +720,9 @@
/** Temporary folder to use instead of the JDK default, if any */
var tempFolder: File? = null
+ /** When non-0, metalava repeats all the errors at the end of the run, at most this many. */
+ var repeatErrorsMax = 0
+
init {
// Pre-check whether --color/--no-color is present and use that to decide how
// to emit the banner even before we emit errors
@@ -734,9 +752,9 @@
var skipGenerateAnnotations = false
reporter = Reporter(null, null)
- var baselineBuilder = Baseline.Builder().apply { description = "base" }
- var baselineApiLintBuilder = Baseline.Builder().apply { description = "api-lint" }
- var baselineCompatibilityReleasedBuilder = Baseline.Builder().apply { description = "compatibility:released" }
+ val baselineBuilder = Baseline.Builder().apply { description = "base" }
+ val baselineApiLintBuilder = Baseline.Builder().apply { description = "api-lint" }
+ val baselineCompatibilityReleasedBuilder = Baseline.Builder().apply { description = "compatibility:released" }
fun getBaselineBuilderForArg(flag: String): Baseline.Builder = when (flag) {
ARG_BASELINE, ARG_UPDATE_BASELINE, ARG_MERGE_BASELINE -> baselineBuilder
@@ -748,9 +766,8 @@
var index = 0
while (index < args.size) {
- val arg = args[index]
- when (arg) {
+ when (val arg = args[index]) {
ARG_HELP, "-h", "-?" -> {
helpAndQuit(color)
}
@@ -787,7 +804,7 @@
"$arg should point to a source root directory, not a source file ($path)"
)
}
- mutableSourcePath.addAll(stringToExistingDirsOrJars(path))
+ mutableSourcePath.addAll(stringToExistingDirsOrJars(path, false))
}
}
@@ -841,20 +858,10 @@
"-sdkvalues", ARG_SDK_VALUES -> sdkValueDir = stringToNewDir(getValue(args, ++index))
ARG_API, "-api" -> apiFile = stringToNewFile(getValue(args, ++index))
ARG_XML_API -> apiXmlFile = stringToNewFile(getValue(args, ++index))
- ARG_DEX_API, "-dexApi" -> dexApiFile = stringToNewFile(getValue(args, ++index))
- ARG_DEX_API_MAPPING, "-apiMapping" -> dexApiMappingFile = stringToNewFile(getValue(args, ++index))
-
- ARG_PRIVATE_API, "-privateApi" -> privateApiFile = stringToNewFile(getValue(args, ++index))
- ARG_PRIVATE_DEX_API, "-privateDexApi" -> privateDexApiFile = stringToNewFile(getValue(args, ++index))
ARG_REMOVED_API, "-removedApi" -> removedApiFile = stringToNewFile(getValue(args, ++index))
ARG_REMOVED_DEX_API, "-removedDexApi" -> removedDexApiFile = stringToNewFile(getValue(args, ++index))
- ARG_EXACT_API, "-exactApi" -> {
- getValue(args, ++index) // prevent next arg from tripping up parser
- unimplemented(arg) // Not yet implemented (because it seems to no longer be hooked up in doclava1)
- }
-
ARG_MANIFEST, "-manifest" -> manifest = stringToExistingFile(getValue(args, ++index))
ARG_SHOW_ANNOTATION, "-showAnnotation" -> mutableShowAnnotations.add(getValue(args, ++index))
@@ -866,6 +873,13 @@
mutableShowAnnotations.add(annotation)
}
+ ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION, "--show-for-stub-purposes-annotations", "-show-for-stub-purposes-annotation" -> {
+ val annotation = getValue(args, ++index)
+ mutableShowForStubPurposesAnnotation.add(annotation)
+ // These should also be counted as show annotations
+ mutableShowAnnotations.add(annotation)
+ }
+
ARG_SHOW_UNANNOTATED, "-showUnannotated" -> showUnannotated = true
"--showAnnotationOverridesVisibility" -> {
@@ -880,6 +894,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))
@@ -903,8 +918,6 @@
// the output when diffing against golden files
"--omit-locations" -> omitLocations = true
- ARG_PROGUARD, "-proguard" -> proguard = stringToNewFile(getValue(args, ++index))
-
ARG_HIDE_PACKAGE, "-hidePackage" -> mutableHidePackages.add(getValue(args, ++index))
ARG_STUB_PACKAGES, "-stubpackages" -> {
@@ -937,7 +950,8 @@
"inline" -> TypedefMode.INLINE
"none" -> TypedefMode.NONE
else -> throw DriverException(
- stderr = "$ARG_TYPEDEFS_IN_SIGNATURES must be one of ref, inline, none; was $type")
+ stderr = "$ARG_TYPEDEFS_IN_SIGNATURES must be one of ref, inline, none; was $type"
+ )
}
}
@@ -1160,8 +1174,6 @@
checkApiIgnorePrefix.add(getValue(args, ++index))
}
- ARG_CHECK_KOTLIN_INTEROP -> checkKotlinInterop = true
-
ARG_COLOR -> color = true
ARG_NO_COLOR -> color = false
ARG_NO_BANNER -> {
@@ -1173,10 +1185,6 @@
ARG_SKIP_JAVA_IN_COVERAGE_REPORT -> omitRuntimePackageStats = true
- ARG_UNHIDE_CLASSPATH_CLASSES -> hideClasspathClasses = false
- ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES -> allowReferencingUnknownClasses = true
- ARG_NO_UNKNOWN_CLASSES -> noUnknownClasses = true
-
// Extracting API levels
ARG_ANDROID_JAR_PATTERN -> {
val list = androidJarPatterns ?: run {
@@ -1333,6 +1341,28 @@
}
}
+ ARG_KOTLIN_SOURCE -> {
+ val value = getValue(args, ++index)
+ val languageLevel =
+ LanguageVersion.fromVersionString(value)
+ ?: throw DriverException("$value is not a valid or supported Kotlin language level")
+ val apiVersion = ApiVersion.createByLanguageVersion(languageLevel)
+ val settings = LanguageVersionSettingsImpl(languageLevel, apiVersion)
+ kotlinLanguageLevel = settings
+ }
+
+ ARG_JDK_HOME -> {
+ jdkHome = stringToExistingDir(getValue(args, ++index))
+ }
+
+ ARG_SDK_HOME -> {
+ sdkHome = stringToExistingDir(getValue(args, ++index))
+ }
+
+ ARG_COMPILE_SDK_VERSION -> {
+ compileSdkVersion = getValue(args, ++index)
+ }
+
ARG_NO_IMPLICIT_ROOT -> {
allowImplicitRoot = false
}
@@ -1350,11 +1380,16 @@
ARG_STRICT_INPUT_FILES_EXEMPT -> {
val listString = getValue(args, ++index)
listString.split(File.pathSeparatorChar).forEach { path ->
- // Throw away the result; just let the function add the files to the whitelist.
+ // Throw away the result; just let the function add the files to the
+ // allowed list.
stringToExistingFilesOrDirs(path)
}
}
+ ARG_REPEAT_ERRORS_MAX -> {
+ repeatErrorsMax = Integer.parseInt(getValue(args, ++index))
+ }
+
"--temp-folder" -> {
tempFolder = stringToNewOrExistingDir(getValue(args, ++index))
}
@@ -1579,6 +1614,9 @@
}
if (generateApiLevelXml != null) {
+ // <String> is redundant here but while IDE (with newer type inference engine
+ // understands that) the current 1.3.x compiler does not
+ @Suppress("RemoveExplicitTypeArguments")
val patterns = androidJarPatterns ?: run {
mutableListOf<String>()
}
@@ -1600,10 +1638,6 @@
showUnannotated = true
}
- if (noUnknownClasses) {
- allowReferencingUnknownClasses = false
- }
-
if (skipGenerateAnnotations) {
generateAnnotations = false
}
@@ -1627,10 +1661,8 @@
docStubsSourceList = null
sdkValueDir = null
externalAnnotations = null
- proguard = null
noDocs = true
invokeDocumentationToolArguments = emptyArray()
- checkKotlinInterop = false
mutableCompatibilityChecks.clear()
mutableAnnotationCoverageOf.clear()
artifactRegistrations.clear()
@@ -1654,10 +1686,8 @@
docStubsSourceList = null
sdkValueDir = null
externalAnnotations = null
- proguard = null
noDocs = true
invokeDocumentationToolArguments = emptyArray()
- checkKotlinInterop = false
mutableAnnotationCoverageOf.clear()
artifactRegistrations.clear()
mutableConvertToXmlFiles.clear()
@@ -1668,10 +1698,6 @@
validateNullabilityFromList = null
apiFile = null
apiXmlFile = null
- privateApiFile = null
- dexApiFile = null
- dexApiMappingFile = null
- privateDexApiFile = null
removedApiFile = null
removedDexApiFile = null
}
@@ -1726,9 +1752,35 @@
reporterCompatibilityCurrent
)
+ updateClassPath()
checkFlagConsistency()
}
+ /** Update the classpath to insert android.jar or JDK classpath elements if necessary */
+ private fun updateClassPath() {
+ val sdkHome = sdkHome
+ val jdkHome = jdkHome
+
+ if (sdkHome != null &&
+ compileSdkVersion != null &&
+ classpath.none { it.name == FN_FRAMEWORK_LIBRARY }) {
+ val jar = File(sdkHome, "platforms/android-$compileSdkVersion")
+ if (jar.isFile) {
+ mutableClassPath.add(jar)
+ } else {
+ throw DriverException(stderr = "Could not find android.jar for API level " +
+ "$compileSdkVersion in SDK $sdkHome: $jar does not exist")
+ }
+ if (jdkHome != null) {
+ throw DriverException(stderr = "Do not specify both $ARG_SDK_HOME and $ARG_JDK_HOME")
+ }
+ } else if (jdkHome != null) {
+ val isJre = !isJdkFolder(jdkHome)
+ val roots = JavaSdkUtil.getJdkClassesRoots(jdkHome, isJre)
+ mutableClassPath.addAll(roots)
+ }
+ }
+
private fun findCompatibilityFlag(arg: String): KMutableProperty1<Compatibility, Boolean>? {
val index = arg.indexOf('=')
val name = arg
@@ -1796,7 +1848,8 @@
}
val apiLevelFiles = mutableListOf<File>()
- apiLevelFiles.add(File("there is no api 0")) // api level 0: dummy, should not be processed
+ // api level 0: placeholder, should not be processed
+ apiLevelFiles.add(File("there is no api 0"))
val minApi = 1
// Get all the android.jar. They are in platforms-#
@@ -1845,7 +1898,7 @@
private fun getAndroidJarFile(apiLevel: Int, patterns: List<String>): File? {
// Note this method doesn't register the result to [FileReadSandbox]
return patterns
- .map { fileForPathInner(it.replace("%", Integer.toString(apiLevel))) }
+ .map { fileForPathInner(it.replace("%", apiLevel.toString())) }
.firstOrNull { it.isFile }
}
@@ -1944,7 +1997,7 @@
return FileReadSandbox.allowAccess(files)
}
- private fun stringToExistingDirsOrJars(value: String): List<File> {
+ private fun stringToExistingDirsOrJars(value: String, exempt: Boolean = true): List<File> {
val files = mutableListOf<File>()
for (path in value.split(File.pathSeparatorChar)) {
val file = fileForPathInner(path)
@@ -1953,7 +2006,10 @@
}
files.add(file)
}
- return FileReadSandbox.allowAccess(files)
+ if (exempt) {
+ return FileReadSandbox.allowAccess(files)
+ }
+ return files
}
private fun stringToExistingDirsOrFiles(value: String): List<File> {
@@ -2150,13 +2206,17 @@
"to make it easier customize build system tasks, particularly for the \"make update-api\" task.",
ARG_CHECK_API, "Cancel any other \"action\" flags other than checking signature files. This is here " +
"to make it easier customize build system tasks, particularly for the \"make checkapi\" task.",
+ "$ARG_REPEAT_ERRORS_MAX <N>", "When specified, repeat at most N errors before finishing.",
"", "\nAPI sources:",
"$ARG_SOURCE_FILES <files>", "A comma separated list of source files to be parsed. Can also be " +
"@ followed by a path to a text file containing paths to the full set of files to parse.",
"$ARG_SOURCE_PATH <paths>", "One or more directories (separated by `${File.pathSeparator}`) " +
- "containing source files (within a package hierarchy)",
+ "containing source files (within a package hierarchy). If $ARG_STRICT_INPUT_FILES, " +
+ "$ARG_STRICT_INPUT_FILES_WARN, or $ARG_STRICT_INPUT_FILES_STACK are used, files accessed under " +
+ "$ARG_SOURCE_PATH that are not explicitly specified in $ARG_SOURCE_FILES are reported as " +
+ "violations.",
"$ARG_CLASS_PATH <paths>", "One or more directories or jars (separated by " +
"`${File.pathSeparator}`) containing classes that should be on the classpath when parsing the " +
@@ -2203,12 +2263,19 @@
"with the given annotation",
"$ARG_SHOW_SINGLE_ANNOTATION <annotation>", "Like $ARG_SHOW_ANNOTATION, but does not apply " +
"to members; these must also be explicitly annotated",
+ "$ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION <annotation class>", "Like $ARG_SHOW_ANNOTATION, but elements annotated " +
+ "with it are assumed to be \"implicitly\" included in the API surface, and they'll be included " +
+ "in certain kinds of output such as stubs, but not in others, such as the signature file and API lint",
"$ARG_HIDE_ANNOTATION <annotation class>", "Treat any elements annotated with the given annotation " +
"as hidden",
"$ARG_HIDE_META_ANNOTATION <meta-annotation class>", "Treat as hidden any elements annotated with an " +
"annotation which is itself annotated with the given meta-annotation",
ARG_SHOW_UNANNOTATED, "Include un-annotated public APIs in the signature file as well",
"$ARG_JAVA_SOURCE <level>", "Sets the source level for Java source files; default is 1.8.",
+ "$ARG_KOTLIN_SOURCE <level>", "Sets the source level for Kotlin source files; default is ${LanguageVersionSettingsImpl.DEFAULT.languageVersion}.",
+ "$ARG_SDK_HOME <dir>", "If set, locate the `android.jar` file from the given Android SDK",
+ "$ARG_COMPILE_SDK_VERSION <api>", "Use the given API level",
+ "$ARG_JDK_HOME <dir>", "If set, add the Java APIs from the given JDK to the classpath",
"$ARG_STUB_PACKAGES <package-list>", "List of packages (separated by ${File.pathSeparator}) which will " +
"be used to filter out irrelevant code. If specified, only code in these packages will be " +
"included in signature files, stubs, etc. (This is not limited to just the stubs; the name " +
@@ -2236,10 +2303,6 @@
"", "\nExtracting Signature Files:",
// TODO: Document --show-annotation!
"$ARG_API <file>", "Generate a signature descriptor file",
- "$ARG_PRIVATE_API <file>", "Generate a signature descriptor file listing the exact private APIs",
- "$ARG_DEX_API <file>", "Generate a DEX signature descriptor file listing the APIs",
- "$ARG_PRIVATE_DEX_API <file>", "Generate a DEX signature descriptor file listing the exact private APIs",
- "$ARG_DEX_API_MAPPING <file>", "Generate a DEX signature descriptor along with file and line numbers",
"$ARG_REMOVED_API <file>", "Generate a signature descriptor file for APIs that have been removed",
"$ARG_FORMAT=<v1,v2,v3,...>", "Sets the output signature file format to be the given version.",
"$ARG_OUTPUT_KOTLIN_NULLS[=yes|no]", "Controls whether nullness annotations should be formatted as " +
@@ -2256,7 +2319,6 @@
"$ARG_INCLUDE_SIG_VERSION[=yes|no]", "Whether the signature files should include a comment listing " +
"the format version of the signature file.",
- "$ARG_PROGUARD <file>", "Write a ProGuard keep file for the API",
"$ARG_SDK_VALUES <dir>", "Write SDK values files to the given directory",
"", "\nGenerating Stubs:",
@@ -2267,10 +2329,12 @@
"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" +
- " annotation classes that must be passed through unchanged.",
+ "$ARG_PASS_THROUGH_ANNOTATION <annotation classes>", "A comma separated list of fully qualified names of " +
+ "annotation classes that must be passed through unchanged.",
ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS, "Exclude element documentation (javadoc and kdoc) " +
"from the generated stubs. (Copyright notices are not affected by this, they are always included. " +
"Documentation stubs (--doc-stubs) are not affected.)",
@@ -2298,8 +2362,6 @@
"provided, only the APIs that are new since the API will be checked.",
"$ARG_API_LINT_IGNORE_PREFIX [prefix]", "A list of package prefixes to ignore API issues in " +
"when running with $ARG_API_LINT.",
- ARG_CHECK_KOTLIN_INTEROP, "Check API intended to be used from both Kotlin and Java for interoperability " +
- "issues",
"$ARG_MIGRATE_NULLNESS <api file>", "Compare nullness information with the previous stable API " +
"and mark newly annotated APIs as under migration.",
ARG_WARNINGS_AS_ERRORS, "Promote all warnings to errors",
@@ -2333,9 +2395,9 @@
"to include.",
"$ARG_ERROR_MESSAGE_API_LINT <message>", "If set, $PROGRAM_NAME shows it when errors are detected in $ARG_API_LINT.",
"$ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_RELEASED <message>", "If set, $PROGRAM_NAME shows it " +
- " when errors are detected in $ARG_CHECK_COMPATIBILITY_API_RELEASED and $ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED.",
+ "when errors are detected in $ARG_CHECK_COMPATIBILITY_API_RELEASED and $ARG_CHECK_COMPATIBILITY_REMOVED_RELEASED.",
"$ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_CURRENT <message>", "If set, $PROGRAM_NAME shows it " +
- " when errors are detected in $ARG_CHECK_COMPATIBILITY_API_CURRENT and $ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT.",
+ "when errors are detected in $ARG_CHECK_COMPATIBILITY_API_CURRENT and $ARG_CHECK_COMPATIBILITY_REMOVED_CURRENT.",
"", "\nJDiff:",
"$ARG_XML_API <file>", "Like $ARG_API, but emits the API in the JDiff XML format instead",
@@ -2439,26 +2501,32 @@
out.println(description)
}
} else {
- if (colorize) {
- val colorArg = bold(arg)
- val invisibleChars = colorArg.length - arg.length
- // +invisibleChars: the extra chars in the above are counted but don't contribute to width
- // so allow more space
- val colorFormatString = "%1$-" + (INDENT_WIDTH + invisibleChars) + "s%2\$s"
+ val output =
+ if (colorize) {
+ val colorArg = bold(arg)
+ val invisibleChars = colorArg.length - arg.length
+ // +invisibleChars: the extra chars in the above are counted but don't contribute to width
+ // so allow more space
+ val colorFormatString = "%1$-" + (INDENT_WIDTH + invisibleChars) + "s%2\$s"
- out.print(
wrap(
String.format(colorFormatString, colorArg, description),
MAX_LINE_WIDTH + invisibleChars, MAX_LINE_WIDTH, indent
)
- )
- } else {
- out.print(
+ } else {
wrap(
String.format(formatString, arg, description),
MAX_LINE_WIDTH, indent
)
- )
+ }
+
+ // Remove trailing whitespace
+ val lines = output.lines()
+ lines.forEachIndexed { index, line ->
+ out.print(line.trimEnd())
+ if (index < lines.size - 1) {
+ out.println()
+ }
}
}
i += 2
diff --git a/src/main/java/com/android/tools/metalava/PackageFilter.kt b/src/main/java/com/android/tools/metalava/PackageFilter.kt
index 175ecfb..c72f093 100644
--- a/src/main/java/com/android/tools/metalava/PackageFilter.kt
+++ b/src/main/java/com/android/tools/metalava/PackageFilter.kt
@@ -28,7 +28,7 @@
* match any subpackage of foo but not foo itself, so foo.* is taken
* to mean "foo" and "foo.*".
*/
-class PackageFilter() {
+class PackageFilter {
val components: MutableList<PackageFilterComponent> = mutableListOf()
fun matches(qualifiedName: String): Boolean {
@@ -79,7 +79,7 @@
}
}
-class StringPrefixPredicate(val acceptedPrefix: String) : Predicate<String> {
+class StringPrefixPredicate(private val acceptedPrefix: String) : Predicate<String> {
override fun test(candidatePackage: String): Boolean {
return candidatePackage.startsWith(acceptedPrefix)
}
diff --git a/src/main/java/com/android/tools/metalava/Progress.kt b/src/main/java/com/android/tools/metalava/Progress.kt
index ebb2fd9..9c461ea 100644
--- a/src/main/java/com/android/tools/metalava/Progress.kt
+++ b/src/main/java/com/android/tools/metalava/Progress.kt
@@ -56,8 +56,8 @@
private fun getCpuStats(): String {
val nowMillis = System.currentTimeMillis()
- val userMillis = ManagementWrapper.getUserTimeNano() / 1000_000
- val cpuMillis = ManagementWrapper.getCpuTimeNano() / 1000_000
+ val userMillis = threadMXBean.getCurrentThreadUserTime() / 1000_000
+ val cpuMillis = threadMXBean.getCurrentThreadCpuTime() / 1000_000
if (lastMillis == -1L) {
lastMillis = nowMillis
@@ -86,7 +86,7 @@
}
private fun getMemoryStats(): String {
- val mu = ManagementWrapper.getHeapUsage()
+ val mu = memoryMXBean.getHeapMemoryUsage()
return String.format(
"%dmi %dmu %dmc %dmx",
diff --git a/src/main/java/com/android/tools/metalava/ProguardWriter.kt b/src/main/java/com/android/tools/metalava/ProguardWriter.kt
deleted file mode 100644
index 54e45e5..0000000
--- a/src/main/java/com/android/tools/metalava/ProguardWriter.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2017 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 com.android.tools.metalava.model.ClassItem
-import com.android.tools.metalava.model.ConstructorItem
-import com.android.tools.metalava.model.FieldItem
-import com.android.tools.metalava.model.Item
-import com.android.tools.metalava.model.MethodItem
-import com.android.tools.metalava.model.ParameterItem
-import com.android.tools.metalava.model.TypeItem
-import com.android.tools.metalava.model.VisibilityLevel
-import com.android.tools.metalava.model.visitors.ApiVisitor
-import java.io.PrintWriter
-import java.util.function.Predicate
-
-class ProguardWriter(
- private val writer: PrintWriter,
- filterEmit: Predicate<Item>,
- filterReference: Predicate<Item>
-) : ApiVisitor(
- visitConstructorsAsMethods = false,
- nestInnerClasses = false,
- inlineInheritedFields = true,
- filterEmit = filterEmit,
- filterReference = filterReference
-) {
-
- override fun visitClass(cls: ClassItem) {
- writer.print("-keep class ")
- writer.print(cls.qualifiedNameWithDollarInnerClasses())
- writer.print(" {\n")
- }
-
- override fun afterVisitClass(cls: ClassItem) {
- writer.print("}\n")
- }
-
- override fun visitConstructor(constructor: ConstructorItem) {
- writer.print(" ")
- writer.print("<init>")
-
- writeParametersKeepList(constructor.parameters())
- writer.print(";\n")
- }
-
- override fun visitMethod(method: MethodItem) {
- writer.print(" ")
- val modifiers = method.modifiers
- val visibilityLevel = modifiers.getVisibilityLevel()
- if (visibilityLevel != VisibilityLevel.PACKAGE_PRIVATE) {
- writer.write(visibilityLevel.sourceCodeModifier + " ")
- }
-
- if (modifiers.isStatic()) {
- writer.print("static ")
- }
- if (modifiers.isAbstract()) {
- writer.print("abstract ")
- }
- if (modifiers.isSynchronized()) {
- writer.print("synchronized ")
- }
-
- writer.print(getCleanTypeName(method.returnType()))
- writer.print(" ")
- writer.print(method.name())
-
- writeParametersKeepList(method.parameters())
-
- writer.print(";\n")
- }
-
- private fun writeParametersKeepList(params: List<ParameterItem>) {
- writer.print("(")
-
- for (pi in params) {
- if (pi !== params[0]) {
- writer.print(", ")
- }
- writer.print(getCleanTypeName(pi.type()))
- }
-
- writer.print(")")
- }
-
- override fun visitField(field: FieldItem) {
- writer.print(" ")
-
- val modifiers = field.modifiers
- val visibilityLevel = modifiers.getVisibilityLevel()
- if (visibilityLevel != VisibilityLevel.PACKAGE_PRIVATE) {
- writer.write(visibilityLevel.sourceCodeModifier + " ")
- }
-
- if (modifiers.isStatic()) {
- writer.print("static ")
- }
- if (modifiers.isTransient()) {
- writer.print("transient ")
- }
- if (modifiers.isVolatile()) {
- writer.print("volatile ")
- }
-
- writer.print(getCleanTypeName(field.type()))
-
- writer.print(" ")
- writer.print(field.name())
-
- writer.print(";\n")
- }
-
- private fun getCleanTypeName(t: TypeItem?): String {
- t ?: return ""
- val cls = t.asClass() ?: return t.toCanonicalType()
- var qualifiedName = cls.qualifiedNameWithDollarInnerClasses()
-
- for (i in 0 until t.arrayDimensions()) {
- qualifiedName += "[]"
- }
- return qualifiedName
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/Reporter.kt b/src/main/java/com/android/tools/metalava/Reporter.kt
index 0fc3d1a..9bf115e 100644
--- a/src/main/java/com/android/tools/metalava/Reporter.kt
+++ b/src/main/java/com/android/tools/metalava/Reporter.kt
@@ -16,7 +16,6 @@
package com.android.tools.metalava
-import com.android.SdkConstants.ATTR_VALUE
import com.android.tools.metalava.Severity.ERROR
import com.android.tools.metalava.Severity.HIDDEN
import com.android.tools.metalava.Severity.INFO
@@ -88,17 +87,19 @@
*/
private val errorMessage: String?
) {
- var errorCount = 0
- private set
- var warningCount = 0
- private set
- val totalCount get() = errorCount + warningCount
+ private var errors = mutableListOf<String>()
+ private var warningCount = 0
+ val totalCount get() = errors.size + warningCount
- private var hasErrors = false
+ /** The number of errors. */
+ val errorCount get() = errors.size
+
+ /** Returns whether any errors have been detected. */
+ fun hasErrors(): Boolean = errors.size > 0
// Note we can't set [options.baseline] as the default for [customBaseline], because
// options.baseline will be initialized after the global [Reporter] is instantiated.
- fun getBaseline(): Baseline? = customBaseline ?: options.baseline
+ private fun getBaseline(): Baseline? = customBaseline ?: options.baseline
fun report(id: Issues.Issue, element: PsiElement?, message: String): Boolean {
val severity = configuration.getSeverity(id)
@@ -184,29 +185,32 @@
item ?: return false
if (severity == LINT || severity == WARNING || severity == ERROR) {
- val annotation = item.modifiers.findAnnotation("android.annotation.SuppressLint")
- if (annotation != null) {
- val attribute = annotation.findAttribute(ATTR_VALUE)
- if (attribute != null) {
- val id1 = "Doclava${id.code}"
- val id2 = id.name
- val value = attribute.value
- if (value is AnnotationArrayAttributeValue) {
- // Example: @SuppressLint({"DocLava1", "DocLava2"})
- for (innerValue in value.values) {
- val string = innerValue.value()?.toString() ?: continue
- if (suppressMatches(string, id1, message) || suppressMatches(string, id2, message)) {
+ for (annotation in item.modifiers.annotations()) {
+ val annotationName = annotation.qualifiedName()
+ if (annotationName != null && annotationName in SUPPRESS_ANNOTATIONS) {
+ for (attribute in annotation.attributes()) {
+ val id1 = "Doclava${id.code}"
+ val id2 = id.name
+ // Assumption that all annotations in SUPPRESS_ANNOTATIONS only have
+ // one attribute such as value/names that is varargs of String
+ val value = attribute.value
+ if (value is AnnotationArrayAttributeValue) {
+ // Example: @SuppressLint({"DocLava1", "DocLava2"})
+ for (innerValue in value.values) {
+ val string = innerValue.value()?.toString() ?: continue
+ if (suppressMatches(string, id1, message) || suppressMatches(string, id2, message)) {
+ return true
+ }
+ }
+ } else {
+ // Example: @SuppressLint("DocLava1")
+ val string = value.value()?.toString()
+ if (string != null && (
+ suppressMatches(string, id1, message) || suppressMatches(string, id2, message))
+ ) {
return true
}
}
- } else {
- // Example: @SuppressLint("DocLava1")
- val string = value.value()?.toString()
- if (string != null && (
- suppressMatches(string, id1, message) || suppressMatches(string, id2, message))
- ) {
- return true
- }
}
}
}
@@ -248,7 +252,7 @@
return range
}
- fun elementToLocation(element: PsiElement?, includeDocs: Boolean = true): String? {
+ private fun elementToLocation(element: PsiElement?, includeDocs: Boolean = true): String? {
element ?: return null
val psiFile = element.containingFile ?: return null
val virtualFile = psiFile.virtualFile ?: return null
@@ -277,7 +281,7 @@
private fun getLineNumber(text: String, offset: Int): Int {
var line = 0
var curr = 0
- val target = Math.min(offset, text.length)
+ val target = offset.coerceAtMost(text.length)
while (curr < target) {
if (text[curr++] == '\n') {
line++
@@ -286,7 +290,7 @@
return line
}
- /** Alias to allow method reference in [report.dispatch] */
+ /** Alias to allow method reference to `dispatch` in [report] */
private fun doReport(severity: Severity, location: String?, message: String, id: Issues.Issue?) =
report(severity, location, message, id)
@@ -310,17 +314,14 @@
severity
}
+ val formattedMessage = format(effectiveSeverity, location, message, id, color, options.omitLocations)
if (effectiveSeverity == ERROR) {
- hasErrors = true
- errorCount++
+ errors.add(formattedMessage)
} else if (severity == WARNING) {
warningCount++
}
- reportPrinter(
- format(effectiveSeverity, location, message, id, color, options.omitLocations),
- effectiveSeverity
- )
+ reportPrinter(formattedMessage, effectiveSeverity)
return true
}
@@ -420,7 +421,20 @@
return true
}
- fun hasErrors(): Boolean = hasErrors
+ /**
+ * Print all the recorded errors to the given writer. Returns the number of errors printer.
+ */
+ fun printErrors(writer: PrintWriter, maxErrors: Int): Int {
+ var i = 0
+ errors.forEach loop@{
+ if (i >= maxErrors) {
+ return@loop
+ }
+ i++
+ writer.println(it)
+ }
+ return i
+ }
/** Write the error message set to this [Reporter], if any errors have been detected. */
fun writeErrorMessage(writer: PrintWriter) {
@@ -456,3 +470,9 @@
}
}
}
+
+private val SUPPRESS_ANNOTATIONS = listOf(
+ ANDROID_SUPPRESS_LINT,
+ JAVA_LANG_SUPPRESS_WARNINGS,
+ KOTLIN_SUPPRESS
+)
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/SdkFileWriter.kt b/src/main/java/com/android/tools/metalava/SdkFileWriter.kt
index 23104fb..e87166e 100644
--- a/src/main/java/com/android/tools/metalava/SdkFileWriter.kt
+++ b/src/main/java/com/android/tools/metalava/SdkFileWriter.kt
@@ -50,7 +50,7 @@
* Writes various SDK metadata files packaged with the SDK, such as
* {@code platforms/android-27/data/features.txt}
*/
-class SdkFileWriter(val codebase: Codebase, private val outputDir: java.io.File) {
+class SdkFileWriter(val codebase: Codebase, private val outputDir: File) {
/**
* Collect the values used by the Dev tools and write them in files packaged with the SDK
*/
@@ -81,13 +81,12 @@
val resolved =
annotation.findAttribute(null)?.leafValues()?.firstOrNull()?.resolve() as? FieldItem
?: continue
- val type = resolved.containingClass().qualifiedName() + "." + resolved.name()
- when {
- SDK_CONSTANT_TYPE_ACTIVITY_ACTION == type -> activityActions.add(value.toString())
- SDK_CONSTANT_TYPE_BROADCAST_ACTION == type -> broadcastActions.add(value.toString())
- SDK_CONSTANT_TYPE_SERVICE_ACTION == type -> serviceActions.add(value.toString())
- SDK_CONSTANT_TYPE_CATEGORY == type -> categories.add(value.toString())
- SDK_CONSTANT_TYPE_FEATURE == type -> features.add(value.toString())
+ when (resolved.containingClass().qualifiedName() + "." + resolved.name()) {
+ SDK_CONSTANT_TYPE_ACTIVITY_ACTION -> activityActions.add(value.toString())
+ SDK_CONSTANT_TYPE_BROADCAST_ACTION -> broadcastActions.add(value.toString())
+ SDK_CONSTANT_TYPE_SERVICE_ACTION -> serviceActions.add(value.toString())
+ SDK_CONSTANT_TYPE_CATEGORY -> categories.add(value.toString())
+ SDK_CONSTANT_TYPE_FEATURE -> features.add(value.toString())
}
}
}
@@ -98,7 +97,7 @@
if (!clazz.isHiddenOrRemoved() && clazz.isPublic && !clazz.modifiers.isAbstract()) {
var annotated = false
val annotations = clazz.modifiers.annotations()
- if (!annotations.isEmpty()) {
+ if (annotations.isNotEmpty()) {
for (annotation in annotations) {
if (SDK_WIDGET_ANNOTATION == annotation.qualifiedName()) {
widgets.add(clazz)
@@ -120,8 +119,7 @@
if (isIncludedPackage(clazz)) {
// now we check what this class inherits either from android.view.ViewGroup
// or android.view.View, or android.view.ViewGroup.LayoutParams
- val type = checkInheritance(clazz)
- when (type) {
+ when (checkInheritance(clazz)) {
TYPE_WIDGET -> widgets.add(clazz)
TYPE_LAYOUT -> layouts.add(clazz)
TYPE_LAYOUT_PARAM -> layoutParams.add(clazz)
diff --git a/src/main/java/com/android/tools/metalava/SignatureFileLoader.kt b/src/main/java/com/android/tools/metalava/SignatureFileLoader.kt
index 40452fb..3f9399c 100644
--- a/src/main/java/com/android/tools/metalava/SignatureFileLoader.kt
+++ b/src/main/java/com/android/tools/metalava/SignatureFileLoader.kt
@@ -16,8 +16,8 @@
package com.android.tools.metalava
-import com.android.tools.metalava.doclava1.ApiFile
-import com.android.tools.metalava.doclava1.ApiParseException
+import com.android.tools.metalava.model.text.ApiFile
+import com.android.tools.metalava.model.text.ApiParseException
import com.android.tools.metalava.model.Codebase
import java.io.File
diff --git a/src/main/java/com/android/tools/metalava/SignatureWriter.kt b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
index f5a5fa9..bc78eda 100644
--- a/src/main/java/com/android/tools/metalava/SignatureWriter.kt
+++ b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
@@ -47,13 +47,6 @@
filterReference = filterReference,
showUnannotated = options.showUnannotated
) {
- override fun skip(item: Item): Boolean {
- val superSkip = super.skip(item)
- val otherSkip = item is ClassItem && item.notStrippable
- val skipped = superSkip || otherSkip
- return skipped
- }
-
init {
if (options.includeSignatureFormatVersion) {
writer.print(options.outputFormat.header())
@@ -331,8 +324,7 @@
if (compatibility.includeExtendsObjectInWildcard && typeString.endsWith(", ?>") && item is ParameterItem) {
// This wasn't done universally; just in a few places, so replicate it for those exact places
- val methodName = item.containingMethod().name()
- when (methodName) {
+ when (item.containingMethod().name()) {
"computeIfAbsent" -> {
if (typeString == "java.util.function.Function<? super java.lang.Object, ?>") {
typeString = "java.util.function.Function<? super java.lang.Object, ? extends java.lang.Object>"
diff --git a/src/main/java/com/android/tools/metalava/Terminal.kt b/src/main/java/com/android/tools/metalava/Terminal.kt
index 8ba7551..5f4f163 100644
--- a/src/main/java/com/android/tools/metalava/Terminal.kt
+++ b/src/main/java/com/android/tools/metalava/Terminal.kt
@@ -16,8 +16,6 @@
package com.android.tools.metalava
-import java.io.PrintWriter
-
enum class TerminalColor(val value: Int) {
BLACK(0),
RED(1),
@@ -81,21 +79,3 @@
fun bold(string: String): String {
return "${terminalAttributes(bold = true)}$string${resetTerminal()}"
}
-
-fun PrintWriter.terminalPrint(
- string: String,
- bold: Boolean = false,
- underline: Boolean = false,
- reverse: Boolean = false,
- foreground: TerminalColor? = null,
- background: TerminalColor? = null
-) {
- print(
- terminalAttributes(
- bold = bold, underline = underline, reverse = reverse, foreground = foreground,
- background = background
- )
- )
- print(string)
- print(resetTerminal())
-}
diff --git a/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java b/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java
index afcac41..f8d9437 100644
--- a/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java
+++ b/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java
@@ -92,7 +92,7 @@
System.err.println("Missing number >= 1 after " + arg);
error = true;
}
- } else if (arg.length() >= 2 && arg.substring(0, 2).equals("--")) {
+ } else if (arg.length() >= 2 && arg.startsWith("--")) {
System.err.println("Unknown argument: " + arg);
error = true;
} else if (outPath == null) {
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt b/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt
index 12e11d0..4392954 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt
@@ -49,7 +49,10 @@
private val allowClassesFromClasspath: Boolean = options.allowClassesFromClasspath,
/** Whether we should include doc-only items */
- private val includeDocOnly: Boolean = false
+ private val includeDocOnly: Boolean = false,
+
+ /** Whether to include "for stub purposes" APIs. See [Options.showForStubPurposesAnnotations] */
+ private val includeApisForStubPurposes: Boolean = true
) : Predicate<Item> {
override fun test(member: Item): Boolean {
@@ -62,11 +65,14 @@
return false
}
- var visible = member.isPublic || member.isProtected // TODO: Should this use checkLevel instead?
+ var visible = member.isPublic || member.isProtected || (member.isInternal && member.hasShowAnnotation()) // TODO: Should this use checkLevel instead?
var hidden = member.hidden
if (!visible || hidden) {
return false
}
+ if (!includeApisForStubPurposes && includeOnlyForStubPurposes(member)) {
+ return false
+ }
var hasShowAnnotation = ignoreShown || member.hasShowAnnotation()
var docOnly = member.docOnly
@@ -88,7 +94,9 @@
}
}
while (clazz != null) {
- visible = visible and (clazz.isPublic || clazz.isProtected)
+ visible = visible and (clazz.isPublic || clazz.isProtected ||
+ (clazz.isInternal && clazz.hasShowAnnotation())
+ )
hasShowAnnotation = hasShowAnnotation or (ignoreShown || clazz.hasShowAnnotation())
hidden = hidden or clazz.hidden
docOnly = docOnly or clazz.docOnly
@@ -106,4 +114,30 @@
return visible && hasShowAnnotation && !hidden && !docOnly && removed == matchRemoved
}
+
+ /**
+ * Returns true, if an item should be included only for "stub" purposes; that is,
+ * the item does *not* have a [Options.showAnnotations] annotation but
+ * has a [Options.showForStubPurposesAnnotations] annotation.
+ */
+ private fun includeOnlyForStubPurposes(item: Item): Boolean {
+ if (options.showForStubPurposesAnnotations.isEmpty()) {
+ return false
+ }
+
+ // If the item has a "show" annotation, then return whether it has a "for stubs" annotation
+ // or not.
+ //
+ // Note, If the item does not have a show annotation, then it can't have a "for stubs" one,
+ // because the later must be a subset of the former, which we don't detect in *this*
+ // run (unfortunately it's hard to do so due to how things work), but when metalava
+ // is executed for the parent API, we'd detect it as
+ // [Issues.SHOWING_MEMBER_IN_HIDDEN_CLASS].
+ if (item.hasShowAnnotationInherited()) {
+ return item.hasShowForStubPurposesAnnotationInherited()
+ }
+ // If this item has neither --show-annotation nor --parent-api-annotation,
+ // Then defer to the "parent" item (i.e. the enclosing class or package).
+ return item.parent()?.let { includeOnlyForStubPurposes(it) } ?: false
+ }
}
diff --git a/src/main/java/com/android/tools/metalava/doclava1/Issues.kt b/src/main/java/com/android/tools/metalava/doclava1/Issues.kt
index 8b61599..aabfcc0 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/Issues.kt
+++ b/src/main/java/com/android/tools/metalava/doclava1/Issues.kt
@@ -128,6 +128,7 @@
val FORBIDDEN_TAG = Issue(162, Severity.ERROR)
val MISSING_COLUMN = Issue(163, Severity.WARNING, Category.DOCUMENTATION)
val INVALID_SYNTAX = Issue(164, Severity.ERROR)
+ val UNRESOLVED_IMPORT = Issue(165, Severity.INFO)
// API lint
val START_WITH_LOWER = Issue(300, Severity.ERROR, Category.API_LINT, "S1")
@@ -220,6 +221,12 @@
val PUBLIC_TYPEDEF = Issue(388, Severity.ERROR, Category.API_LINT, "FW15")
val ANDROID_URI = Issue(389, Severity.ERROR, Category.API_LINT, "FW14")
val BAD_FUTURE = Issue(390, Severity.ERROR, Category.API_LINT)
+ val STATIC_FINAL_BUILDER = Issue(391, Severity.WARNING, Category.API_LINT)
+ val GETTER_ON_BUILDER = Issue(392, Severity.WARNING, Category.API_LINT)
+ val MISSING_GETTER_MATCHING_BUILDER = Issue(393, Severity.WARNING, Category.API_LINT)
+ val OPTIONAL_BUILDER_CONSTRUCTOR_ARGUMENT = Issue(394, Severity.WARNING, Category.API_LINT)
+ val NO_SETTINGS_PROVIDER = Issue(395, Severity.HIDDEN, Category.API_LINT)
+ val PRIVATE_COMPANION = Issue(396, Severity.ERROR, Category.API_LINT)
fun findIssueById(id: Int): Issue? {
return idToIssue[id]
@@ -307,4 +314,4 @@
check(issue.name != "")
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
index 7b82910..37802ad 100644
--- a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
@@ -38,6 +38,12 @@
import com.android.tools.metalava.doclava1.ApiPredicate
import com.android.tools.metalava.model.psi.PsiBasedCodebase
import com.android.tools.metalava.options
+import com.intellij.psi.PsiCallExpression
+import com.intellij.psi.PsiField
+import com.intellij.psi.PsiModifierListOwner
+import com.intellij.psi.PsiReference
+import org.jetbrains.kotlin.psi.KtObjectDeclaration
+import org.jetbrains.uast.UElement
import java.util.function.Predicate
fun isNullableAnnotation(qualifiedName: String): Boolean {
@@ -389,7 +395,10 @@
if (target == AnnotationTarget.SDK_STUBS_FILE) ANDROID_NONNULL else ANDROIDX_NONNULL
/** The applicable targets for this annotation */
- fun computeTargets(annotation: AnnotationItem, codebase: Codebase): Set<AnnotationTarget> {
+ fun computeTargets(
+ annotation: AnnotationItem,
+ classFinder: (String) -> ClassItem?
+ ): Set<AnnotationTarget> {
val qualifiedName = annotation.qualifiedName() ?: return NO_ANNOTATION_TARGETS
if (options.passThroughAnnotations.contains(qualifiedName)) {
return ANNOTATION_IN_ALL_STUBS
@@ -409,21 +418,29 @@
"android.annotation.LongDef",
"androidx.annotation.LongDef" -> return ANNOTATION_EXTERNAL_ONLY
+ // Not directly API relevant
+ "android.view.ViewDebug.ExportedProperty",
+ "android.view.ViewDebug.CapturedViewProperty" -> return ANNOTATION_STUBS_ONLY
+
// Skip known annotations that we (a) never want in external annotations and (b) we are
// specially overwriting anyway in the stubs (and which are (c) not API significant)
"java.lang.annotation.Native",
"java.lang.SuppressWarnings",
- "java.lang.Override" -> return NO_ANNOTATION_TARGETS
+ "java.lang.Override",
+ "kotlin.Suppress",
+ "androidx.annotation.experimental.UseExperimental",
+ "kotlin.UseExperimental",
+ "kotlin.OptIn" -> return NO_ANNOTATION_TARGETS
+ // TODO(aurimas): consider using annotation directly instead of modifiers
+ "kotlin.Deprecated" -> return NO_ANNOTATION_TARGETS // tracked separately as a pseudo-modifier
"java.lang.Deprecated", // tracked separately as a pseudo-modifier
// Below this when-statement we perform the correct lookup: check API predicate, and check
// that retention is class or runtime, but we've hardcoded the answers here
// for some common annotations.
- "android.view.ViewDebug.ExportedProperty",
"android.widget.RemoteViews.RemoteView",
- "android.view.ViewDebug.CapturedViewProperty",
"kotlin.annotation.Target",
"kotlin.annotation.Retention",
@@ -440,6 +457,12 @@
"java.lang.annotation.Repeatable",
"java.lang.annotation.Retention",
"java.lang.annotation.Target" -> return ANNOTATION_IN_ALL_STUBS
+
+ // Metalava already tracks all the methods that get generated due to these annotations.
+ "kotlin.jvm.JvmOverloads",
+ "kotlin.jvm.JvmField",
+ "kotlin.jvm.JvmStatic",
+ "kotlin.jvm.JvmName" -> return NO_ANNOTATION_TARGETS
}
// @android.annotation.Nullable and NonNullable specially recognized annotations by the Kotlin
@@ -489,7 +512,7 @@
}
// See if the annotation is pointing to an annotation class that is part of the API; if not, skip it.
- val cls = codebase.findClass(qualifiedName) ?: return NO_ANNOTATION_TARGETS
+ val cls = classFinder(qualifiedName) ?: return NO_ANNOTATION_TARGETS
if (!ApiPredicate().test(cls)) {
if (options.typedefMode != Options.TypedefMode.NONE) {
if (cls.modifiers.annotations().any { it.isTypeDefAnnotation() }) {
@@ -503,7 +526,7 @@
if (cls.isAnnotationType()) {
val retention = cls.getRetention()
if (retention == AnnotationRetention.RUNTIME || retention == AnnotationRetention.CLASS) {
- return ANNOTATION_IN_SDK_STUBS
+ return ANNOTATION_IN_ALL_STUBS
}
}
@@ -564,12 +587,51 @@
fun getImplicitNullness(item: Item): Boolean? {
var nullable: Boolean? = null
+ // Is this a Kotlin object declaration (such as a companion object) ?
+ // If so, it is always non null.
+ val sourcePsi = item.psi()
+ if (sourcePsi is UElement && sourcePsi.sourcePsi is KtObjectDeclaration) {
+ nullable = false
+ }
+
// Constant field not initialized to null?
if (item is FieldItem &&
(item.isEnumConstant() || item.modifiers.isFinal() && item.initialValue(false) != null)
) {
// Assigned to constant: not nullable
nullable = false
+ } else if (item is FieldItem && item.modifiers.isFinal()) {
+ // If we're looking at a final field, look at the right hand side
+ // of the field to the field initialization. If that right hand side
+ // for example represents a method call, and the method we're calling
+ // is annotated with @NonNull, then the field (since it is final) will
+ // always be @NonNull as well.
+ val initializer = (item.psi() as? PsiField)?.initializer
+ if (initializer != null && initializer is PsiReference) {
+ val resolved = initializer.resolve()
+ if (resolved is PsiModifierListOwner &&
+ resolved.annotations.any {
+ isNonNullAnnotation(it.qualifiedName ?: "")
+ }
+ ) {
+ nullable = false
+ }
+ } else if (initializer != null && initializer is PsiCallExpression) {
+ val resolved = initializer.resolveMethod()
+ if (resolved != null &&
+ resolved.annotations.any {
+ isNonNullAnnotation(it.qualifiedName ?: "")
+ }
+ ) {
+ nullable = false
+ }
+ }
+ } else if (item.synthetic && (item is MethodItem && item.isEnumSyntheticMethod() ||
+ item is ParameterItem && item.containingMethod().isEnumSyntheticMethod())
+ ) {
+ // Workaround the fact that the Kotlin synthetic enum methods
+ // do not have nullness information
+ nullable = false
}
// Annotation type members cannot be null
@@ -593,11 +655,13 @@
/** Default implementation of an annotation item */
abstract class DefaultAnnotationItem(override val codebase: Codebase) : AnnotationItem {
- private var targets: Set<AnnotationTarget>? = null
+ protected var targets: Set<AnnotationTarget>? = null
override fun targets(): Set<AnnotationTarget> {
if (targets == null) {
- targets = AnnotationItem.computeTargets(this, codebase)
+ targets = AnnotationItem.computeTargets(this) { className ->
+ codebase.findClass(className)
+ }
}
return targets!!
}
diff --git a/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt b/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt
index a4d2c72..37865b4 100644
--- a/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt
+++ b/src/main/java/com/android/tools/metalava/model/AnnotationTarget.kt
@@ -91,3 +91,6 @@
/** Write it only into the he signature file */
val ANNOTATION_SIGNATURE_ONLY = setOf(AnnotationTarget.SIGNATURE_FILE)
+
+/** Write it only into the stubs, but don't track it in the signature files. */
+val ANNOTATION_STUBS_ONLY = setOf(AnnotationTarget.SDK_STUBS_FILE, AnnotationTarget.DOC_STUBS_FILE)
diff --git a/src/main/java/com/android/tools/metalava/model/ClassItem.kt b/src/main/java/com/android/tools/metalava/model/ClassItem.kt
index 75c558a..0cee0cc 100644
--- a/src/main/java/com/android/tools/metalava/model/ClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/ClassItem.kt
@@ -215,7 +215,7 @@
fun typeParameterList(): TypeParameterList
/** Returns the classes that are part of the type parameters of this method, if any */
- fun typeArgumentClasses(): List<ClassItem> = TODO("Not yet implemented")
+ fun typeArgumentClasses(): List<ClassItem> = codebase.unsupported()
fun isJavaLangObject(): Boolean {
return qualifiedName() == JAVA_LANG_OBJECT
@@ -235,9 +235,6 @@
var hasPrivateConstructor: Boolean
- /** If true, this is an invisible element that was referenced by a public API. */
- var notStrippable: Boolean
-
/**
* Maven artifact of this class, if any. (Not used for the Android SDK, but used in
* for example support libraries.
@@ -502,7 +499,7 @@
if (parameters.size != parameterStrings.size) {
return false
}
- for (i in 0 until parameters.size) {
+ for (i in parameters.indices) {
var parameterString = parameterStrings[i]
val index = parameterString.indexOf('<')
if (index != -1) {
@@ -761,7 +758,7 @@
}
class VisitCandidate(val cls: ClassItem, private val visitor: ApiVisitor) {
- public val innerClasses: Sequence<VisitCandidate>
+ val innerClasses: Sequence<VisitCandidate>
private val constructors: Sequence<MethodItem>
private val methods: Sequence<MethodItem>
private val fields: Sequence<FieldItem>
@@ -814,7 +811,7 @@
}
/** Whether the class body contains any Item's (other than inner Classes) */
- public fun nonEmpty(): Boolean {
+ fun nonEmpty(): Boolean {
return !(constructors.none() && methods.none() && enums.none() && fields.none() && properties.none())
}
diff --git a/src/main/java/com/android/tools/metalava/model/Codebase.kt b/src/main/java/com/android/tools/metalava/model/Codebase.kt
index 63e9582..cf3036a 100644
--- a/src/main/java/com/android/tools/metalava/model/Codebase.kt
+++ b/src/main/java/com/android/tools/metalava/model/Codebase.kt
@@ -32,7 +32,6 @@
import com.android.utils.XmlUtils.getFirstSubTagByName
import com.android.utils.XmlUtils.getNextTagByName
import com.intellij.psi.PsiFile
-import org.intellij.lang.annotations.Language
import org.objectweb.asm.Type
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldInsnNode
@@ -109,7 +108,7 @@
* Creates an annotation item for the given (fully qualified) Java source
*/
fun createAnnotation(
- @Language("JAVA") source: String,
+ source: String,
context: Item? = null,
mapName: Boolean = true
): AnnotationItem = TextBackedAnnotationItem(
@@ -193,7 +192,7 @@
val parameters = if (types.isNotEmpty()) {
val sb = StringBuilder()
for (type in types) {
- if (!sb.isEmpty()) {
+ if (sb.isNotEmpty()) {
sb.append(", ")
}
sb.append(type.className.replace('/', '.').replace('$', '.'))
@@ -217,7 +216,7 @@
val parameters = if (types.isNotEmpty()) {
val sb = StringBuilder()
for (type in types) {
- if (!sb.isEmpty()) {
+ if (sb.isNotEmpty()) {
sb.append(", ")
}
sb.append(type.className.replace('/', '.').replace('$', '.'))
@@ -307,10 +306,10 @@
minSdkVersion = UnsetMinSdkVersion
return minSdkVersion!!
}
- try {
+ minSdkVersion = try {
val doc = parseDocument(manifest?.readText(UTF_8) ?: "", true)
val usesSdk = getFirstSubTagByName(doc.documentElement, TAG_USES_SDK)
- minSdkVersion = if (usesSdk == null) {
+ if (usesSdk == null) {
UnsetMinSdkVersion
} else {
val value = usesSdk.getAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)
@@ -318,7 +317,7 @@
}
} catch (error: Throwable) {
reporter.report(Issues.PARSE_ERROR, manifest, "Failed to parse $manifest: ${error.message}")
- minSdkVersion = UnsetMinSdkVersion
+ UnsetMinSdkVersion
}
}
return minSdkVersion!!
diff --git a/src/main/java/com/android/tools/metalava/model/CompilationUnit.kt b/src/main/java/com/android/tools/metalava/model/CompilationUnit.kt
index 37529de..528407d 100644
--- a/src/main/java/com/android/tools/metalava/model/CompilationUnit.kt
+++ b/src/main/java/com/android/tools/metalava/model/CompilationUnit.kt
@@ -18,11 +18,13 @@
import com.intellij.lang.Language
import com.intellij.psi.PsiFile
+import org.jetbrains.uast.UFile
import java.util.function.Predicate
/** Represents a compilation unit (e.g. a .java or a .kt file) */
open class CompilationUnit(
- val file: PsiFile
+ val file: PsiFile,
+ val uFile: UFile?
) {
val language: Language? get() = file.language
diff --git a/src/main/java/com/android/tools/metalava/model/IssueConfiguration.kt b/src/main/java/com/android/tools/metalava/model/IssueConfiguration.kt
index 527e240..6b34b13 100644
--- a/src/main/java/com/android/tools/metalava/model/IssueConfiguration.kt
+++ b/src/main/java/com/android/tools/metalava/model/IssueConfiguration.kt
@@ -16,6 +16,7 @@
package com.android.tools.metalava.model
+import com.android.tools.metalava.Options
import com.android.tools.metalava.Severity
import com.android.tools.metalava.doclava1.Issues
diff --git a/src/main/java/com/android/tools/metalava/model/Item.kt b/src/main/java/com/android/tools/metalava/model/Item.kt
index 1d2c047..c5d66a6 100644
--- a/src/main/java/com/android/tools/metalava/model/Item.kt
+++ b/src/main/java/com/android/tools/metalava/model/Item.kt
@@ -41,13 +41,6 @@
val modifiers: ModifierList
/**
- * Whether this element should be part of the API. The algorithm for this is complicated, so it can't
- * be computed initially; we'll make passes over the source code to determine eligibility and mark all
- * items as included or not.
- */
- var included: Boolean
-
- /**
* Whether this element was originally hidden with @hide/@Hide. The [hidden] property
* tracks whether it is *actually* hidden, since elements can be unhidden via show annotations, etc.
*/
@@ -74,6 +67,12 @@
/** True if this element is only intended for documentation */
var docOnly: Boolean
+ /**
+ * True if this is a synthetic element, such as the generated "value" and "valueOf" methods
+ * in enums
+ */
+ val synthetic: Boolean
+
/** True if this item is either hidden or removed */
fun isHiddenOrRemoved(): Boolean = hidden || removed
@@ -122,6 +121,7 @@
val isPublic: Boolean
val isProtected: Boolean
+ val isInternal: Boolean
val isPackagePrivate: Boolean
val isPrivate: Boolean
@@ -161,9 +161,30 @@
fun isKotlin() = !isJava()
fun hasShowAnnotation(): Boolean = modifiers.hasShowAnnotation()
+ fun hasShowForStubPurposesAnnotation(): Boolean = modifiers.hasShowForStubPurposesAnnotation()
fun hasHideAnnotation(): Boolean = modifiers.hasHideAnnotations()
fun hasHideMetaAnnotation(): Boolean = modifiers.hasHideMetaAnnotations()
+ /**
+ * Same as [hasShowAnnotation], except if it's a method, take into account super methods'
+ * annotations.
+ *
+ * Unlike classes or fields, methods implicitly inherits visibility annotations, and for
+ * some visibility calculation we need to take it into account.
+ * (See ShowAnnotationTest.`Methods inherit showAnnotations but fields and classes don't`.)
+ */
+ fun hasShowAnnotationInherited(): Boolean = hasShowAnnotation()
+
+ /**
+ * Same as [hasShowForStubPurposesAnnotation], except if it's a method, take into account super methods'
+ * annotations.
+ *
+ * Unlike classes or fields, methods implicitly inherits visibility annotations, and for
+ * some visibility calculation we need to take it into account.
+ * (See ShowAnnotationTest.`Methods inherit showAnnotations but fields and classes don't`.)
+ */
+ fun hasShowForStubPurposesAnnotationInherited(): Boolean = hasShowForStubPurposesAnnotation()
+
fun checkLevel(): Boolean {
return modifiers.checkLevel()
}
@@ -355,17 +376,14 @@
abstract class DefaultItem(override val sortingRank: Int = nextRank++) : Item {
override val isPublic: Boolean get() = modifiers.isPublic()
override val isProtected: Boolean get() = modifiers.isProtected()
+ override val isInternal: Boolean
+ get() = modifiers.getVisibilityLevel() == VisibilityLevel.INTERNAL
override val isPackagePrivate: Boolean get() = modifiers.isPackagePrivate()
override val isPrivate: Boolean get() = modifiers.isPrivate()
override var emit = true
override var tag: Boolean = false
- // TODO: Get rid of this; with the new predicate approach it's redundant (and
- // storing it per element is problematic since the predicate sometimes includes
- // methods from parent interfaces etc)
- override var included: Boolean = true
-
companion object {
private var nextRank: Int = 1
}
diff --git a/src/main/java/com/android/tools/metalava/model/Language.kt b/src/main/java/com/android/tools/metalava/model/Language.kt
new file mode 100644
index 0000000..03642fa
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/Language.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.model
+
+enum class Language {
+ KOTLIN,
+ JAVA
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/MethodItem.kt b/src/main/java/com/android/tools/metalava/model/MethodItem.kt
index 251d1d8..98fbf5a 100644
--- a/src/main/java/com/android/tools/metalava/model/MethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/MethodItem.kt
@@ -17,7 +17,7 @@
package com.android.tools.metalava.model
import com.android.tools.metalava.compatibility
-import com.android.tools.metalava.doclava1.TextCodebase
+import com.android.tools.metalava.model.text.TextCodebase
import com.android.tools.metalava.model.visitors.ItemVisitor
import com.android.tools.metalava.model.visitors.TypeVisitor
import java.util.LinkedHashSet
@@ -79,7 +79,7 @@
fun typeParameterList(): TypeParameterList
/** Returns the classes that are part of the type parameters of this method, if any */
- fun typeArgumentClasses(): List<ClassItem> = TODO("Not yet implemented")
+ fun typeArgumentClasses(): List<ClassItem> = codebase.unsupported()
/** Types of exceptions that this method can throw */
fun throwsTypes(): List<ClassItem>
@@ -298,7 +298,7 @@
}
assert(parameterList1.size == parameterList2.size)
- for (i in 0 until parameterList1.size) {
+ for (i in parameterList1.indices) {
val p1 = parameterList1[i]
val p2 = parameterList2[i]
val pt1 = p1.type()
@@ -327,7 +327,7 @@
}
assert(throwsList12.size == throwsList2.size)
- for (i in 0 until throwsList12.size) {
+ for (i in throwsList12.indices) {
val p1 = throwsList12[i]
val p2 = throwsList2[i]
val pt1 = p1.qualifiedName()
@@ -349,7 +349,7 @@
}
val sb = StringBuilder()
for (parameter in parameters()) {
- if (!sb.isEmpty()) {
+ if (sb.isNotEmpty()) {
sb.append(", ")
}
sb.append(parameter.type().toTypeString())
@@ -440,7 +440,7 @@
return false
}
- for (i in 0 until parameters1.size) {
+ for (i in parameters1.indices) {
val parameter1 = parameters1[i]
val parameter2 = parameters2[i]
val typeString1 = parameter1.type().toString()
@@ -456,11 +456,13 @@
// parameters: if we see a mismatch here which looks like a failure to erase say T into
// java.lang.Object, don't treat that as a mismatch. (Similar common case: T[] and Object[])
if (typeString1[0].isUpperCase() && typeString1.length == 1 &&
- parameter1.codebase is TextCodebase) {
+ parameter1.codebase is TextCodebase
+ ) {
continue
}
if (typeString2.length >= 2 && !typeString2[1].isLetterOrDigit() &&
- parameter1.codebase is TextCodebase) {
+ parameter1.codebase is TextCodebase
+ ) {
continue
}
return false
@@ -512,6 +514,32 @@
return false
}
+ override fun hasShowAnnotationInherited(): Boolean {
+ if (super.hasShowAnnotationInherited()) {
+ return true
+ }
+ return superMethods().any {
+ it.hasShowAnnotationInherited()
+ }
+ }
+
+ override fun hasShowForStubPurposesAnnotationInherited(): Boolean {
+ if (super.hasShowForStubPurposesAnnotationInherited()) {
+ return true
+ }
+ return superMethods().any {
+ it.hasShowForStubPurposesAnnotationInherited()
+ }
+ }
+
/** Whether this method is a getter/setter for an underlying Kotlin property (val/var) */
fun isKotlinProperty(): Boolean = false
+
+ /** Returns true if this is a synthetic enum method */
+ fun isEnumSyntheticMethod(): Boolean {
+ return containingClass().isEnum() &&
+ (name() == "values" && parameters().isEmpty() ||
+ name() == "valueOf" && parameters().size == 1 &&
+ parameters()[0].type().isString())
+ }
}
diff --git a/src/main/java/com/android/tools/metalava/model/ModifierList.kt b/src/main/java/com/android/tools/metalava/model/ModifierList.kt
index 648a051..4d2ee67 100644
--- a/src/main/java/com/android/tools/metalava/model/ModifierList.kt
+++ b/src/main/java/com/android/tools/metalava/model/ModifierList.kt
@@ -120,6 +120,19 @@
/**
* Returns true if this modifier list contains any annotations explicitly passed in
+ * via [Options.showForStubPurposesAnnotations]
+ */
+ fun hasShowForStubPurposesAnnotation(): Boolean {
+ if (options.showForStubPurposesAnnotations.isEmpty()) {
+ return false
+ }
+ return annotations().any {
+ options.showForStubPurposesAnnotations.matches(it)
+ }
+ }
+
+ /**
+ * Returns true if this modifier list contains any annotations explicitly passed in
* via [Options.hideAnnotations] or any annotations which are themselves annotated
* with meta-annotations explicitly passed in via [Options.hideMetaAnnotations]
*
@@ -215,7 +228,7 @@
* the source code for the visibility modifiers in the modifier list
*/
fun getVisibilityModifiers(): String {
- return getVisibilityLevel().sourceCodeModifier
+ return getVisibilityLevel().javaSourceCodeModifier
}
companion object {
@@ -233,7 +246,8 @@
removeAbstract: Boolean = false,
removeFinal: Boolean = false,
addPublic: Boolean = false,
- separateLines: Boolean = false
+ separateLines: Boolean = false,
+ language: Language = Language.JAVA
) {
val list = if (removeAbstract || removeFinal || addPublic) {
@@ -292,7 +306,7 @@
if (compatibility.nonstandardModifierOrder) {
val visibilityLevel = list.getVisibilityLevel()
if (visibilityLevel != VisibilityLevel.PACKAGE_PRIVATE) {
- writer.write(visibilityLevel.sourceCodeModifier + " ")
+ writer.write(visibilityLevel.javaSourceCodeModifier + " ")
}
if (list.isDefault()) {
@@ -371,8 +385,13 @@
}
val visibilityLevel = list.getVisibilityLevel()
- if (visibilityLevel != VisibilityLevel.PACKAGE_PRIVATE) {
- writer.write(visibilityLevel.sourceCodeModifier + " ")
+ val modifier = if (language == Language.JAVA) {
+ visibilityLevel.javaSourceCodeModifier
+ } else {
+ visibilityLevel.kotlinSourceCodeModifier
+ }
+ if (modifier.isNotEmpty()) {
+ writer.write("$modifier ")
}
val isInterface = classItem?.isInterface() == true ||
@@ -397,11 +416,14 @@
}
if (list.isFinal() &&
+ language == Language.JAVA &&
// Don't show final on parameters: that's an implementation side detail
item !is ParameterItem &&
(classItem?.isEnum() != true || compatibility.finalInInterfaces)
) {
writer.write("final ")
+ } else if (!list.isFinal() && language == Language.KOTLIN) {
+ writer.write("open ")
}
if (list.isSealed()) {
diff --git a/src/main/java/com/android/tools/metalava/model/TypeItem.kt b/src/main/java/com/android/tools/metalava/model/TypeItem.kt
index 75c1ffd..05a881f 100644
--- a/src/main/java/com/android/tools/metalava/model/TypeItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/TypeItem.kt
@@ -98,7 +98,7 @@
fun convertType(from: ClassItem, to: ClassItem): TypeItem {
val map = from.mapTypeVariables(to)
- if (!map.isEmpty()) {
+ if (map.isNotEmpty()) {
return convertType(map)
}
@@ -256,8 +256,8 @@
// method provided in a hidden superclass), with generics signatures.
// We need to rewrite the generics variables in case they differ
// between the classes.
- if (!replacementMap.isEmpty()) {
- replacementMap.forEach { from, to ->
+ if (replacementMap.isNotEmpty()) {
+ replacementMap.forEach { (from, to) ->
// We can't just replace one string at a time:
// what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C?
// If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions
diff --git a/src/main/java/com/android/tools/metalava/model/VisibilityLevel.kt b/src/main/java/com/android/tools/metalava/model/VisibilityLevel.kt
index 7c0417b..c973bc2 100644
--- a/src/main/java/com/android/tools/metalava/model/VisibilityLevel.kt
+++ b/src/main/java/com/android/tools/metalava/model/VisibilityLevel.kt
@@ -25,7 +25,12 @@
/**
* String representation in source code.
*/
- val sourceCodeModifier: String,
+ val javaSourceCodeModifier: String,
+
+ /**
+ * String representation in Kotlin source code.
+ */
+ val kotlinSourceCodeModifier: String,
/**
* String representation in user visible messages.
@@ -36,9 +41,9 @@
*/
val visibilityFlagValue: Int
) {
- PRIVATE("private", "private", DefaultModifierList.PRIVATE),
- INTERNAL("internal", "internal", DefaultModifierList.INTERNAL),
- PACKAGE_PRIVATE("", "package private", DefaultModifierList.PACKAGE_PRIVATE),
- PROTECTED("protected", "protected", DefaultModifierList.PROTECTED),
- PUBLIC("public", "public", DefaultModifierList.PUBLIC)
+ PRIVATE("private", "private", "private", DefaultModifierList.PRIVATE),
+ INTERNAL("internal", "internal", "internal", DefaultModifierList.INTERNAL),
+ PACKAGE_PRIVATE("", "", "package private", DefaultModifierList.PACKAGE_PRIVATE),
+ PROTECTED("protected", "protected", "protected", DefaultModifierList.PROTECTED),
+ PUBLIC("public", "", "public", DefaultModifierList.PUBLIC)
}
diff --git a/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt b/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt
index ffe93db..97bfad7 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/CodePrinter.kt
@@ -179,8 +179,7 @@
sb.append('}')
return sb.length != 2
} else if (expression is UReferenceExpression) {
- val resolved = expression.resolve()
- when (resolved) {
+ when (val resolved = expression.resolve()) {
is PsiField -> {
@Suppress("UnnecessaryVariable")
val field = resolved
@@ -438,7 +437,7 @@
return String.format("'%s'", javaEscapeString(value.toString()))
}
- is kotlin.Pair<*, *> -> {
+ is Pair<*, *> -> {
val first = value.first
if (first is ClassId) {
return first.packageFqName.asString() + "." + first.relativeClassName.asString()
diff --git a/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt b/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt
index 1b6bfda..e96fae5 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt
@@ -50,12 +50,6 @@
*/
/**
- * If true, we'll rewrite all the javadoc documentation in doc stubs
- * to include fully qualified names
- */
-const val EXPAND_DOCUMENTATION = true
-
-/**
* If the reference is to a class in the same package, include the package prefix?
* This should not be necessary, but doclava has problems finding classes without
* it. Consider turning this off when we switch to Dokka.
@@ -868,8 +862,7 @@
// android.os.Bundle#getInt, but the resolved method actually points to
// an inherited method into android.os.Bundle from android.os.BaseBundle.
// In that case we don't want to rewrite the link.
- for (index in 0 until referenceText.length) {
- val c = referenceText[index]
+ for (c in referenceText) {
if (c == '.') {
// Already qualified
sb.append(text)
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
index 4771035..6561881 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
@@ -102,6 +102,15 @@
return attributes!!
}
+ override fun targets(): Set<AnnotationTarget> {
+ if (targets == null) {
+ targets = AnnotationItem.computeTargets(this) { className ->
+ codebase.findOrCreateClass(className)
+ }
+ }
+ return targets!!
+ }
+
companion object {
fun create(codebase: PsiBasedCodebase, psiAnnotation: PsiAnnotation, qualifiedName: String? = psiAnnotation.qualifiedName): PsiAnnotationItem {
return PsiAnnotationItem(codebase, psiAnnotation, qualifiedName)
@@ -198,8 +207,7 @@
null -> sb.append("null")
is PsiLiteral -> sb.append(constantToSource(value.value))
is PsiReference -> {
- val resolved = value.resolve()
- when (resolved) {
+ when (val resolved = value.resolve()) {
is PsiField -> {
val containing = resolved.containingClass
if (containing != null) {
@@ -323,8 +331,7 @@
override fun resolve(): Item? {
if (psiValue is PsiReference) {
- val resolved = psiValue.resolve()
- when (resolved) {
+ when (val resolved = psiValue.resolve()) {
is PsiField -> return codebase.findField(resolved)
is PsiClass -> return codebase.findOrCreateClass(resolved)
is PsiMethod -> return codebase.findMethod(resolved)
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt
index dcfd717..ca97060 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt
@@ -17,6 +17,7 @@
package com.android.tools.metalava.model.psi
import com.android.SdkConstants
+import com.android.tools.lint.UastEnvironment
import com.android.tools.metalava.ANDROIDX_NONNULL
import com.android.tools.metalava.ANDROIDX_NULLABLE
import com.android.tools.metalava.doclava1.Issues
@@ -32,7 +33,6 @@
import com.android.tools.metalava.reporter
import com.android.tools.metalava.tick
import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.Disposer
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.JavaRecursiveElementVisitor
import com.intellij.psi.PsiAnnotation
@@ -44,6 +44,7 @@
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiField
import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiImportStatement
import com.intellij.psi.PsiJavaCodeReferenceElement
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiMethod
@@ -55,9 +56,8 @@
import com.intellij.psi.javadoc.PsiDocTag
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
-import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.uast.UFile
-import org.jetbrains.uast.UastContext
+import org.jetbrains.uast.UastFacade
import java.io.File
import java.io.IOException
import java.util.ArrayList
@@ -69,9 +69,9 @@
const val METHOD_ESTIMATE = 1000
open class PsiBasedCodebase(location: File, override var description: String = "Unknown") : DefaultCodebase(location) {
- lateinit var project: Project
-
- var bindingContext: BindingContext? = null
+ lateinit var uastEnvironment: UastEnvironment
+ val project: Project
+ get() = uastEnvironment.ideaProject
/** Map from class name to class item */
private val classMap: MutableMap<String, PsiClassItem> = HashMap(CLASS_ESTIMATE)
@@ -110,12 +110,12 @@
private lateinit var emptyPackage: PsiPackageItem
- fun initialize(project: Project, units: List<PsiFile>, packages: PackageDocs) {
+ fun initialize(uastEnvironment: UastEnvironment, units: List<PsiFile>, packages: PackageDocs) {
initializing = true
this.units = units
packageDocs = packages
- this.project = project
+ this.uastEnvironment = uastEnvironment
// there are currently ~230 packages in the public SDK, but here we need to account for internal ones too
val hiddenPackages: MutableSet<String> = packages.hiddenPackages
val packageDocs: MutableMap<String, String> = packages.packageDocs
@@ -134,10 +134,22 @@
for (unit in units.asSequence().distinct()) {
tick() // show progress
+ unit.accept(object : JavaRecursiveElementVisitor() {
+ override fun visitImportStatement(element: PsiImportStatement) {
+ super.visitImportStatement(element)
+ if (element.resolve() == null) {
+ reporter.report(
+ Issues.UNRESOLVED_IMPORT,
+ element,
+ "Unresolved import: `${element.qualifiedName}`"
+ )
+ }
+ }
+ })
+
var classes = (unit as? PsiClassOwner)?.classes?.toList() ?: emptyList()
if (classes.isEmpty()) {
- val uastContext = project.getComponent(UastContext::class.java)
- val uFile = uastContext.convertElementWithParent(unit, UFile::class.java) as? UFile?
+ val uFile = UastFacade.convertElementWithParent(unit, UFile::class.java) as? UFile?
classes = uFile?.classes?.map { it }?.toList() ?: emptyList()
}
var packageName: String? = null
@@ -168,12 +180,12 @@
} else {
for (psiClass in classes) {
psiClass.accept(object : JavaRecursiveElementVisitor() {
- override fun visitErrorElement(element: PsiErrorElement?) {
+ override fun visitErrorElement(element: PsiErrorElement) {
super.visitErrorElement(element)
reporter.report(
Issues.INVALID_SYNTAX,
element,
- "Syntax error: `${element?.errorDescription}`"
+ "Syntax error: `${element.errorDescription}`"
)
}
})
@@ -228,7 +240,7 @@
}
override fun dispose() {
- Disposer.dispose(project)
+ uastEnvironment.dispose()
super.dispose()
}
@@ -294,12 +306,12 @@
return packageItem
}
- fun initialize(project: Project, jarFile: File, preFiltered: Boolean = false) {
+ fun initialize(uastEnvironment: UastEnvironment, jarFile: File, preFiltered: Boolean = false) {
this.preFiltered = preFiltered
initializing = true
hideClassesFromJars = false
- this.project = project
+ this.uastEnvironment = uastEnvironment
// Find all classes referenced from the class
val facade = JavaPsiFacade.getInstance(project)
@@ -441,7 +453,7 @@
private fun createClass(clz: PsiClass): PsiClassItem {
val classItem = PsiClassItem.create(this, clz)
- if (!initializing && options.hideClasspathClasses) {
+ if (!initializing) {
// This class is found while we're no longer initializing all the source units:
// that means it must be found on the classpath instead. These should be treated
// as hidden; we don't want to generate code for them.
@@ -486,9 +498,7 @@
if (psiPackage != null) {
val packageItem = registerPackage(psiPackage, null, packageHtml, pkgName)
// Don't include packages from API that isn't directly included in the API
- if (options.hideClasspathClasses) {
- packageItem.emit = false
- }
+ packageItem.emit = false
packageItem.addClass(classItem)
}
} else {
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
index 86ef69d..4de63e1 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
@@ -36,11 +36,15 @@
import com.intellij.psi.PsiModifierListOwner
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypeParameter
+import com.intellij.psi.SyntheticElement
import com.intellij.psi.impl.source.PsiClassReferenceType
import com.intellij.psi.util.PsiUtil
import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UFile
import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.getParentOfType
import org.jetbrains.uast.kotlin.KotlinUClass
open class PsiClassItem(
@@ -84,15 +88,11 @@
}
override var stubConstructor: ConstructorItem? = null
- override var notStrippable = false
override var artifact: String? = null
private var containingClass: PsiClassItem? = null
override fun containingClass(): PsiClassItem? = containingClass
- // TODO: Come up with a better scheme for how to compute this
- override var included: Boolean = true
-
override var hasPrivateConstructor: Boolean = false
override fun interfaceTypes(): List<TypeItem> = interfaceTypes
@@ -202,7 +202,13 @@
return null
}
- return PsiCompilationUnit(codebase, containingFile)
+ val uFile =
+ if (psiClass is UClass) {
+ psiClass.getParentOfType<UFile>(UFile::class.java)
+ } else {
+ null
+ }
+ return PsiCompilationUnit(codebase, uFile, containingFile)
}
override fun finishInitialization() {
@@ -233,7 +239,7 @@
private fun initializeSuperClasses() {
val extendsListTypes = psiClass.extendsListTypes
- if (!extendsListTypes.isEmpty()) {
+ if (extendsListTypes.isNotEmpty()) {
val type = PsiTypeItem.create(codebase, extendsListTypes[0])
this.superClassType = type
this.superClass = type.asClass()
@@ -436,7 +442,15 @@
val isKotlin = isKotlin(psiClass)
if (classType == ClassType.ENUM) {
- addEnumMethods(codebase, item, psiClass, methods)
+ // In compatibility mode we want explicit valueOf and values methods.
+ // UAST recently started including these in the AST (as synthetic elements),
+ // so we no longer need to create those here, but we still need to create
+ // the synthetic constructor
+ if (compatibility.defaultEnumMethods) {
+ // Also add a private constructor; used when emitting the private API
+ val psiMethod = codebase.createConstructor("private ${psiClass.name}", psiClass)
+ methods.add(PsiConstructorItem.create(codebase, item, psiMethod))
+ }
} else if (classType == ClassType.ANNOTATION_TYPE && compatibility.explicitlyListClassRetention &&
!hasExplicitRetention(modifiers, psiClass, isKotlin)
) {
@@ -454,6 +468,11 @@
if (psiMethod.isConstructor) {
val constructor = PsiConstructorItem.create(codebase, item, psiMethod)
constructors.add(constructor)
+ } else if (classType == ClassType.ENUM &&
+ !compatibility.defaultEnumMethods &&
+ psiMethod is SyntheticElement
+ ) {
+ // skip
} else {
val method = PsiMethodItem.create(codebase, item, psiMethod)
methods.add(method)
@@ -467,7 +486,7 @@
val fields: MutableList<FieldItem> = mutableListOf()
val psiFields = psiClass.fields
- if (!psiFields.isEmpty()) {
+ if (psiFields.isNotEmpty()) {
psiFields.asSequence()
.mapTo(fields) {
PsiFieldItem.create(codebase, item, it)
@@ -504,11 +523,16 @@
continue
}
val sourcePsi = method.sourcePsi
- if (sourcePsi is KtProperty) {
+ if (sourcePsi is KtProperty || sourcePsi is KtPropertyAccessor) {
if (method.name.startsWith("set")) {
continue
}
- val name = sourcePsi.name ?: continue
+ val name =
+ when (sourcePsi) {
+ is KtProperty -> sourcePsi.name
+ is KtPropertyAccessor -> sourcePsi.property.name
+ else -> null
+ } ?: continue
val psiType = method.returnType ?: continue
properties.add(PsiPropertyItem.create(codebase, item, name, psiType, method))
}
@@ -534,47 +558,6 @@
return item
}
- private fun addEnumMethods(
- codebase: PsiBasedCodebase,
- classItem: PsiClassItem,
- psiClass: PsiClass,
- result: MutableList<PsiMethodItem>
- ) {
- // Add these two methods as overrides into the API; this isn't necessary but is done in the old
- // API generator
- // method public static android.graphics.ColorSpace.Adaptation valueOf(java.lang.String);
- // method public static final android.graphics.ColorSpace.Adaptation[] values();
-
- if (compatibility.defaultEnumMethods) {
- // TODO: Skip if we already have these methods here (but that shouldn't happen; nobody would
- // type this by hand)
- addEnumMethod(
- codebase, classItem,
- psiClass, result,
- "public static ${psiClass.qualifiedName} valueOf(java.lang.String s) { return null; }"
- )
- addEnumMethod(
- codebase, classItem,
- psiClass, result,
- "public static final ${psiClass.qualifiedName}[] values() { return null; }"
- )
- // Also add a private constructor; used when emitting the private API
- val psiMethod = codebase.createConstructor("private ${psiClass.name}", psiClass)
- result.add(PsiConstructorItem.create(codebase, classItem, psiMethod))
- }
- }
-
- private fun addEnumMethod(
- codebase: PsiBasedCodebase,
- classItem: PsiClassItem,
- psiClass: PsiClass,
- result: MutableList<PsiMethodItem>,
- source: String
- ) {
- val psiMethod = codebase.createPsiMethod(source, psiClass)
- result.add(PsiMethodItem.create(codebase, classItem, psiMethod))
- }
-
/**
* Computes the "full" class name; this is not the qualified class name (e.g. with package)
* but for an inner class it includes all the outer classes
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt
index 5c2deab..7817da9 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt
@@ -37,13 +37,31 @@
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.startOffset
+import org.jetbrains.uast.UFile
import java.util.function.Predicate
/** Whether we should limit import statements to symbols found in class docs */
private const val ONLY_IMPORT_CLASSES_REFERENCED_IN_DOCS = true
-class PsiCompilationUnit(val codebase: PsiBasedCodebase, containingFile: PsiFile) : CompilationUnit(containingFile) {
+class PsiCompilationUnit(
+ val codebase: PsiBasedCodebase,
+ uFile: UFile?,
+ containingFile: PsiFile
+) : CompilationUnit(containingFile, uFile) {
override fun getHeaderComments(): String? {
+ if (uFile != null) {
+ var comment: String? = null
+ for (uComment in uFile.allCommentsInFile) {
+ val text = uComment.text
+ comment = if (comment != null) {
+ comment + "\n" + text
+ } else {
+ text
+ }
+ }
+ return comment
+ }
+
// https://youtrack.jetbrains.com/issue/KT-22135
if (file is PsiJavaFile) {
val pkg = file.packageStatement ?: return null
@@ -100,7 +118,11 @@
}
} else if (resolved is PsiField) {
val classItem = codebase.findClass(resolved.containingClass ?: continue) ?: continue
- val fieldItem = classItem.findField(resolved.name, true, false) ?: continue
+ val fieldItem = classItem.findField(
+ resolved.name,
+ includeSuperClasses = true,
+ includeInterfaces = false
+ ) ?: continue
if (predicate.test(fieldItem)) {
imports.add(fieldItem)
}
@@ -121,7 +143,7 @@
// Next only keep those that are present in any docs; those are the only ones
// we need to import
- if (!imports.isEmpty()) {
+ if (imports.isNotEmpty()) {
val map: Multimap<String, Item> = ArrayListMultimap.create()
for (item in imports) {
if (item is ClassItem) {
@@ -134,7 +156,7 @@
// Compute set of import statements that are actually referenced
// from the documentation (we do inexact matching here; we don't
// need to have an exact set of imports since it's okay to have
- // some extras). This isn't a big problem since our codestyle
+ // some extras). This isn't a big problem since our code style
// forbids/discourages wildcards, so it shows up in fewer places,
// but we need to handle it when it does -- such as in ojluni.
@@ -143,7 +165,7 @@
val result = mutableListOf<Item>()
// We keep the wildcard imports since we don't know which ones of those are relevant
- imports.filter { it is PackageItem }.forEach { result.add(it) }
+ imports.filterIsInstance<PackageItem>().forEach { result.add(it) }
for (cls in classes(predicate)) {
cls.accept(object : ItemVisitor() {
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
index 11df5a9..69d77a5 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
@@ -112,10 +112,7 @@
val name = psiMethod.name
val commentText = javadoc(psiMethod)
val modifiers = modifiers(codebase, psiMethod, commentText)
- val parameters = psiMethod.parameterList.parameters.mapIndexed { index, parameter ->
- PsiParameterItem.create(codebase, parameter, index)
- }
-
+ val parameters = parameterList(codebase, psiMethod)
val constructor = PsiConstructorItem(
codebase = codebase,
psiMethod = psiMethod,
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt
index 1e65543..125cc05 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt
@@ -45,8 +45,10 @@
@Suppress("LeakingThis")
override var removed = documentation.contains("@removed")
+ override val synthetic = false
+
// a property with a lazily calculated default value
- inner class lazyDelegate<T>(
+ inner class LazyDelegate<T>(
val defaultValueProvider: () -> T
) : ReadWriteProperty<PsiItem, T> {
private var currentValue: T? = null
@@ -63,7 +65,7 @@
}
}
- override var originallyHidden: Boolean by lazyDelegate({
+ override var originallyHidden: Boolean by LazyDelegate {
documentation.contains('@') &&
(documentation.contains("@hide") ||
@@ -71,9 +73,9 @@
// KDoc:
documentation.contains("@suppress")) ||
modifiers.hasHideAnnotations()
- })
+ }
- override var hidden: Boolean by lazyDelegate({ originallyHidden && !modifiers.hasShowAnnotation() })
+ override var hidden: Boolean by LazyDelegate { originallyHidden && !modifiers.hasShowAnnotation() }
override fun psi(): PsiElement? = element
@@ -173,9 +175,8 @@
// Already single line?
if (documentation.indexOf('\n') == -1) {
- var end = documentation.lastIndexOf("*/")
- val s = "/**\n *" + documentation.substring(3, end) + "\n * $tagSection $commentLine\n */"
- return s
+ val end = documentation.lastIndexOf("*/")
+ return "/**\n *" + documentation.substring(3, end) + "\n * $tagSection $commentLine\n */"
}
var end = documentation.lastIndexOf("*/")
@@ -185,9 +186,9 @@
}
// The comment ends with:
// * some comment here */
- var insertNewLine: Boolean = documentation[end - 1] != '\n'
+ val insertNewLine: Boolean = documentation[end - 1] != '\n'
- var indent: String
+ val indent: String
var linePrefix = ""
val secondLine = documentation.indexOf('\n')
if (secondLine == -1) {
@@ -208,8 +209,7 @@
linePrefix = "* "
}
}
- val s = documentation.substring(0, end) + (if (insertNewLine) "\n" else "") + indent + linePrefix + tagSection + " " + commentLine + "\n" + indent + " */"
- return s
+ return documentation.substring(0, end) + (if (insertNewLine) "\n" else "") + indent + linePrefix + tagSection + " " + commentLine + "\n" + indent + " */"
}
override fun fullyQualifiedDocumentation(): String {
@@ -239,7 +239,9 @@
val comments = element.comments
if (comments.isNotEmpty()) {
val sb = StringBuilder()
- comments.asSequence().joinTo(buffer = sb, separator = "\n")
+ comments.asSequence().joinTo(buffer = sb, separator = "\n") {
+ it.text
+ }
return sb.toString()
} else {
// Temporary workaround: UAST seems to not return document nodes
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
index c337332..264dda1 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
@@ -24,8 +24,8 @@
import com.android.tools.metalava.model.ParameterItem
import com.android.tools.metalava.model.TypeItem
import com.android.tools.metalava.model.TypeParameterList
-import com.intellij.openapi.components.ServiceManager
import com.intellij.psi.PsiAnnotationMethod
+import com.intellij.psi.PsiArrayType
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTypesUtil
@@ -33,13 +33,14 @@
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.uast.UClass
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.UThrowExpression
import org.jetbrains.uast.UTryExpression
-import org.jetbrains.uast.UastContext
+import org.jetbrains.uast.UastFacade
import org.jetbrains.uast.getParentOfType
import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
import org.jetbrains.uast.visitor.AbstractUastVisitor
@@ -107,6 +108,8 @@
override fun parameters(): List<ParameterItem> = parameters
+ override val synthetic: Boolean get() = isEnumSyntheticMethod()
+
private var superMethods: List<MethodItem>? = null
override fun superMethods(): List<MethodItem> {
if (superMethods == null) {
@@ -166,7 +169,9 @@
}
override fun isKotlinProperty(): Boolean {
- return psiMethod is KotlinUMethod && psiMethod.sourcePsi is KtProperty
+ return psiMethod is KotlinUMethod && (
+ psiMethod.sourcePsi is KtProperty ||
+ psiMethod.sourcePsi is KtPropertyAccessor)
}
override fun findThrownExceptions(): Set<ClassItem> {
@@ -217,10 +222,8 @@
if (psiMethod is PsiAnnotationMethod) {
val value = psiMethod.defaultValue
if (value != null) {
- if (PsiItem.isKotlin(value)) {
- val uastContext = ServiceManager.getService(value.project, UastContext::class.java)
- ?: error("UastContext not found")
- val defaultExpression: UExpression = uastContext.convertElement(
+ if (isKotlin(value)) {
+ val defaultExpression: UExpression = UastFacade.convertElement(
value, null,
UExpression::class.java
) as? UExpression ?: return ""
@@ -308,6 +311,12 @@
sb.append(", ")
}
+ val parameterModifierString = StringWriter()
+ ModifierList.write(
+ parameterModifierString, parameter.modifiers, parameter,
+ target = AnnotationTarget.SDK_STUBS_FILE
+ )
+ sb.append(parameterModifierString.toString())
sb.append(parameter.type().convertTypeString(replacementMap))
sb.append(' ')
sb.append(parameter.name())
@@ -357,17 +366,20 @@
// methods with super methods also consider this method non-final.)
modifiers.setFinal(false)
}
- val parameters =
- if (psiMethod is UMethod) {
- psiMethod.uastParameters.mapIndexed { index, parameter ->
- PsiParameterItem.create(codebase, parameter, index)
- }
- } else {
- psiMethod.parameterList.parameters.mapIndexed { index, parameter ->
- PsiParameterItem.create(codebase, parameter, index)
- }
+ val parameters = parameterList(codebase, psiMethod)
+ var psiReturnType = psiMethod.returnType
+
+ // UAST workaround: the enum synthetic methods are sometimes missing return types,
+ // see https://youtrack.jetbrains.com/issue/KT-39560
+ if (psiReturnType == null && containingClass.isEnum()) {
+ if (name == "valueOf") {
+ psiReturnType = codebase.getClassType(containingClass.psiClass)
+ } else if (name == "values") {
+ psiReturnType = PsiArrayType(codebase.getClassType(containingClass.psiClass))
}
- val returnType = codebase.getType(psiMethod.returnType!!)
+ }
+
+ val returnType = codebase.getType(psiReturnType!!)
val method = PsiMethodItem(
codebase = codebase,
psiMethod = psiMethod,
@@ -404,6 +416,21 @@
return method
}
+ internal fun parameterList(
+ codebase: PsiBasedCodebase,
+ psiMethod: PsiMethod
+ ): List<PsiParameterItem> {
+ return if (psiMethod is UMethod) {
+ psiMethod.uastParameters.mapIndexed { index, parameter ->
+ PsiParameterItem.create(codebase, parameter, index)
+ }
+ } else {
+ psiMethod.parameterList.parameters.mapIndexed { index, parameter ->
+ PsiParameterItem.create(codebase, parameter, index)
+ }
+ }
+ }
+
private fun throwsTypes(codebase: PsiBasedCodebase, psiMethod: PsiMethod): List<ClassItem> {
val interfaces = psiMethod.throwsList.referencedTypes
if (interfaces.isEmpty()) {
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
index bd88e3a..faa7292 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
@@ -19,27 +19,30 @@
import com.android.tools.metalava.ANDROIDX_VISIBLE_FOR_TESTING
import com.android.tools.metalava.ANDROID_SUPPORT_VISIBLE_FOR_TESTING
import com.android.tools.metalava.ATTR_OTHERWISE
+import com.android.tools.metalava.METALAVA_SYNTHETIC_SUFFIX
import com.android.tools.metalava.model.AnnotationItem
import com.android.tools.metalava.model.Codebase
import com.android.tools.metalava.model.DefaultModifierList
import com.android.tools.metalava.model.ModifierList
import com.android.tools.metalava.model.MutableModifierList
import com.intellij.psi.PsiDocCommentOwner
-import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiModifierList
import com.intellij.psi.PsiModifierListOwner
-import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.PsiPrimitiveType
+import com.intellij.psi.PsiReferenceExpression
+import com.intellij.psi.impl.light.LightModifierList
import org.jetbrains.kotlin.asJava.elements.KtLightModifierList
import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtModifierList
import org.jetbrains.kotlin.psi.KtNamedFunction
-import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.KtPropertyAccessor
import org.jetbrains.uast.UAnnotated
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.UVariable
import org.jetbrains.uast.kotlin.KotlinNullabilityUAnnotation
+import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
class PsiModifierItem(
codebase: Codebase,
@@ -65,14 +68,11 @@
}
private fun computeFlag(element: PsiModifierListOwner, modifierList: PsiModifierList): Int {
- var visibilityFlags = if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) {
- PUBLIC
- } else if (modifierList.hasModifierProperty(PsiModifier.PROTECTED)) {
- PROTECTED
- } else if (modifierList.hasModifierProperty(PsiModifier.PRIVATE)) {
- PRIVATE
- } else {
- PACKAGE_PRIVATE
+ var visibilityFlags = when {
+ modifierList.hasModifierProperty(PsiModifier.PUBLIC) -> PUBLIC
+ modifierList.hasModifierProperty(PsiModifier.PROTECTED) -> PROTECTED
+ modifierList.hasModifierProperty(PsiModifier.PRIVATE) -> PRIVATE
+ else -> PACKAGE_PRIVATE
}
var flags = 0
if (modifierList.hasModifierProperty(PsiModifier.STATIC)) {
@@ -104,67 +104,66 @@
}
// Look for special Kotlin keywords
+ var ktModifierList: KtModifierList? = null
if (modifierList is KtLightModifierList<*>) {
- val ktModifierList = modifierList.kotlinOrigin
- if (ktModifierList != null) {
- if (ktModifierList.hasModifier(KtTokens.VARARG_KEYWORD)) {
- flags = flags or VARARG
+ ktModifierList = modifierList.kotlinOrigin
+ } else if (modifierList is LightModifierList && element is KotlinUMethod) {
+ ktModifierList = element.sourcePsi?.modifierList
+ }
+ if (ktModifierList != null) {
+ if (ktModifierList.hasModifier(KtTokens.VARARG_KEYWORD)) {
+ flags = flags or VARARG
+ }
+ if (ktModifierList.hasModifier(KtTokens.SEALED_KEYWORD)) {
+ flags = flags or SEALED
+ }
+ if (ktModifierList.hasModifier(KtTokens.INTERNAL_KEYWORD)) {
+ // Also remove public flag which at the UAST levels it promotes these
+ // methods to, e.g. "internal myVar" gets turned into
+ // public final boolean getMyHiddenVar$lintWithKotlin()
+ visibilityFlags = INTERNAL
+ }
+ if (ktModifierList.hasModifier(KtTokens.INFIX_KEYWORD)) {
+ flags = flags or INFIX
+ }
+ if (ktModifierList.hasModifier(KtTokens.CONST_KEYWORD)) {
+ flags = flags or CONST
+ }
+ if (ktModifierList.hasModifier(KtTokens.OPERATOR_KEYWORD)) {
+ flags = flags or OPERATOR
+ }
+ if (ktModifierList.hasModifier(KtTokens.INLINE_KEYWORD)) {
+ flags = flags or INLINE
+
+ // Workaround for b/117565118:
+ val func = (element as? UMethod)?.sourcePsi as? KtNamedFunction
+ if (func != null &&
+ (func.typeParameterList?.text ?: "").contains("reified") &&
+ !ktModifierList.hasModifier(KtTokens.PRIVATE_KEYWORD) &&
+ !ktModifierList.hasModifier(KtTokens.INTERNAL_KEYWORD)
+ ) {
+ // Switch back from private to public
+ visibilityFlags = PUBLIC
}
- if (ktModifierList.hasModifier(KtTokens.SEALED_KEYWORD)) {
- flags = flags or SEALED
- }
- if (ktModifierList.hasModifier(KtTokens.INTERNAL_KEYWORD)) {
- // Also remove public flag which at the UAST levels it promotes these
- // methods to, e.g. "internal myVar" gets turned into
- // public final boolean getMyHiddenVar$lintWithKotlin()
+ }
+ if (ktModifierList.hasModifier(KtTokens.SUSPEND_KEYWORD)) {
+ flags = flags or SUSPEND
+ }
+ if (ktModifierList.hasModifier(KtTokens.COMPANION_KEYWORD)) {
+ flags = flags or COMPANION
+ }
+ } else {
+ // UAST returns a null modifierList.kotlinOrigin for get/set methods for
+ // properties
+ if (element is UMethod &&
+ (
+ element.sourceElement is KtPropertyAccessor
+ )
+ ) {
+ // If the name contains the marker of an internal method, mark it internal
+ if (element.name.endsWith("\$$METALAVA_SYNTHETIC_SUFFIX")) {
visibilityFlags = INTERNAL
}
- if (ktModifierList.hasModifier(KtTokens.INFIX_KEYWORD)) {
- flags = flags or INFIX
- }
- if (ktModifierList.hasModifier(KtTokens.CONST_KEYWORD)) {
- flags = flags or CONST
- }
- if (ktModifierList.hasModifier(KtTokens.OPERATOR_KEYWORD)) {
- flags = flags or OPERATOR
- }
- if (ktModifierList.hasModifier(KtTokens.INLINE_KEYWORD)) {
- flags = flags or INLINE
-
- // Workaround for b/117565118:
- if (element is PsiMethod) {
- val t =
- ((element as? UMethod)?.sourcePsi as? KtNamedFunction)?.typeParameterList?.text ?: ""
- if (t.contains("reified") &&
- !ktModifierList.hasModifier(KtTokens.PRIVATE_KEYWORD) &&
- !ktModifierList.hasModifier(KtTokens.INTERNAL_KEYWORD)
- ) {
- // Switch back from private to public
- visibilityFlags = PUBLIC
- }
- }
- }
- if (ktModifierList.hasModifier(KtTokens.SUSPEND_KEYWORD)) {
- flags = flags or SUSPEND
-
- // Workaround for b/117565118:
- if (!ktModifierList.hasModifier(KtTokens.INTERNAL_KEYWORD)) {
- // Switch back from private to public
- visibilityFlags = PUBLIC
- }
- }
- if (ktModifierList.hasModifier(KtTokens.COMPANION_KEYWORD)) {
- flags = flags or COMPANION
- }
- } else {
- // UAST returns a null modifierList.kotlinOrigin for get/set methods for
- // properties
- if (element is UMethod && element.sourceElement is KtProperty) {
- // If the name contains the marker of an internal method, mark it internal
- if (element.name.endsWith("\$lintWithKotlin")) {
- visibilityFlags = INTERNAL
- }
- }
}
}
@@ -184,7 +183,8 @@
PsiModifierItem(codebase, flags)
} else {
val annotations: MutableList<AnnotationItem> =
- psiAnnotations.map {
+ // psi sometimes returns duplicate annotations, using distint() to counter that.
+ psiAnnotations.distinct().map {
val qualifiedName = it.qualifiedName
// Consider also supporting com.android.internal.annotations.VisibleForTesting?
if (qualifiedName == ANDROIDX_VISIBLE_FOR_TESTING ||
@@ -211,11 +211,11 @@
): PsiModifierItem {
val modifierList = element.modifierList ?: return PsiModifierItem(codebase)
var flags = computeFlag(element, modifierList)
- val uAnnotations = annotated.annotations
+ val uAnnotations = annotated.uAnnotations
return if (uAnnotations.isEmpty()) {
val psiAnnotations = modifierList.annotations
- if (!psiAnnotations.isEmpty()) {
+ if (psiAnnotations.isNotEmpty()) {
val annotations: MutableList<AnnotationItem> =
psiAnnotations.map { PsiAnnotationItem.create(codebase, it) }.toMutableList()
PsiModifierItem(codebase, flags, annotations)
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
index f8dab13..de137d8 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
@@ -20,14 +20,12 @@
import com.android.tools.metalava.model.ParameterItem
import com.android.tools.metalava.model.TypeItem
import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToSource
-import com.intellij.openapi.components.ServiceManager
import com.intellij.psi.PsiParameter
import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtParameter
-import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.UastContext
+import org.jetbrains.uast.UastFacade
import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
class PsiParameterItem(
@@ -116,6 +114,8 @@
return null
}
+ override val synthetic: Boolean get() = containingMethod.isEnumSyntheticMethod()
+
private var defaultValue: String? = null
override fun defaultValue(): String? {
@@ -134,14 +134,12 @@
return defaultValue.text
}
- val uastContext = ServiceManager.getService(psiParameter.project, UastContext::class.java)
- ?: error("UastContext not found")
- val defaultExpression: UExpression = uastContext.convertElement(
+ val defaultExpression: UExpression = UastFacade.convertElement(
defaultValue, null,
UExpression::class.java
) as? UExpression ?: return INVALID_VALUE
val constant = defaultExpression.evaluate()
- return if (constant != null && constant !is kotlin.Pair<*, *>) {
+ return if (constant != null && constant !is Pair<*, *>) {
constantToSource(constant)
} else {
// Expression: Compute from UAST rather than just using the source text
@@ -188,7 +186,7 @@
psiParameter: PsiParameter,
parameterIndex: Int
): PsiParameterItem {
- val name = psiParameter.name ?: "arg${psiParameter.parameterIndex() + 1}"
+ val name = psiParameter.name
val commentText = "" // no javadocs on individual parameters
val modifiers = modifiers(codebase, psiParameter, commentText)
val type = codebase.getType(psiParameter.type)
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt
index ead3577..5dc96a2 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt
@@ -17,12 +17,14 @@
package com.android.tools.metalava.model.psi
import com.android.tools.lint.detector.api.getInternalName
+import com.android.tools.metalava.JAVA_LANG_STRING
import com.android.tools.metalava.compatibility
import com.android.tools.metalava.model.AnnotationItem
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.MethodItem
+import com.android.tools.metalava.model.ParameterItem
import com.android.tools.metalava.model.TypeItem
import com.android.tools.metalava.model.TypeParameterItem
import com.intellij.psi.JavaTokenType
@@ -50,7 +52,6 @@
import com.intellij.psi.PsiWildcardType
import com.intellij.psi.util.PsiTypesUtil
import com.intellij.psi.util.TypeConversionUtil
-import org.jetbrains.kotlin.asJava.elements.KtLightTypeParameter
import java.util.function.Predicate
/** Represents a type backed by PSI */
@@ -246,7 +247,7 @@
val classes = mutableListOf<ClassItem>()
psiType.accept(object : PsiTypeVisitor<PsiType>() {
- override fun visitType(type: PsiType?): PsiType? {
+ override fun visitType(type: PsiType): PsiType {
return type
}
@@ -365,9 +366,7 @@
return false
}
- val psiType = TypeConversionUtil.erasure(type)
-
- when (psiType) {
+ when (val psiType = TypeConversionUtil.erasure(type)) {
is PsiArrayType -> {
buffer.append('[')
appendJvmSignature(buffer, psiType.componentType)
@@ -420,7 +419,15 @@
val typeString =
if (kotlinStyleNulls && (innerAnnotations || outerAnnotations)) {
try {
- getCanonicalText(codebase, context, type, true, true, kotlinStyleNulls, filter)
+ getCanonicalText(
+ codebase = codebase,
+ owner = context,
+ type = type,
+ annotated = true,
+ mapAnnotations = true,
+ kotlinStyleNulls = kotlinStyleNulls,
+ filter = filter
+ )
} catch (ignore: Throwable) {
type.canonicalText
}
@@ -460,12 +467,23 @@
if (implicitNullness == false &&
owner is MethodItem &&
- owner.containingClass().isAnnotationType() &&
+ (owner.containingClass().isAnnotationType() ||
+ owner.containingClass().isEnum() && owner.name() == "values") &&
type is PsiArrayType
) {
// For arrays in annotations not only is the method itself non null but so
// is the component type
- type.componentType.annotate(provider).createArrayType().annotate(provider)
+ type.componentType.annotate(provider).createArrayType()
+ .annotate(provider)
+ } else if (implicitNullness == false &&
+ owner is ParameterItem &&
+ owner.containingMethod().isEnumSyntheticMethod()
+ ) {
+ // Workaround the fact that the Kotlin synthetic enum methods
+ // do not have nullness information; this must be the parameter
+ // to the valueOf(String) method.
+ // See https://youtrack.jetbrains.com/issue/KT-39667.
+ return JAVA_LANG_STRING
} else {
type.annotate(provider)
}
@@ -534,7 +552,7 @@
sb.append(">")
return
} else if (element is PsiTypeParameter) {
- if (element is KtLightTypeParameter && element.kotlinOrigin.text.startsWith("reified")) {
+ if (PsiTypeParameterItem.isReified(element)) {
sb.append("reified ")
}
sb.append(element.name)
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeParameterItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeParameterItem.kt
index 91d27ee..de15c4a 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeParameterItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeParameterItem.kt
@@ -20,6 +20,7 @@
import com.android.tools.metalava.model.TypeParameterItem
import com.android.tools.metalava.model.psi.ClassType.TYPE_PARAMETER
import com.intellij.psi.PsiTypeParameter
+import org.jetbrains.kotlin.asJava.elements.KotlinLightTypeParameterBuilder
import org.jetbrains.kotlin.asJava.elements.KtLightTypeParameter
class PsiTypeParameterItem(
@@ -42,7 +43,7 @@
override fun bounds(): List<ClassItem> = bounds
override fun isReified(): Boolean {
- return element is KtLightTypeParameter && element.kotlinOrigin.text.startsWith("reified")
+ return isReified(element as? PsiTypeParameter)
}
private lateinit var bounds: List<ClassItem>
@@ -75,5 +76,18 @@
item.initialize(emptyList(), emptyList(), emptyList(), emptyList(), emptyList())
return item
}
+
+ fun isReified(element: PsiTypeParameter?): Boolean {
+ element ?: return false
+ if (element is KtLightTypeParameter &&
+ element.kotlinOrigin.text.startsWith("reified")) {
+ return true
+ } else if (element is KotlinLightTypeParameterBuilder) {
+ if (element.sourcePsi.text.startsWith("reified")) {
+ return true
+ }
+ }
+ return false
+ }
}
}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiTypePrinter.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiTypePrinter.kt
index aefe2e0..4966673 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/PsiTypePrinter.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiTypePrinter.kt
@@ -54,7 +54,6 @@
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil
import com.intellij.psi.util.PsiUtilCore
-import java.util.Arrays
import java.util.function.Predicate
/**
@@ -138,7 +137,7 @@
return getCanonicalText(type.wildcard, elementAnnotations)
is PsiDisjunctionType ->
// Based on PsiDisjunctionType.getCanonicalText(true)
- return StringUtil.join<PsiType>(type.disjunctions, { psiType ->
+ return StringUtil.join(type.disjunctions, { psiType ->
getCanonicalText(
psiType,
elementAnnotations
@@ -267,7 +266,7 @@
tailStart = prefix.length + 1
}
- appendAnnotations(sb, Arrays.asList(*annotations), elementAnnotations)
+ appendAnnotations(sb, listOf(*annotations), elementAnnotations)
sb.append(text, tailStart, text.length)
@@ -300,8 +299,7 @@
allowKotlinSuffix: Boolean
): String {
var remaining = annotations
- val kind = reference.getKindEnum(containingFile)
- when (kind) {
+ when (val kind = reference.getKindEnum(containingFile)) {
PsiJavaCodeReferenceElementImpl.Kind.CLASS_NAME_KIND,
PsiJavaCodeReferenceElementImpl.Kind.CLASS_OR_PACKAGE_NAME_KIND,
PsiJavaCodeReferenceElementImpl.Kind.CLASS_IN_QUALIFIED_NEW_KIND -> {
@@ -312,8 +310,7 @@
false,
PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE
)
- val target = if (results.size == 1) results[0].element else null
- when (target) {
+ when (val target = if (results.size == 1) results[0].element else null) {
is PsiClass -> {
val buffer = StringBuilder()
val qualifier = reference.qualifier
@@ -339,7 +336,7 @@
buffer.append('.')
}
- val list = if (remaining != null) Arrays.asList(*remaining) else getAnnotations(reference)
+ val list = if (remaining != null) listOf(*remaining) else getAnnotations(reference)
appendAnnotations(buffer, list, elementAnnotations)
buffer.append(target.name)
@@ -460,8 +457,7 @@
private fun getOuterClassRef(ref: String): String {
var stack = 0
for (i in ref.length - 1 downTo 0) {
- val c = ref[i]
- when (c) {
+ when (ref[i]) {
'<' -> stack--
'>' -> stack++
'.' -> if (stack == 0) return ref.substring(0, i)
@@ -478,7 +474,7 @@
annotations: Array<PsiAnnotation>,
elementAnnotations: List<AnnotationItem>?
): Boolean {
- return appendAnnotations(sb, Arrays.asList(*annotations), elementAnnotations)
+ return appendAnnotations(sb, listOf(*annotations), elementAnnotations)
}
private fun mapAnnotation(qualifiedName: String?): String? {
diff --git a/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt
index b5037dd..a2c6f00 100644
--- a/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt
@@ -101,6 +101,15 @@
return attributes!!
}
+ override fun targets(): Set<AnnotationTarget> {
+ if (targets == null) {
+ targets = AnnotationItem.computeTargets(this) { className ->
+ codebase.findOrCreateClass(className)
+ }
+ }
+ return targets!!
+ }
+
companion object {
fun create(codebase: PsiBasedCodebase, uAnnotation: UAnnotation, qualifiedName: String? = uAnnotation.qualifiedName): UAnnotationItem {
return UAnnotationItem(codebase, uAnnotation, qualifiedName)
@@ -183,8 +192,7 @@
null -> sb.append("null")
is ULiteralExpression -> sb.append(CodePrinter.constantToSource(value.value))
is UReferenceExpression -> {
- val resolved = value.resolve()
- when (resolved) {
+ when (val resolved = value.resolve()) {
is PsiField -> {
val containing = resolved.containingClass
if (containing != null) {
@@ -321,8 +329,7 @@
override fun resolve(): Item? {
if (psiValue is UReferenceExpression) {
- val resolved = psiValue.resolve()
- when (resolved) {
+ when (val resolved = psiValue.resolve()) {
is PsiField -> return codebase.findField(resolved)
is PsiClass -> return codebase.findOrCreateClass(resolved)
is PsiMethod -> return codebase.findMethod(resolved)
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java b/src/main/java/com/android/tools/metalava/model/text/ApiFile.java
similarity index 97%
rename from src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
rename to src/main/java/com/android/tools/metalava/model/text/ApiFile.java
index f91ce7f..c89a9c7 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
+++ b/src/main/java/com/android/tools/metalava/model/text/ApiFile.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2011 Google Inc.
+ * 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
+ * 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,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tools.metalava.doclava1;
+package com.android.tools.metalava.model.text;
import com.android.tools.lint.checks.infrastructure.ClassNameKt;
import com.android.tools.metalava.FileFormat;
@@ -22,17 +22,6 @@
import com.android.tools.metalava.model.DefaultModifierList;
import com.android.tools.metalava.model.TypeParameterList;
import com.android.tools.metalava.model.VisibilityLevel;
-import com.android.tools.metalava.model.text.TextClassItem;
-import com.android.tools.metalava.model.text.TextConstructorItem;
-import com.android.tools.metalava.model.text.TextFieldItem;
-import com.android.tools.metalava.model.text.TextMethodItem;
-import com.android.tools.metalava.model.text.TextModifiers;
-import com.android.tools.metalava.model.text.TextPackageItem;
-import com.android.tools.metalava.model.text.TextParameterItem;
-import com.android.tools.metalava.model.text.TextParameterItemKt;
-import com.android.tools.metalava.model.text.TextPropertyItem;
-import com.android.tools.metalava.model.text.TextTypeItem;
-import com.android.tools.metalava.model.text.TextTypeParameterList;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.Files;
import kotlin.Pair;
@@ -210,7 +199,7 @@
// If the same package showed up multiple times, make sure they have the same modifiers.
// (Packages can't have public/private/etc, but they can have annotations, which are part of ModifierList.)
- // ModifierList doesn't provide equals(), neither does AnnotationItem which ModifilerList contains,
+ // ModifierList doesn't provide equals(), neither does AnnotationItem which ModifierList contains,
// so we just use toString() here for equality comparison.
// However, ModifierList.toString() throws if the owner is not yet set, so we have to instantiate an
// (owner) TextPackageItem here.
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.kt b/src/main/java/com/android/tools/metalava/model/text/ApiParseException.kt
similarity index 90%
rename from src/main/java/com/android/tools/metalava/doclava1/ApiParseException.kt
rename to src/main/java/com/android/tools/metalava/model/text/ApiParseException.kt
index 3a95605..47151d8 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/ApiParseException.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2010 Google Inc.
+ * 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
+ * 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,
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.metalava.doclava1
+package com.android.tools.metalava.model.text
class ApiParseException : Exception {
private var file: String? = null
diff --git a/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.kt b/src/main/java/com/android/tools/metalava/model/text/SourcePositionInfo.kt
similarity index 85%
rename from src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.kt
rename to src/main/java/com/android/tools/metalava/model/text/SourcePositionInfo.kt
index 29f770b..c81dc3b 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/SourcePositionInfo.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.tools.metalava.doclava1
+package com.android.tools.metalava.model.text
class SourcePositionInfo(
private val file: String,
@@ -29,7 +29,10 @@
}
companion object {
- val UNKNOWN = SourcePositionInfo("(unknown)", 0)
+ val UNKNOWN = SourcePositionInfo(
+ "(unknown)",
+ 0
+ )
/**
* Given this position and str which occurs at that position, as well as str an index into str,
@@ -54,7 +57,10 @@
}
prev = c
}
- return SourcePositionInfo(that.file, line)
+ return SourcePositionInfo(
+ that.file,
+ line
+ )
}
}
}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
index a3c1aa3..28d1e59 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
@@ -16,8 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.SourcePositionInfo
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.AnnotationRetention
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.ConstructorItem
@@ -57,8 +55,6 @@
override val isTypeParameter: Boolean = false
- override var notStrippable = false
-
override var artifact: String? = null
override fun equals(other: Any?): Boolean {
@@ -77,7 +73,7 @@
return interfaceTypes.asSequence().map { it.asClass() }.filterNotNull()
}
- private var innerClasses: List<ClassItem> = mutableListOf()
+ private var innerClasses: MutableList<ClassItem> = mutableListOf()
override var stubConstructor: ConstructorItem? = null
@@ -224,7 +220,7 @@
}
fun addInnerClass(cls: TextClassItem) {
- innerClasses += cls
+ innerClasses.add(cls)
}
override fun filteredSuperClassType(predicate: Predicate<Item>): TypeItem? {
diff --git a/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt b/src/main/java/com/android/tools/metalava/model/text/TextCodebase.kt
similarity index 95%
rename from src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt
rename to src/main/java/com/android/tools/metalava/model/text/TextCodebase.kt
index f959212..1d8f9a9 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextCodebase.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2010 Google Inc.
+ * 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
+ * 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,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.tools.metalava.doclava1
+package com.android.tools.metalava.model.text
import com.android.tools.metalava.ApiType
import com.android.tools.metalava.CodebaseComparator
@@ -38,21 +38,13 @@
import com.android.tools.metalava.model.PackageList
import com.android.tools.metalava.model.PropertyItem
import com.android.tools.metalava.model.TypeParameterList
-import com.android.tools.metalava.model.text.TextBackedAnnotationItem
-import com.android.tools.metalava.model.text.TextClassItem
-import com.android.tools.metalava.model.text.TextConstructorItem
-import com.android.tools.metalava.model.text.TextFieldItem
-import com.android.tools.metalava.model.text.TextMethodItem
-import com.android.tools.metalava.model.text.TextModifiers
-import com.android.tools.metalava.model.text.TextPackageItem
-import com.android.tools.metalava.model.text.TextPropertyItem
-import com.android.tools.metalava.model.text.TextTypeItem
import com.android.tools.metalava.model.visitors.ItemVisitor
import com.android.tools.metalava.model.visitors.TypeVisitor
import java.io.File
import java.util.ArrayList
import java.util.HashMap
import java.util.function.Predicate
+import kotlin.math.min
// Copy of ApiInfo in doclava1 (converted to Kotlin + some cleanup to make it work with metalava's data structures.
// (Converted to Kotlin such that I can inherit behavior via interfaces, in particular Codebase.)
@@ -168,7 +160,7 @@
private fun resolveThrowsClasses(methodItem: MethodItem) {
val methodInfo = methodItem as TextMethodItem
val names = methodInfo.throwsTypeNames()
- if (!names.isEmpty()) {
+ if (names.isNotEmpty()) {
val result = ArrayList<TextClassItem>()
for (exception in names) {
var exceptionClass: TextClassItem? = mAllClasses[exception]
@@ -344,7 +336,8 @@
includeFieldsInApiDiff: Boolean = compatibility.includeFieldsInApiDiff
): TextCodebase {
// Compute just the delta
- val delta = TextCodebase(baseFile)
+ val delta =
+ TextCodebase(baseFile)
delta.description = "Delta between $baseApi and $signatureApi"
CodebaseComparator().compare(object : ComparisonVisitor() {
@@ -461,7 +454,7 @@
val typeEnd =
if (array != -1) {
if (generics != -1) {
- Math.min(array, generics)
+ min(array, generics)
} else {
array
}
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
index ce2eefc..d854695 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
@@ -16,8 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.SourcePositionInfo
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.ConstructorItem
class TextConstructorItem(
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt
index 754b859..7358a76 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt
@@ -16,8 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.SourcePositionInfo
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.FieldItem
import com.android.tools.metalava.model.TypeItem
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextItem.kt
index 1d870d8..7e9b015 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextItem.kt
@@ -16,8 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.SourcePositionInfo
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.DefaultItem
import com.android.tools.metalava.model.MutableModifierList
@@ -29,6 +27,7 @@
override var modifiers: TextModifiers
) : DefaultItem() {
+ override val synthetic = false
override var originallyHidden = false
override var hidden = false
override var removed = false
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt
index a2d7f53..7e3003c 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt
@@ -16,8 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.SourcePositionInfo
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.MemberItem
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
index a1a38d2..30be82a 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
@@ -17,8 +17,6 @@
package com.android.tools.metalava.model.text
import com.android.tools.metalava.compatibility
-import com.android.tools.metalava.doclava1.SourcePositionInfo
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.Item
import com.android.tools.metalava.model.MethodItem
@@ -64,7 +62,7 @@
return false
}
- for (i in 0 until parameters1.size) {
+ for (i in parameters1.indices) {
val parameter1 = parameters1[i]
val parameter2 = parameters2[i]
if (parameter1.type() != parameter2.type()) {
@@ -168,6 +166,8 @@
return duplicated
}
+ override val synthetic: Boolean get() = isEnumSyntheticMethod()
+
private val throwsTypes = mutableListOf<String>()
private val parameters = mutableListOf<TextParameterItem>()
private var throwsClasses: List<ClassItem>? = null
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
index 3af8594..d3bfc4d 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
@@ -16,8 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.SourcePositionInfo
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.PackageItem
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt
index 8e9099f..690db47 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt
@@ -16,8 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.SourcePositionInfo
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.ParameterItem
@@ -45,7 +43,7 @@
return type.toString().contains("...")
}
- override var included: Boolean = true
+ override val synthetic: Boolean get() = containingMethod.isEnumSyntheticMethod()
override fun type(): TextTypeItem = type
override fun name(): String = name
override fun publicName(): String? = publicName
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextPropertyItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextPropertyItem.kt
index f6fb8c9..5161a46 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextPropertyItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextPropertyItem.kt
@@ -16,8 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.SourcePositionInfo
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.FieldItem
import com.android.tools.metalava.model.PropertyItem
import com.android.tools.metalava.model.TypeItem
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt
index e2ebedc..84608e6 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt
@@ -18,7 +18,6 @@
import com.android.tools.metalava.JAVA_LANG_OBJECT
import com.android.tools.metalava.JAVA_LANG_PREFIX
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.AnnotationItem
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.Item
@@ -29,6 +28,7 @@
import com.android.tools.metalava.model.TypeParameterList
import com.android.tools.metalava.model.TypeParameterListOwner
import java.util.function.Predicate
+import kotlin.math.min
const val ASSUME_TYPE_VARS_EXTEND_OBJECT = false
@@ -354,7 +354,7 @@
val generics = type.indexOf('<')
val first = if (space != -1) {
if (generics != -1) {
- Math.min(space, generics)
+ min(space, generics)
} else {
space
}
@@ -386,7 +386,7 @@
// Sometimes we have a second type after the max, such as
// @androidx.annotation.NonNull java.lang.reflect.@androidx.annotation.NonNull TypeVariable<...>
- for (i in 0 until s.length) {
+ for (i in s.indices) {
val c = s[i]
if (Character.isJavaIdentifierPart(c) || c == '.') {
continue
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterItem.kt
index 4d3ac8f..389caa4 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterItem.kt
@@ -16,7 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.DefaultModifierList
import com.android.tools.metalava.model.TypeParameterItem
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterList.kt b/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterList.kt
index b21f1e2..d6fd800 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterList.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextTypeParameterList.kt
@@ -16,7 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.TextCodebase
import com.android.tools.metalava.model.TypeParameterItem
import com.android.tools.metalava.model.TypeParameterList
import com.android.tools.metalava.model.TypeParameterListOwner
@@ -65,7 +64,7 @@
var balance = 0
var expect = false
var start = 0
- for (i in 0 until s.length) {
+ for (i in s.indices) {
val c = s[i]
if (c == '<') {
balance++
diff --git a/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt
index 411f008..8186e08 100644
--- a/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt
+++ b/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt
@@ -16,6 +16,7 @@
package com.android.tools.metalava.model.visitors
+import com.android.tools.metalava.Options
import com.android.tools.metalava.doclava1.ApiPredicate
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.FieldItem
@@ -97,12 +98,15 @@
methodComparator: Comparator<MethodItem>? = null,
/** Comparator to sort fields with, or null to use natural (source) order */
- fieldComparator: Comparator<FieldItem>? = null
+ fieldComparator: Comparator<FieldItem>? = null,
+
+ /** Whether to include "for stub purposes" APIs. See [Options.showForStubPurposesAnnotations] */
+ includeApisForStubPurposes: Boolean = true
) : this(
visitConstructorsAsMethods, nestInnerClasses,
true, methodComparator,
fieldComparator,
- ApiPredicate(ignoreShown = ignoreShown, matchRemoved = remove),
+ ApiPredicate(ignoreShown = ignoreShown, matchRemoved = remove, includeApisForStubPurposes = includeApisForStubPurposes),
ApiPredicate(ignoreShown = true, ignoreRemoved = remove)
)
@@ -147,13 +151,11 @@
/**
* @return Whether the body of this class (everything other than the inner classes) emits anything
*/
- fun shouldEmitClassBody(vc: VisitCandidate): Boolean {
- return if (filterEmit.test(vc.cls)) {
- true
- } else if (vc.nonEmpty()) {
- filterReference.test(vc.cls)
- } else {
- false
+ private fun shouldEmitClassBody(vc: VisitCandidate): Boolean {
+ return when {
+ filterEmit.test(vc.cls) -> true
+ vc.nonEmpty() -> filterReference.test(vc.cls)
+ else -> false
}
}
@@ -167,7 +169,7 @@
/**
* @return Whether this class will emit anything
*/
- fun shouldEmitAnyClass(vc: VisitCandidate): Boolean {
+ private fun shouldEmitAnyClass(vc: VisitCandidate): Boolean {
return shouldEmitClassBody(vc) || shouldEmitInnerClasses(vc)
}
}
diff --git a/src/main/java/com/android/tools/metalava/model/visitors/VisibleItemVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/VisibleItemVisitor.kt
deleted file mode 100644
index 923e34f..0000000
--- a/src/main/java/com/android/tools/metalava/model/visitors/VisibleItemVisitor.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 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.model.visitors
-
-import com.android.tools.metalava.model.Item
-
-// TODO: Use ApiVisitor instead!
-open class VisibleItemVisitor(
- /**
- * Whether constructors should be visited as part of a [#visitMethod] call
- * instead of just a [#visitConstructor] call. Helps simplify visitors that
- * don't care to distinguish between the two cases. Defaults to true.
- */
- visitConstructorsAsMethods: Boolean = true,
- /**
- * Whether inner classes should be visited "inside" a class; when this property
- * is true, inner classes are visited before the [#afterVisitClass] method is
- * called; when false, it's done afterwards. Defaults to false.
- */
- nestInnerClasses: Boolean = false
-) : ItemVisitor(visitConstructorsAsMethods, nestInnerClasses) {
- override fun skip(item: Item): Boolean {
- return !item.included
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/stub/JavaStubWriter.kt b/src/main/java/com/android/tools/metalava/stub/JavaStubWriter.kt
index 4e2a878..f0e3aac 100644
--- a/src/main/java/com/android/tools/metalava/stub/JavaStubWriter.kt
+++ b/src/main/java/com/android/tools/metalava/stub/JavaStubWriter.kt
@@ -16,7 +16,6 @@
package com.android.tools.metalava.stub
-import com.android.tools.metalava.JAVA_LANG_STRING
import com.android.tools.metalava.compatibility
import com.android.tools.metalava.model.AnnotationTarget
import com.android.tools.metalava.model.ClassItem
@@ -28,9 +27,7 @@
import com.android.tools.metalava.model.ModifierList
import com.android.tools.metalava.model.PackageItem
import com.android.tools.metalava.model.TypeParameterList
-import com.android.tools.metalava.model.psi.EXPAND_DOCUMENTATION
import com.android.tools.metalava.model.psi.PsiClassItem
-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
@@ -53,9 +50,10 @@
writer.println("package $qualifiedName;")
writer.println()
}
-
- @Suppress("ConstantConditionIf")
- if (EXPAND_DOCUMENTATION && options.includeDocumentationInStubs) {
+ if (options.includeDocumentationInStubs) {
+ // All the classes referenced in the stubs are fully qualified, so no imports are
+ // needed. However, in some cases for javadoc, replacement with fully qualified name
+ // fails and thus we need to include imports for the stubs to compile.
val compilationUnit = cls.getCompilationUnit()
compilationUnit?.getImportStatements(filterReference)?.let {
for (item in it) {
@@ -65,7 +63,10 @@
is ClassItem ->
writer.println("import ${item.qualifiedName()};")
is MemberItem ->
- writer.println("import static ${item.containingClass().qualifiedName()}.${item.name()};")
+ writer.println(
+ "import static ${item.containingClass()
+ .qualifiedName()}.${item.name()};"
+ )
}
}
writer.println()
@@ -73,7 +74,7 @@
}
}
- appendDocumentation(cls, writer)
+ appendDocumentation(cls, writer, docStubs)
// "ALL" doesn't do it; compiler still warns unless you actually explicitly list "unchecked"
writer.println("@SuppressWarnings({\"unchecked\", \"deprecation\", \"all\"})")
@@ -95,10 +96,8 @@
writer.print(cls.simpleName())
generateTypeParameterList(typeList = cls.typeParameterList(), addSpace = false)
- generateSuperClassStatement(cls)
- if (!cls.notStrippable) {
- generateInterfaceList(cls)
- }
+ generateSuperClassDeclaration(cls)
+ generateInterfaceList(cls)
writer.print(" {\n")
if (cls.isEnum()) {
@@ -111,7 +110,7 @@
} else {
writer.write(",\n")
}
- appendDocumentation(field, writer)
+ appendDocumentation(field, writer, docStubs)
// Can't just appendModifiers(field, true, true): enum constants
// don't take modifier lists, only annotations
@@ -136,21 +135,6 @@
generateMissingConstructors(cls)
}
- 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.print("}\n\n")
}
@@ -186,7 +170,7 @@
)
}
- private fun generateSuperClassStatement(cls: ClassItem) {
+ private fun generateSuperClassDeclaration(cls: ClassItem) {
if (cls.isEnum() || cls.isAnnotationType()) {
// No extends statement for enums and annotations; it's implied by the "enum" and "@interface" keywords
return
@@ -259,9 +243,6 @@
}
override fun visitConstructor(constructor: ConstructorItem) {
- if (constructor.containingClass().notStrippable) {
- return
- }
writeConstructor(constructor, constructor.superConstructor)
}
@@ -270,7 +251,7 @@
superConstructor: MethodItem?
) {
writer.println()
- appendDocumentation(constructor, writer)
+ appendDocumentation(constructor, writer, docStubs)
appendModifiers(constructor, false)
generateTypeParameterList(
typeList = constructor.typeParameterList(),
@@ -352,9 +333,6 @@
}
override fun visitMethod(method: MethodItem) {
- if (method.containingClass().notStrippable) {
- return
- }
writeMethod(method.containingClass(), method, false)
}
@@ -363,10 +341,7 @@
val isEnum = containingClass.isEnum()
val isAnnotation = containingClass.isAnnotationType()
- if (isEnum && (method.name() == "values" ||
- method.name() == "valueOf" && method.parameters().size == 1 &&
- method.parameters()[0].type().toTypeString() == JAVA_LANG_STRING)
- ) {
+ if (method.isEnumSyntheticMethod()) {
// Skip the values() and valueOf(String) methods in enums: these are added by
// the compiler for enums anyway, but was part of the doclava1 signature files
// so inserted in compat mode.
@@ -374,7 +349,7 @@
}
writer.println()
- appendDocumentation(method, writer)
+ appendDocumentation(method, writer, docStubs)
// Need to filter out abstract from the modifiers list and turn it
// into a concrete method to make the stub compile
@@ -420,14 +395,10 @@
return
}
- if (field.containingClass().notStrippable) {
- return
- }
-
writer.println()
- appendDocumentation(field, writer)
- appendModifiers(field, false, false)
+ appendDocumentation(field, writer, docStubs)
+ appendModifiers(field, removeAbstract = false, removeFinal = false)
writer.print(
field.type().toTypeString(
outerAnnotations = false,
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..154d8ce
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/stub/KotlinStubWriter.kt
@@ -0,0 +1,302 @@
+/*
+ * 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.AnnotationTarget
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.Language
+import com.android.tools.metalava.model.MemberItem
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.ModifierList
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.model.TypeParameterList
+import com.android.tools.metalava.model.psi.PsiClassItem
+import com.android.tools.metalava.model.visitors.ItemVisitor
+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() {
+ private val annotationTarget = if (docStubs) AnnotationTarget.DOC_STUBS_FILE else AnnotationTarget.SDK_STUBS_FILE
+
+ override fun visitClass(cls: ClassItem) {
+ if (cls.isTopLevelClass()) {
+ val qualifiedName = cls.containingPackage().qualifiedName()
+ if (qualifiedName.isNotBlank()) {
+ writer.println("package $qualifiedName")
+ writer.println()
+ }
+ 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, docStubs)
+
+ writer.println("@file:Suppress(\"ALL\")")
+
+ // Need to filter out abstract from the modifiers list and turn it
+ // into a concrete method to make the stub compile
+ val removeAbstract = cls.modifiers.isAbstract() && (cls.isEnum() || cls.isAnnotationType())
+
+ appendModifiers(cls, cls.modifiers, removeAbstract)
+
+ 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())
+
+ generateTypeParameterList(typeList = cls.typeParameterList(), addSpace = false)
+ val printedSuperClass = generateSuperClassDeclaration(cls)
+ generateInterfaceList(cls, printedSuperClass)
+ writer.print(" {\n")
+ }
+
+ private fun generateTypeParameterList(
+ typeList: TypeParameterList,
+ addSpace: Boolean
+ ) {
+ val typeListString = typeList.toString()
+ if (typeListString.isNotEmpty()) {
+ writer.print(typeListString)
+
+ if (addSpace) {
+ writer.print(' ')
+ }
+ }
+ }
+
+ private fun appendModifiers(
+ item: Item,
+ modifiers: ModifierList,
+ removeAbstract: Boolean,
+ removeFinal: Boolean = false,
+ addPublic: Boolean = false
+ ) {
+ val separateLines = item is ClassItem || item is MethodItem
+
+ ModifierList.write(
+ writer, modifiers, item,
+ target = annotationTarget,
+ includeAnnotations = true,
+ skipNullnessAnnotations = true,
+ includeDeprecated = true,
+ runtimeAnnotationsOnly = !generateAnnotations,
+ removeAbstract = removeAbstract,
+ removeFinal = removeFinal,
+ addPublic = addPublic,
+ separateLines = separateLines,
+ language = Language.KOTLIN
+ )
+ }
+
+ private fun generateSuperClassDeclaration(cls: ClassItem): Boolean {
+ if (cls.isEnum() || cls.isAnnotationType()) {
+ // No extends statement for enums and annotations; it's implied by the "enum" and "@interface" keywords
+ return false
+ }
+
+ val superClass = if (preFiltered)
+ cls.superClassType()
+ else cls.filteredSuperClassType(filterReference)
+
+ if (superClass != null && !superClass.isJavaLangObject()) {
+ val qualifiedName = superClass.toTypeString() // TODO start passing language = Language.KOTLIN
+ writer.print(" : ")
+
+ if (qualifiedName.contains("<")) {
+ // TODO: push this into the model at filter-time such that clients don't need
+ // to remember to do this!!
+ val s = superClass.asClass()
+ if (s != null) {
+ val map = cls.mapTypeVariables(s)
+ val replaced = superClass.convertTypeString(map)
+ writer.print(replaced)
+ return true
+ }
+ }
+ (cls as PsiClassItem).psiClass.superClassType
+ writer.print(qualifiedName)
+ // TODO: print out arguments to the parent constructor
+ writer.print("()")
+ return true
+ }
+ return false
+ }
+
+ private fun generateInterfaceList(cls: ClassItem, printedSuperClass: Boolean) {
+ if (cls.isAnnotationType()) {
+ // No extends statement for annotations; it's implied by the "@interface" keyword
+ return
+ }
+
+ val interfaces = if (preFiltered)
+ cls.interfaceTypes().asSequence()
+ else cls.filteredInterfaceTypes(filterReference).asSequence()
+
+ if (interfaces.any()) {
+ if (printedSuperClass) {
+ writer.print(",")
+ } else {
+ writer.print(" :")
+ }
+ interfaces.forEachIndexed { index, type ->
+ if (index > 0) {
+ writer.print(",")
+ }
+ writer.print(" ")
+ writer.print(type.toTypeString()) // TODO start passing language = Language.KOTLIN
+ }
+ }
+ }
+
+ private fun writeType(
+ item: Item,
+ type: TypeItem?
+ ) {
+ type ?: return
+
+ val typeString = type.toTypeString(
+ outerAnnotations = false,
+ innerAnnotations = generateAnnotations,
+ erased = false,
+ kotlinStyleNulls = true,
+ context = item,
+ filter = filterReference
+ // TODO pass in language = Language.KOTLIN
+ )
+
+ writer.print(typeString)
+ }
+
+ override fun visitMethod(method: MethodItem) {
+ if (method.isKotlinProperty()) return // will be handled by visitProperty
+ val containingClass = method.containingClass()
+ val modifiers = method.modifiers
+ val isEnum = containingClass.isEnum()
+ val isAnnotation = containingClass.isAnnotationType()
+
+ writer.println()
+ appendDocumentation(method, writer, docStubs)
+
+ // TODO: Should be an annotation
+ generateThrowsList(method)
+
+ // Need to filter out abstract from the modifiers list and turn it
+ // into a concrete method to make the stub compile
+ val removeAbstract = modifiers.isAbstract() && (isEnum || isAnnotation)
+
+ appendModifiers(method, modifiers, removeAbstract, false)
+ generateTypeParameterList(typeList = method.typeParameterList(), addSpace = true)
+
+ writer.print("fun ")
+ writer.print(method.name())
+ generateParameterList(method)
+
+ writer.print(": ")
+ val returnType = method.returnType()
+ writeType(method, returnType)
+
+ if (isAnnotation) {
+ val default = method.defaultValue()
+ if (default.isNotEmpty()) {
+ writer.print(" default ")
+ writer.print(default)
+ }
+ }
+
+ if (modifiers.isAbstract() && !isEnum || isAnnotation || modifiers.isNative()) {
+ // do nothing
+ } else {
+ writer.print(" = ")
+ writeThrowStub()
+ }
+ writer.println()
+ }
+
+ override fun afterVisitClass(cls: ClassItem) {
+ writer.println("}\n\n")
+ }
+
+ private fun writeThrowStub() {
+ writer.write("error(\"Stub!\")")
+ }
+
+ private fun generateParameterList(method: MethodItem) {
+ writer.print("(")
+ method.parameters().asSequence().forEachIndexed { i, parameter ->
+ if (i > 0) {
+ writer.print(", ")
+ }
+ appendModifiers(
+ parameter,
+ parameter.modifiers,
+ removeAbstract = false,
+ removeFinal = false,
+ addPublic = false
+ )
+ val name = parameter.publicName() ?: parameter.name()
+ writer.print(name)
+ writer.print(": ")
+ writeType(method, parameter.type())
+ }
+ writer.print(")")
+ }
+
+ private fun generateThrowsList(method: MethodItem) {
+ // Note that throws types are already sorted internally to help comparison matching
+ val throws = if (preFiltered) {
+ method.throwsTypes().asSequence()
+ } else {
+ method.filteredThrowsTypes(filterReference).asSequence()
+ }
+ if (throws.any()) {
+ writer.print("@Throws(")
+ throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type ->
+ if (i > 0) {
+ writer.print(",")
+ }
+ writer.print(type.qualifiedName())
+ writer.print("::class")
+ }
+ writer.print(")")
+ }
+ }
+}
\ 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..fd148fc 100644
--- a/src/main/java/com/android/tools/metalava/stub/StubWriter.kt
+++ b/src/main/java/com/android/tools/metalava/stub/StubWriter.kt
@@ -28,7 +28,6 @@
import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.ModifierList
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.ApiVisitor
import com.android.tools.metalava.model.visitors.ItemVisitor
@@ -54,11 +53,7 @@
// Methods are by default sorted in source order in stubs, to encourage methods
// that are near each other in the source to show up near each other in the documentation
methodComparator = MethodItem.sourceOrderComparator,
- filterEmit = FilterPredicate(ApiPredicate(ignoreShown = true, includeDocOnly = docStubs))
- // In stubs we have to include non-strippable things too. This is an error in the API,
- // and we've removed all of it from the framework, but there are libraries which still
- // have reference errors.
- .or { it is ClassItem && it.notStrippable },
+ filterEmit = FilterPredicate(ApiPredicate(ignoreShown = true, includeDocOnly = docStubs)),
filterReference = ApiPredicate(ignoreShown = true, includeDocOnly = docStubs),
includeEmptyOuterClasses = true
) {
@@ -131,7 +126,7 @@
}
startFile(sourceFile)
- appendDocumentation(pkg, packageInfoWriter)
+ appendDocumentation(pkg, packageInfoWriter, docStubs)
if (annotations.isNotEmpty()) {
ModifierList.writeAnnotations(
@@ -194,7 +189,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()
@@ -203,21 +202,6 @@
stubWriter?.visitClass(cls)
}
- 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) {
stubWriter?.afterVisitClass(cls)
@@ -252,4 +236,23 @@
override fun afterVisitField(field: FieldItem) {
stubWriter?.afterVisitField(field)
}
+}
+
+internal fun appendDocumentation(
+ item: Item,
+ writer: PrintWriter,
+ docStubs: Boolean
+) {
+ if (options.includeDocumentationInStubs || docStubs) {
+ val documentation = if (docStubs) {
+ item.fullyQualifiedDocumentation()
+ } else {
+ item.documentation
+ }
+ if (documentation.isNotBlank()) {
+ val trimmed = trimDocIndent(documentation)
+ writer.println(trimmed)
+ writer.println()
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties
index dd4d4b5..62a299c 100644
--- a/src/main/resources/version.properties
+++ b/src/main/resources/version.properties
@@ -2,4 +2,4 @@
# Version definition
# This file is read by gradle build scripts, but also packaged with metalava
# as a resource for the Version classes to read.
-metalavaVersion=1.3.0
+metalavaVersion=1.0.0-alpha01
diff --git a/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt b/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt
index 9fcefe6..07fd835 100644
--- a/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt
+++ b/src/test/java/com/android/tools/metalava/AndroidApiChecksTest.kt
@@ -249,7 +249,8 @@
}
"""
),
- intDefAnnotationSource
+ intDefAnnotationSource,
+ nullableSource
)
)
}
diff --git a/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt
index 3821744..1b09cf4 100644
--- a/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt
+++ b/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt
@@ -16,7 +16,7 @@
package com.android.tools.metalava
-import com.android.tools.metalava.doclava1.ApiFile
+import com.android.tools.metalava.model.text.ApiFile
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
diff --git a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
index 3ddb8e1..751588e 100644
--- a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
+++ b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
@@ -185,7 +185,9 @@
Appendable append(CharSequence csq) throws IOException;
}
"""
- )
+ ),
+ libcoreNonNullSource,
+ libcoreNullableSource
),
compatibilityMode = false,
outputKotlinStyleNulls = false,
@@ -206,7 +208,10 @@
method @androidx.annotation.NonNull public test.pkg.Appendable append(@androidx.annotation.Nullable java.lang.CharSequence);
}
}
- """
+ """,
+ extraArguments = arrayOf(
+ ARG_HIDE_PACKAGE, "libcore.util"
+ )
)
}
@@ -234,7 +239,9 @@
void foo();
}
"""
- )
+ ),
+ libcoreNonNullSource,
+ libcoreNullableSource
),
compatibilityMode = false,
outputKotlinStyleNulls = false,
@@ -273,7 +280,10 @@
method public void foo();
}
}
- """
+ """,
+ extraArguments = arrayOf(
+ ARG_HIDE_PACKAGE, "libcore.util"
+ )
)
}
@@ -335,7 +345,9 @@
public class PublicClass extends HiddenSuperClass {
}
"""
- )
+ ),
+ libcoreNonNullSource,
+ libcoreNullableSource
),
compatibilityMode = false,
outputKotlinStyleNulls = false,
@@ -357,14 +369,15 @@
method @androidx.annotation.NonNull public java.lang.String publicMethod(@androidx.annotation.Nullable java.lang.Object);
}
}
- """
+ """,
+ extraArguments = arrayOf(ARG_HIDE_PACKAGE, "libcore.util")
)
}
@Test
fun `Merge inclusion annotations from Java stub files`() {
check(
- expectedIssues = "src/test/pkg/Example.annotated.java:6: error: @test.annotation.Show APIs must also be marked @hide: method test.pkg.Example.cShown() [UnhiddenSystemApi]",
+ expectedIssues = "",
sourceFiles = arrayOf(
java(
"src/test/pkg/Example.annotated.java",
diff --git a/src/test/java/com/android/tools/metalava/ApiAnalyzerTest.kt b/src/test/java/com/android/tools/metalava/ApiAnalyzerTest.kt
new file mode 100644
index 0000000..49c454a
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/ApiAnalyzerTest.kt
@@ -0,0 +1,42 @@
+/*
+ * 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
+
+import org.junit.Test
+
+class ApiAnalyzerTest : DriverTest() {
+ @Test
+ fun `private companion object inside an interface`() {
+ check(
+ compatibilityMode = false,
+ expectedIssues = """
+ src/test/pkg/MyInterface.kt:4: error: Do not use private companion objects inside interfaces as these become public if targeting Java 8 or older. [PrivateCompanion]
+ """,
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ package test.pkg
+
+ interface MyInterface {
+ private companion object
+ }
+ """
+ )
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
index 9735d43..6684e73 100644
--- a/src/test/java/com/android/tools/metalava/ApiFileTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
@@ -18,6 +18,7 @@
package com.android.tools.metalava
+import com.android.tools.lint.checks.infrastructure.TestFiles.base64gzip
import org.junit.Test
class ApiFileTest : DriverTest() {
@@ -203,7 +204,7 @@
method public void method1(int p = 42, Integer? int2 = null, int p1 = 42, String str = "hello world", java.lang.String... args);
method public void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE);
method public void method3(String str, int p, int int2 = double(int) + str.length);
- field public static final test.pkg.Foo.Companion! Companion;
+ field public static final test.pkg.Foo.Companion Companion;
}
public static final class Foo.Companion {
method public int double(int p);
@@ -281,7 +282,7 @@
// Signature format: 3.0
package androidx.core.util {
public final class TestKt {
- method public static inline <K, V> android.util.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (V)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });
+ method public static inline <K, V> android.util.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> return 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { return null as V }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });
}
}
""",
@@ -359,27 +360,6 @@
method public int method3(java.lang.Integer value, int value2);
}
}
- """,
- privateApi = """
- package test.pkg {
- public final class Kotlin extends test.pkg.Parent {
- method internal boolean getMyHiddenVar${"$"}lintWithKotlin();
- method internal void myHiddenMethod${"$"}lintWithKotlin();
- method internal void setMyHiddenVar${"$"}lintWithKotlin(boolean p);
- property internal final boolean myHiddenVar;
- field internal boolean myHiddenVar;
- field private final java.lang.String property1;
- field private java.lang.String property2;
- field private int someField;
- }
- public static final class Kotlin.Companion {
- ctor private Kotlin.Companion();
- }
- internal static final class Kotlin.myHiddenClass extends kotlin.Unit {
- ctor public Kotlin.myHiddenClass();
- method internal test.pkg.Kotlin.myHiddenClass copy();
- }
- }
"""
)
}
@@ -474,7 +454,7 @@
api = """
package test.pkg {
public final class TestKt {
- method public static suspend inline Object hello(@NonNull kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+ method @Nullable public static suspend inline Object hello(@NonNull kotlin.coroutines.Continuation<? super kotlin.Unit> p);
}
}
"""
@@ -492,7 +472,7 @@
class MyClass {
// This property should have no public setter
var readOnlyVar = false
- internal set
+ internal set
}
"""
)
@@ -585,10 +565,20 @@
// Signature format: 3.0
package test.pkg {
public final class TestKt {
- method @UiThread public static inline <reified Args extends test.pkg2.NavArgs> test.pkg2.NavArgsLazy<Args> navArgs(test.pkg2.Fragment);
+ method @UiThread public static inline <reified Args extends test.pkg2.NavArgs> test.pkg2.NavArgsLazy<Args>! navArgs(test.pkg2.Fragment);
}
}
""",
+// Actual expected API is below. However, due to KT-39209 the nullability information is
+// missing
+// api = """
+// // Signature format: 3.0
+// package test.pkg {
+// public final class TestKt {
+// method @UiThread public static inline <reified Args extends test.pkg2.NavArgs> test.pkg2.NavArgsLazy<Args> navArgs(test.pkg2.Fragment);
+// }
+// }
+// """,
format = FileFormat.V3,
extraArguments = arrayOf(
ARG_HIDE_PACKAGE, "androidx.annotation",
@@ -662,7 +652,9 @@
return null
}
"""
- )
+ ),
+ androidxNonNullSource,
+ androidxNullableSource
),
api = """
// Signature format: 3.0
@@ -875,6 +867,53 @@
}
"""
),
+ kotlin(
+ """
+ package test.pkg
+ enum class Language {
+ KOTLIN,
+ JAVA
+ }
+ """
+ ).indented(),
+ kotlin(
+ """
+ package test.pkg
+ class Issue {
+ fun setAndroidSpecific(value: Boolean): Issue { return this }
+ companion object {
+ @JvmStatic
+ fun create(
+ id: String,
+ briefDescription: String,
+ explanation: String
+ ): Issue {
+ return Issue()
+ }
+ }
+ }
+ """
+ ).indented(),
+ kotlin(
+ """
+ package test.pkg
+ object MySingleton {
+ }
+ """
+ ).indented(),
+ java(
+ """
+ package test.pkg;
+ public class WrongCallDetector {
+ public static final Issue ISSUE =
+ Issue.create(
+ "WrongCall",
+ "Using wrong draw/layout method",
+ "Custom views typically need to call `measure()`)
+ .setAndroidSpecific(true));
+ }
+ """
+ ).indented(),
androidxNonNullSource,
androidxNullableSource
),
@@ -895,9 +934,31 @@
enum_constant public static final test.pkg.Foo A;
enum_constant public static final test.pkg.Foo B;
}
+ public final class Issue {
+ ctor public Issue();
+ method public static test.pkg.Issue create(String id, String briefDescription, String explanation);
+ method public test.pkg.Issue setAndroidSpecific(boolean value);
+ field public static final test.pkg.Issue.Companion Companion;
+ }
+ public static final class Issue.Companion {
+ method public test.pkg.Issue create(String id, String briefDescription, String explanation);
+ }
+ public enum Language {
+ method public static test.pkg.Language valueOf(String name) throws java.lang.IllegalArgumentException;
+ method public static test.pkg.Language[] values();
+ enum_constant public static final test.pkg.Language JAVA;
+ enum_constant public static final test.pkg.Language KOTLIN;
+ }
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface MyAnnotation {
method public abstract String[] value();
}
+ public final class MySingleton {
+ field public static final test.pkg.MySingleton INSTANCE;
+ }
+ public class WrongCallDetector {
+ ctor public WrongCallDetector();
+ field public static final test.pkg.Issue ISSUE;
+ }
}
""",
extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
@@ -957,6 +1018,239 @@
}
@Test
+ fun `Test JvmStatic`() {
+ check(
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ package test.pkg
+
+ class SimpleClass {
+ companion object {
+ @JvmStatic
+ fun jvmStaticMethod() {}
+ fun nonJvmStaticMethod() {}
+ }
+ }
+ """
+ )
+ ),
+ format = FileFormat.V3,
+ api = """
+ // Signature format: 3.0
+ package test.pkg {
+ public final class SimpleClass {
+ ctor public SimpleClass();
+ method public static void jvmStaticMethod();
+ field public static final test.pkg.SimpleClass.Companion Companion;
+ }
+ public static final class SimpleClass.Companion {
+ method public void jvmStaticMethod();
+ method public void nonJvmStaticMethod();
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Test JvmField`() {
+ check(
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ package test.pkg
+
+ class SimpleClass {
+ @JvmField
+ var jvmField = -1
+
+ var nonJvmField = -2
+ }
+ """
+ )
+ ),
+ format = FileFormat.V3,
+ api = """
+ // Signature format: 3.0
+ package test.pkg {
+ public final class SimpleClass {
+ ctor public SimpleClass();
+ method public int getNonJvmField();
+ method public void setNonJvmField(int p);
+ property public final int nonJvmField;
+ field public int jvmField;
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Test JvmName`() {
+ check(
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ package test.pkg
+
+ class SimpleClass {
+ @get:JvmName("myPropertyJvmGetter")
+ var myProperty = -1
+
+ var anotherProperty = -1
+ }
+ """
+ )
+ ),
+ format = FileFormat.V3,
+ api = """
+ // Signature format: 3.0
+ package test.pkg {
+ public final class SimpleClass {
+ ctor public SimpleClass();
+ method public int getAnotherProperty();
+ method public int myPropertyJvmGetter();
+ method public void setAnotherProperty(int p);
+ method public void setMyProperty(int p);
+ property public final int anotherProperty;
+ property public final int myProperty;
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Test RequiresOptIn and OptIn`() {
+ check(
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ package test.pkg
+
+ @RequiresOptIn
+ @Retention(AnnotationRetention.BINARY)
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+ annotation class ExperimentalBar
+
+ @ExperimentalBar
+ class FancyBar
+
+ @OptIn(FancyBar::class) // @OptIn should not be tracked as it is not API
+ class SimpleClass {
+ fun methodUsingFancyBar() {
+ val fancyBar = FancyBar()
+ }
+ }
+ """
+ )
+ ),
+ format = FileFormat.V3,
+ api = """
+ // Signature format: 3.0
+ package test.pkg {
+ @kotlin.RequiresOptIn @kotlin.annotation.Retention(AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={AnnotationTarget.CLASS, AnnotationTarget.FUNCTION}) public @interface ExperimentalBar {
+ }
+ @test.pkg.ExperimentalBar public final class FancyBar {
+ ctor public FancyBar();
+ }
+ public final class SimpleClass {
+ ctor public SimpleClass();
+ method public void methodUsingFancyBar();
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Test Experimental and UseExperimental`() {
+ check(
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ package test.pkg
+
+ @Experimental
+ @Retention(AnnotationRetention.BINARY)
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+ annotation class ExperimentalBar
+
+ @ExperimentalBar
+ class FancyBar
+
+ @UseExperimental(FancyBar::class) // @UseExperimental should not be tracked as it is not API
+ class SimpleClass {
+ fun methodUsingFancyBar() {
+ val fancyBar = FancyBar()
+ }
+ }
+
+ @androidx.annotation.experimental.UseExperimental(FancyBar::class) // @UseExperimental should not be tracked as it is not API
+ class AnotherSimpleClass {
+ fun methodUsingFancyBar() {
+ val fancyBar = FancyBar()
+ }
+ }
+ """
+ ),
+ kotlin("""
+ package androidx.annotation.experimental
+
+ import kotlin.annotation.Retention
+ import kotlin.annotation.Target
+ import kotlin.reflect.KClass
+
+ @Retention(AnnotationRetention.BINARY)
+ @Target(
+ AnnotationTarget.CLASS,
+ AnnotationTarget.PROPERTY,
+ AnnotationTarget.LOCAL_VARIABLE,
+ AnnotationTarget.VALUE_PARAMETER,
+ AnnotationTarget.CONSTRUCTOR,
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER,
+ AnnotationTarget.FILE,
+ AnnotationTarget.TYPEALIAS
+ )
+ annotation class UseExperimental(
+ /**
+ * Defines the experimental API(s) whose usage this annotation allows.
+ */
+ vararg val markerClass: KClass<out Annotation>
+ )
+ """)
+ ),
+ format = FileFormat.V3,
+ api = """
+ // Signature format: 3.0
+ package androidx.annotation.experimental {
+ @kotlin.annotation.Retention(AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FILE, AnnotationTarget.TYPEALIAS}) public @interface UseExperimental {
+ method public abstract Class<? extends java.lang.annotation.Annotation>[] markerClass();
+ }
+ }
+ package test.pkg {
+ public final class AnotherSimpleClass {
+ ctor public AnotherSimpleClass();
+ method public void methodUsingFancyBar();
+ }
+ @kotlin.Experimental @kotlin.annotation.Retention(AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={AnnotationTarget.CLASS, AnnotationTarget.FUNCTION}) public @interface ExperimentalBar {
+ }
+ @test.pkg.ExperimentalBar public final class FancyBar {
+ ctor public FancyBar();
+ }
+ public final class SimpleClass {
+ ctor public SimpleClass();
+ method public void methodUsingFancyBar();
+ }
+ }
+ """
+ )
+ }
+
+ @Test
fun `Extract class with generics`() {
// Basic interface with generics; makes sure <T extends Object> is written as just <T>
// Also include some more complex generics expressions to make sure they're serialized
@@ -1145,7 +1439,7 @@
api = """
package test.pkg {
public final class Foo extends java.lang.Enum {
- method public static test.pkg.Foo valueOf(java.lang.String);
+ method public static test.pkg.Foo valueOf(java.lang.String) throws java.lang.IllegalArgumentException;
method public static final test.pkg.Foo[] values();
enum_constant public static final test.pkg.Foo A;
enum_constant public static final test.pkg.Foo B;
@@ -1832,7 +2126,7 @@
package test.pkg {
public class FooBar extends java.lang.Enum {
method protected abstract void foo();
- method public static test.pkg.FooBar valueOf(java.lang.String);
+ method public static test.pkg.FooBar valueOf(java.lang.String) throws java.lang.IllegalArgumentException;
method public static final test.pkg.FooBar[] values();
enum_constant public static final test.pkg.FooBar ABC;
enum_constant public static final test.pkg.FooBar DEF;
@@ -2001,10 +2295,10 @@
api = """
package test.pkg {
public final class ChronUnit extends java.lang.Enum implements test.pkg.TempUnit {
- method public static test.pkg.ChronUnit valueOf(java.lang.String);
method public java.lang.String valueOf(int);
- method public static final test.pkg.ChronUnit[] values();
+ method public static test.pkg.ChronUnit valueOf(java.lang.String) throws java.lang.IllegalArgumentException;
method public final java.lang.String values(java.lang.String);
+ method public static final test.pkg.ChronUnit[] values();
enum_constant public static final test.pkg.ChronUnit A;
enum_constant public static final test.pkg.ChronUnit B;
enum_constant public static final test.pkg.ChronUnit C;
@@ -2754,27 +3048,7 @@
ctor public Parent();
}
}
- """,
- dexApi = """
- Ltest/pkg/Child;
- Ltest/pkg/Child;-><init>()V
- Ltest/pkg/Child;->toString()Ljava/lang/String;
- Ltest/pkg/Parent;
- Ltest/pkg/Parent;-><init>()V
- Ltest/pkg/Parent;->toString()Ljava/lang/String;
- """,
- dexApiMapping = """
- Ltest/pkg/Child;-><init>()V
- src/test/pkg/Child.java:2
- Ltest/pkg/Child;->hiddenApi()V
- src/test/pkg/Child.java:16
- Ltest/pkg/Child;->toString()Ljava/lang/String;
- src/test/pkg/Child.java:8
- Ltest/pkg/Parent;-><init>()V
- src/test/pkg/Parent.java:2
- Ltest/pkg/Parent;->toString()Ljava/lang/String;
- src/test/pkg/Parent.java:3
- """
+ """
)
}
@@ -2893,6 +3167,13 @@
Cache getCache();
}
"""
+ ),
+ java(
+ """
+ package com.squareup.okhttp;
+ public class Cache {
+ }
+ """
)
),
api = """
@@ -2906,226 +3187,6 @@
}
@Test
- fun `Private API signatures`() {
- check(
- sourceFiles = arrayOf(
- java(
- """
- package test.pkg;
- public class Class1 implements MyInterface {
- Class1(int arg) { }
- /** @hide */
- public void method1() { }
- void method2() { }
- private void method3() { }
- public int field1 = 1;
- protected int field2 = 2;
- int field3 = 3;
- float[][] field4 = 3;
- long[] field5 = null;
- private int field6 = 4;
- void myVarargsMethod(int x, String... args) { }
-
- public class Inner { // Fully public, should not be included
- public void publicMethod() { }
- }
- }
- """
- ),
-
- java(
- """
- package test.pkg;
- class Class2 {
- public void method4() { }
-
- private class Class3 {
- public void method5() { }
- }
- }
- """
- ),
-
- java(
- """
- package test.pkg;
- /** @doconly */
- class Class4 {
- public void method5() { }
- }
- """
- ),
-
- java(
- """
- package test.pkg;
- /** @hide */
- @SuppressWarnings("UnnecessaryInterfaceModifier")
- public interface MyInterface {
- public static final String MY_CONSTANT = "5";
- }
- """
- )
- ),
- privateApi = """
- package test.pkg {
- public class Class1 implements test.pkg.MyInterface {
- ctor Class1(int);
- method public void method1();
- method void method2();
- method private void method3();
- method void myVarargsMethod(int, java.lang.String...);
- field int field3;
- field float[][] field4;
- field long[] field5;
- field private int field6;
- }
- class Class2 {
- ctor Class2();
- method public void method4();
- }
- private class Class2.Class3 {
- ctor private Class2.Class3();
- method public void method5();
- }
- class Class4 {
- ctor Class4();
- method public void method5();
- }
- public abstract interface MyInterface {
- field public static final java.lang.String MY_CONSTANT = "5";
- }
- }
- """,
- privateDexApi = """
- Ltest/pkg/Class1;-><init>(I)V
- Ltest/pkg/Class1;->method1()V
- Ltest/pkg/Class1;->method2()V
- Ltest/pkg/Class1;->method3()V
- Ltest/pkg/Class1;->myVarargsMethod(I[Ljava/lang/String;)V
- Ltest/pkg/Class1;->field3:I
- Ltest/pkg/Class1;->field4:[[F
- Ltest/pkg/Class1;->field5:[J
- Ltest/pkg/Class1;->field6:I
- Ltest/pkg/Class2;
- Ltest/pkg/Class2;-><init>()V
- Ltest/pkg/Class2;->method4()V
- Ltest/pkg/Class2${"$"}Class3;
- Ltest/pkg/Class2${"$"}Class3;-><init>()V
- Ltest/pkg/Class2${"$"}Class3;->method5()V
- Ltest/pkg/Class4;
- Ltest/pkg/Class4;-><init>()V
- Ltest/pkg/Class4;->method5()V
- Ltest/pkg/MyInterface;
- Ltest/pkg/MyInterface;->MY_CONSTANT:Ljava/lang/String;
- """
- )
- }
-
- @Test
- fun `Private API signature corner cases`() {
- // Some corner case scenarios exposed by differences in output from doclava and metalava
- check(
- sourceFiles = arrayOf(
- java(
- """
- package test.pkg;
- import android.os.Parcel;
- import android.os.Parcelable;
- import java.util.concurrent.FutureTask;
-
- public class Class1 extends PrivateParent implements MyInterface {
- Class1(int arg) { }
-
- @Override public String toString() {
- return "Class1";
- }
-
- private abstract class AmsTask extends FutureTask<String> {
- @Override
- protected void set(String bundle) {
- super.set(bundle);
- }
- }
-
- /** @hide */
- public abstract static class TouchPoint implements Parcelable {
- }
- }
- """
- ),
-
- java(
- """
- package test.pkg;
- class PrivateParent {
- final String getValue() {
- return "";
- }
- }
- """
- ),
-
- java(
- """
- package test.pkg;
- /** @hide */
- public enum MyEnum {
- FOO, BAR
- }
- """
- ),
-
- java(
- """
- package test.pkg;
- @SuppressWarnings("UnnecessaryInterfaceModifier")
- public interface MyInterface {
- public static final String MY_CONSTANT = "5";
- }
- """
- )
- ),
- privateApi = """
- package test.pkg {
- public class Class1 extends test.pkg.PrivateParent implements test.pkg.MyInterface {
- ctor Class1(int);
- }
- private abstract class Class1.AmsTask extends java.util.concurrent.FutureTask {
- }
- public static abstract class Class1.TouchPoint implements android.os.Parcelable {
- ctor public Class1.TouchPoint();
- }
- public final class MyEnum extends java.lang.Enum {
- ctor private MyEnum();
- enum_constant public static final test.pkg.MyEnum BAR;
- enum_constant public static final test.pkg.MyEnum FOO;
- }
- class PrivateParent {
- ctor PrivateParent();
- method final java.lang.String getValue();
- }
- }
- """,
- privateDexApi = """
- Ltest/pkg/Class1;-><init>(I)V
- Ltest/pkg/Class1${"$"}AmsTask;
- Ltest/pkg/Class1${"$"}TouchPoint;
- Ltest/pkg/Class1${"$"}TouchPoint;-><init>()V
- Ltest/pkg/MyEnum;
- Ltest/pkg/MyEnum;-><init>()V
- Ltest/pkg/MyEnum;->valueOf(Ljava/lang/String;)Ltest/pkg/MyEnum;
- Ltest/pkg/MyEnum;->values()[Ltest/pkg/MyEnum;
- Ltest/pkg/MyEnum;->BAR:Ltest/pkg/MyEnum;
- Ltest/pkg/MyEnum;->FOO:Ltest/pkg/MyEnum;
- Ltest/pkg/PrivateParent;
- Ltest/pkg/PrivateParent;-><init>()V
- Ltest/pkg/PrivateParent;->getValue()Ljava/lang/String;
- """
- )
- }
-
- @Test
fun `Extend from multiple interfaces`() {
// Real-world example: XmlResourceParser
check(
@@ -3761,6 +3822,58 @@
}
@Test
+ fun `Test inherited methods that use generics`() {
+ check(
+ compatibilityMode = false,
+ sourceFiles = arrayOf(
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.NonNull;
+ public class Class2 extends Class1<String> {
+ @Override
+ public void method1(String input) { }
+ @Override
+ public void method2(@NonNull String input) { }
+ }
+ """
+ ),
+ java(
+ """
+ package test.pkg;
+ import androidx.annotation.NonNull;
+ class Class1<T> {
+ public void method1(T input) { }
+ public void method2(T input) { }
+ public void method3(T input) { }
+ @NonNull
+ public String method4(T input) { return ""; }
+ public T method5(@NonNull String input) { return null; }
+ }
+ """
+ ),
+ androidxNonNullSource
+ ),
+ extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
+ expectedIssues = "",
+ api =
+ """
+ package test.pkg {
+ public class Class2 {
+ ctor public Class2();
+ method public void method1(String);
+ method public void method2(@NonNull String);
+ method public void method3(String);
+ method @NonNull public String method4(String);
+ method public String method5(@NonNull String);
+ }
+ }
+ """
+
+ )
+ }
+
+ @Test
fun `Test merging API signature files`() {
val source1 = """
package Test.pkg {
@@ -3956,7 +4069,7 @@
"""
check(
signatureSources = arrayOf(source1, source2),
- expectedFail = "Unable to parse signature file: TESTROOT/project/load-api2.txt:2: Duplicate class found: Test.pkg.Class1"
+ expectedFail = "Aborting: Unable to parse signature file: TESTROOT/project/load-api2.txt:2: Duplicate class found: Test.pkg.Class1"
)
}
@@ -3974,8 +4087,148 @@
"""
check(
signatureSources = arrayOf(source1, source2),
- expectedFail = "Unable to parse signature file: Cannot merge different formats of signature files. " +
+ expectedFail = "Aborting: Unable to parse signature file: Cannot merge different formats of signature files. " +
"First file format=V2, current file format=V3: file=TESTROOT/project/load-api2.txt"
)
}
+
+ @Test
+ fun `Test tracking of @Composable annotation from classpath`() {
+ check(
+ format = FileFormat.V3,
+ classpath = arrayOf(
+ /* The following source file, compiled, and root folder jar'ed and stored as base64 gzip:
+ package test.pkg
+ @MustBeDocumented
+ @Retention(AnnotationRetention.BINARY)
+ @Target(
+ AnnotationTarget.CLASS,
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.TYPE,
+ AnnotationTarget.TYPE_PARAMETER,
+ AnnotationTarget.PROPERTY
+ )
+ annotation class Composable
+ */
+ base64gzip(
+ "test.jar", "" +
+ "UEsDBAoAAAgIAKx6s1AAAAAAAgAAAAAAAAAJAAAATUVUQS1JTkYvAwBQSwMECgAACAgAZ3qzULJ/" +
+ "Au4bAAAAGQAAABQAAABNRVRBLUlORi9NQU5JRkVTVC5NRvNNzMtMSy0u0Q1LLSrOzM+zUjDUM+Dl" +
+ "4uUCAFBLAwQKAAAICABnerNQDArdZgwAAAAQAAAAGwAAAE1FVEEtSU5GL3RlbXAua290bGluX21v" +
+ "ZHVsZWNgYGBmYGBghGIBAFBLAwQKAAAICABnerNQAAAAAAIAAAAAAAAABQAAAHRlc3QvAwBQSwME" +
+ "CgAACAgAZ3qzUAAAAAACAAAAAAAAAAkAAAB0ZXN0L3BrZy8DAFBLAwQKAAAICABnerNQbrgjGPQB" +
+ "AACVAwAAGQAAAHRlc3QvcGtnL0NvbXBvc2FibGUuY2xhc3OFUk1v2kAQfWtioG6TkKRpSdI0H01I" +
+ "P6S65doTEEdF4kvGrRRxqBZYIQdjo+xClRu3Xvsz+ht6qFCO/VFVZ4kCVLJU2Xo7O/PGM/M8v//8" +
+ "/AUgjzcMW0pIZQ/7PbsUDYaR5O1ApMAYMld8zO2Ahz273r4SHZVCguFg4eVhGCmu/Ci0C3MzBZPh" +
+ "pNKPVOCHy5TqSKqiOI86o4EIleh+YNiPoblCUZgsiptjHowEw1kMb1FxOSNZLNcK7iXDbkyKx697" +
+ "QhFrjQdB9FV07xwyvt9FgXmeWaoUmk2G9MWnWskr12sMK95lw6Ev6uNLo+AWqo7nuERpuPWG43rU" +
+ "ylElVrJ/lDiM5yyPlvsPpREFfudmpmoscT7FcXzcCYRux7sZCi0kzfGxfs6wcS9NVSje5YpT0BiM" +
+ "E7Q+TEOGru3ZFRpoQ1ifXN33NNR0YllG1rCMzJ41naRvvxnZ6SRvvGPF6eT2R9LQvDzDdiVmBakM" +
+ "SF4lBkOG1YX/bV8xWM1odN0RF35A27HjjkiAgfjsS58Ii/8mc1QAK/SZpG6P7FczfInXdH5Hih4g" +
+ "TfEHAhYe4hGZqy2YAmtY15DRsKFhU8MWHlPC9l3CE6zjqTZbMASympbFDnZhYq+FRBnPZu8+nt/f" +
+ "Dso4xBGZOG6BSbzACYUkTiVyEmd/AVBLAQIUAwoAAAgIAKx6s1AAAAAAAgAAAAAAAAAJAAAAAAAA" +
+ "AAAAEADtQQAAAABNRVRBLUlORi9QSwECFAMKAAAICABnerNQsn8C7hsAAAAZAAAAFAAAAAAAAAAA" +
+ "AAAApIEpAAAATUVUQS1JTkYvTUFOSUZFU1QuTUZQSwECFAMKAAAICABnerNQDArdZgwAAAAQAAAA" +
+ "GwAAAAAAAAAAAAAAoIF2AAAATUVUQS1JTkYvdGVtcC5rb3RsaW5fbW9kdWxlUEsBAhQDCgAACAgA" +
+ "Z3qzUAAAAAACAAAAAAAAAAUAAAAAAAAAAAAQAOhBuwAAAHRlc3QvUEsBAhQDCgAACAgAZ3qzUAAA" +
+ "AAACAAAAAAAAAAkAAAAAAAAAAAAQAOhB4AAAAHRlc3QvcGtnL1BLAQIUAwoAAAgIAGd6s1BuuCMY" +
+ "9AEAAJUDAAAZAAAAAAAAAAAAAACggQkBAAB0ZXN0L3BrZy9Db21wb3NhYmxlLmNsYXNzUEsFBgAA" +
+ "AAAGAAYAcwEAADQDAAAAAA=="
+ )
+ ),
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ package test.pkg
+ class RadioGroupScope() {
+ @Composable
+ fun RadioGroupItem(
+ selected: Boolean,
+ onSelect: () -> Unit,
+ content: @Composable () -> Unit
+ ) { }
+ }
+ """
+ )
+ ),
+ expectedIssues = "",
+ api =
+ """
+ // Signature format: 3.0
+ package test.pkg {
+ public final class RadioGroupScope {
+ ctor public RadioGroupScope();
+ method @test.pkg.Composable public void RadioGroupItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onSelect, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `@IntRange value in kotlin`() {
+ check(
+ format = FileFormat.V3,
+ sourceFiles = arrayOf(
+ kotlin("""
+ package test.pkg
+
+ import androidx.annotation.IntRange
+
+ class KotlinClass(@IntRange(from = 1) val param: Int) {
+ constructor(@IntRange(from = 2) val differentParam: Int)
+ fun myMethod(@IntRange(from = 3) val methodParam: Int) {}
+ }
+ """
+ ),
+ androidxIntRangeSource
+ ),
+ extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
+ api = """
+ // Signature format: 3.0
+ package test.pkg {
+ public final class KotlinClass {
+ ctor public KotlinClass(@IntRange(from=1) int param);
+ ctor public KotlinClass(@IntRange(from=2) int differentParam);
+ method public int getParam();
+ method public void myMethod(@IntRange(from=3) int methodParam);
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Kotlin properties with overriding get`() {
+ check(
+ format = FileFormat.V3,
+ sourceFiles = arrayOf(
+ kotlin("""
+ package test.pkg
+
+ import androidx.annotation.IntRange
+
+ class KotlinClass() {
+ val propertyWithGetter: Boolean get() = true
+ val propertyWithNoGetter: Boolean = true
+ }
+ """
+ ),
+ androidxIntRangeSource
+ ),
+ extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation"),
+ api = """
+ // Signature format: 3.0
+ package test.pkg {
+ public final class KotlinClass {
+ ctor public KotlinClass();
+ method public boolean getPropertyWithGetter();
+ method public boolean getPropertyWithNoGetter();
+ property public final boolean propertyWithGetter;
+ property public final boolean propertyWithNoGetter;
+ }
+ }
+ """
+ )
+ }
}
diff --git a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
index f34a83b..2143883 100644
--- a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
@@ -793,10 +793,18 @@
}
"""
+ val expectedApi = """
+ package android.app {
+ public static class ActionBar {
+ field public int gravity;
+ }
+ }
+ """
+
check(
compatibilityMode = false,
signatureSource = source,
- api = source
+ api = expectedApi
)
}
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/ApiLintTest.kt b/src/test/java/com/android/tools/metalava/ApiLintTest.kt
index 2779748..800214c 100644
--- a/src/test/java/com/android/tools/metalava/ApiLintTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiLintTest.kt
@@ -29,10 +29,12 @@
ARG_API_LINT_IGNORE_PREFIX,
"android.icu.",
ARG_API_LINT_IGNORE_PREFIX,
- "java."
+ "java.",
+ ARG_HIDE, "MissingNullability"
),
compatibilityMode = false,
expectedIssues = """
+ src/Dp.kt:3: warning: Acronyms should not be capitalized in method names: was `badCALL`, should this be `badCall`? [AcronymName] [Rule S1 in go/android-api-guidelines]
src/android/pkg/ALL_CAPS.java:3: warning: Acronyms should not be capitalized in class names: was `ALL_CAPS`, should this be `AllCaps`? [AcronymName] [Rule S1 in go/android-api-guidelines]
src/android/pkg/HTMLWriter.java:3: warning: Acronyms should not be capitalized in class names: was `HTMLWriter`, should this be `HtmlWriter`? [AcronymName] [Rule S1 in go/android-api-guidelines]
src/android/pkg/MyStringImpl.java:3: error: Don't expose your implementation details: `MyStringImpl` ends with `Impl` [EndsWithImpl]
@@ -44,7 +46,7 @@
src/android/pkg/badlyNamedClass.java:6: error: Constant field names must be named with only upper case characters: `android.pkg.badlyNamedClass#BadlyNamedField`, should be `BADLY_NAMED_FIELD`? [AllUpper] [Rule C2 in go/android-api-guidelines]
""",
expectedFail = """
- 9 new API lint issues were found.
+ 10 new API lint issues were found.
See tools/metalava/API-LINT.md for how to handle these.
""",
sourceFiles = arrayOf(
@@ -146,7 +148,13 @@
public void setZOrderOnTop() { }
}
"""
- )
+ ),
+ kotlin("""
+ inline class Dp(val value: Float)
+ fun greatCall(width: Dp)
+ fun badCALL(width: Dp)
+ """),
+ androidxNullableSource
)
/*
expectedOutput = """
@@ -241,7 +249,9 @@
public static final String FOO = System.getProperty("foo");
}
"""
- )
+ ),
+ androidxNonNullSource,
+ androidxNullableSource
)
)
}
@@ -278,7 +288,7 @@
apiLint = "", // enabled
compatibilityMode = false,
expectedIssues = """
- src/android/pkg/MyCallback.java:3: error: Callback method names must follow the on<Something> style: bar [CallbackMethodName] [Rule L1 in go/android-api-guidelines]
+ src/android/pkg/MyCallback.java:8: error: Callback method names must follow the on<Something> style: bar [CallbackMethodName] [Rule L1 in go/android-api-guidelines]
src/android/pkg/MyCallbacks.java:3: error: Callback class names should be singular: MyCallbacks [SingularCallback] [Rule L1 in go/android-api-guidelines]
src/android/pkg/MyInterfaceCallback.java:3: error: Callbacks must be abstract class instead of interface to enable extension in future API levels: MyInterfaceCallback [CallbackInterface] [Rule CL3 in go/android-api-guidelines]
src/android/pkg/MyObserver.java:3: warning: Class should be named MyCallback [CallbackName] [Rule L1 in go/android-api-guidelines]
@@ -300,6 +310,15 @@
"""
package android.pkg;
+ public final class RemoteCallback {
+ public void sendResult();
+ }
+ """
+ ),
+ java(
+ """
+ package android.pkg;
+
public class MyObserver {
}
"""
@@ -323,6 +342,10 @@
}
public void bar() {
}
+ public static void staticMethod() {
+ }
+ public final void finalMethod() {
+ }
}
"""
)
@@ -336,8 +359,8 @@
apiLint = "", // enabled
compatibilityMode = false,
expectedIssues = """
- src/android/pkg/MyCallback.java:3: error: Callback method names must follow the on<Something> style: bar [CallbackMethodName] [Rule L1 in go/android-api-guidelines]
src/android/pkg/MyClassListener.java:3: error: Listeners should be an interface, or otherwise renamed Callback: MyClassListener [ListenerInterface] [Rule L1 in go/android-api-guidelines]
+ src/android/pkg/MyListener.java:6: error: Listener method names must follow the on<Something> style: bar [CallbackMethodName] [Rule L1 in go/android-api-guidelines]
""",
expectedFail = """
2 new API lint issues were found.
@@ -382,11 +405,15 @@
"""
package android.pkg;
- public class MyCallback {
+ public interface MyListener {
public void onFoo() {
}
public void bar() {
}
+ public static void staticMethod() {
+ }
+ public static void finalMethod() {
+ }
}
"""
)
@@ -518,7 +545,8 @@
public boolean equals(@Nullable Object other, int bar) { return false; } // wrong signature
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -610,7 +638,8 @@
public static final android.os.Parcelable.Creator<android.app.WallpaperColors> CREATOR = null;
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -723,7 +752,8 @@
public void bad3(@Nullable android.net.URL param) { }
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -773,7 +803,15 @@
}
}
"""
- )
+ ),
+ java(
+ """
+ package com.google.common.util.concurrent;
+ public class ListenableFuture<T> {
+ }
+ """
+ ),
+ androidxNullableSource
)
)
}
@@ -832,15 +870,16 @@
apiLint = "", // enabled
compatibilityMode = false,
expectedIssues = """
+ src/android/pkg/RegistrationInterface.java:6: error: Found registerOverriddenUnpairedCallback but not unregisterOverriddenUnpairedCallback in android.pkg.RegistrationInterface [PairedRegistration] [Rule L2 in go/android-api-guidelines]
src/android/pkg/RegistrationMethods.java:8: error: Found registerUnpairedCallback but not unregisterUnpairedCallback in android.pkg.RegistrationMethods [PairedRegistration] [Rule L2 in go/android-api-guidelines]
- src/android/pkg/RegistrationMethods.java:9: error: Found unregisterMismatchedCallback but not registerMismatchedCallback in android.pkg.RegistrationMethods [PairedRegistration] [Rule L2 in go/android-api-guidelines]
- src/android/pkg/RegistrationMethods.java:10: error: Callback methods should be named register/unregister; was addCallback [RegistrationName] [Rule L3 in go/android-api-guidelines]
- src/android/pkg/RegistrationMethods.java:15: error: Found addUnpairedListener but not removeUnpairedListener in android.pkg.RegistrationMethods [PairedRegistration] [Rule L2 in go/android-api-guidelines]
- src/android/pkg/RegistrationMethods.java:16: error: Found removeMismatchedListener but not addMismatchedListener in android.pkg.RegistrationMethods [PairedRegistration] [Rule L2 in go/android-api-guidelines]
- src/android/pkg/RegistrationMethods.java:17: error: Listener methods should be named add/remove; was registerWrongListener [RegistrationName] [Rule L3 in go/android-api-guidelines]
+ src/android/pkg/RegistrationMethods.java:12: error: Found unregisterMismatchedCallback but not registerMismatchedCallback in android.pkg.RegistrationMethods [PairedRegistration] [Rule L2 in go/android-api-guidelines]
+ src/android/pkg/RegistrationMethods.java:13: error: Callback methods should be named register/unregister; was addCallback [RegistrationName] [Rule L3 in go/android-api-guidelines]
+ src/android/pkg/RegistrationMethods.java:18: error: Found addUnpairedListener but not removeUnpairedListener in android.pkg.RegistrationMethods [PairedRegistration] [Rule L2 in go/android-api-guidelines]
+ src/android/pkg/RegistrationMethods.java:19: error: Found removeMismatchedListener but not addMismatchedListener in android.pkg.RegistrationMethods [PairedRegistration] [Rule L2 in go/android-api-guidelines]
+ src/android/pkg/RegistrationMethods.java:20: error: Listener methods should be named add/remove; was registerWrongListener [RegistrationName] [Rule L3 in go/android-api-guidelines]
""",
expectedFail = """
- 6 new API lint issues were found.
+ 7 new API lint issues were found.
See tools/metalava/API-LINT.md for how to handle these.
""",
sourceFiles = arrayOf(
@@ -850,10 +889,13 @@
import androidx.annotation.Nullable;
- public class RegistrationMethods {
+ public class RegistrationMethods implements RegistrationInterface {
public void registerOkCallback(@Nullable Runnable r) { } // OK
public void unregisterOkCallback(@Nullable Runnable r) { } // OK
public void registerUnpairedCallback(@Nullable Runnable r) { }
+ // OK here because it is override
+ @Override
+ public void registerOverriddenUnpairedCallback(@Nullable Runnable r) { }
public void unregisterMismatchedCallback(@Nullable Runnable r) { }
public void addCallback(@Nullable Runnable r) { }
@@ -865,7 +907,19 @@
public void registerWrongListener(@Nullable Runnable r) { }
}
"""
- )
+ ),
+ java(
+ """
+ package android.pkg;
+
+ import androidx.annotation.Nullable;
+
+ public interface RegistrationInterface {
+ void registerOverriddenUnpairedCallback(@Nullable Runnable r) { }
+ }
+ """
+ ),
+ androidxNullableSource
)
)
}
@@ -882,11 +936,12 @@
src/android/pkg/CheckSynchronization.java:23: error: Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.pkg.CheckSynchronization.errorMethod3() [VisiblySynchronized] [Rule M5 in go/android-api-guidelines]
src/android/pkg/CheckSynchronization2.kt:5: error: Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.pkg.CheckSynchronization2.errorMethod1() [VisiblySynchronized] [Rule M5 in go/android-api-guidelines]
src/android/pkg/CheckSynchronization2.kt:8: error: Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.pkg.CheckSynchronization2.errorMethod2() [VisiblySynchronized] [Rule M5 in go/android-api-guidelines]
+ src/android/pkg/CheckSynchronization2.kt:13: error: Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.pkg.CheckSynchronization2.errorMethod3() [VisiblySynchronized] [Rule M5 in go/android-api-guidelines]
src/android/pkg/CheckSynchronization2.kt:16: error: Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.pkg.CheckSynchronization2.errorMethod4() [VisiblySynchronized] [Rule M5 in go/android-api-guidelines]
src/android/pkg/CheckSynchronization2.kt:18: error: Internal locks must not be exposed (synchronizing on this or class is still externally observable): method android.pkg.CheckSynchronization2.errorMethod5() [VisiblySynchronized] [Rule M5 in go/android-api-guidelines]
""",
expectedFail = """
- 8 new API lint issues were found.
+ 9 new API lint issues were found.
See tools/metalava/API-LINT.md for how to handle these.
""",
sourceFiles = arrayOf(
@@ -948,7 +1003,9 @@
}
}
"""
- )
+ ),
+ androidxNullableSource,
+ nullableSource
)
)
}
@@ -978,7 +1035,8 @@
public Intent createIntentNow() { return null; } // WARN
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -1062,9 +1120,18 @@
apiLint = "", // enabled
compatibilityMode = false,
expectedIssues = """
- src/android/pkg/MyClass.java:9: warning: Methods must return the builder object (return type android.pkg.MyClass.Builder<T> instead of void): method android.pkg.MyClass.Builder.setSomething(int) [SetterReturnsThis] [Rule M4 in go/android-api-guidelines]
- src/android/pkg/MyClass.java:10: warning: Builder methods names should use setFoo() style: method android.pkg.MyClass.Builder.withFoo(int) [BuilderSetStyle]
- src/android/pkg/MyClass.java:6: warning: android.pkg.MyClass.Builder does not declare a `build()` method, but builder classes are expected to [MissingBuildMethod]
+ src/android/pkg/Bad.java:12: warning: Builder must be final: android.pkg.Bad.BadBuilder [StaticFinalBuilder]
+ src/android/pkg/Bad.java:12: warning: Builder must be static: android.pkg.Bad.BadBuilder [StaticFinalBuilder]
+ src/android/pkg/Bad.java:13: warning: Builder constructor arguments must be mandatory (i.e. not @Nullable): parameter badParameter in android.pkg.Bad.BadBuilder(String badParameter) [OptionalBuilderConstructorArgument]
+ src/android/pkg/Bad.java:24: warning: Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.pkg.Bad.BadBuilder.withBadSetterStyle(boolean) [BuilderSetStyle]
+ src/android/pkg/Bad.java:27: warning: Builder setter must be @NonNull: method android.pkg.Bad.BadBuilder.setReturnsNullable(boolean) [SetterReturnsThis] [Rule M4 in go/android-api-guidelines]
+ src/android/pkg/Bad.java:30: warning: Getter should be on the built object, not the builder: method android.pkg.Bad.BadBuilder.getOnBuilder() [GetterOnBuilder]
+ src/android/pkg/Bad.java:32: warning: Methods must return the builder object (return type android.pkg.Bad.BadBuilder instead of void): method android.pkg.Bad.BadBuilder.setNotReturningBuilder(boolean) [SetterReturnsThis] [Rule M4 in go/android-api-guidelines]
+ src/android/pkg/Bad.java:18: warning: android.pkg.Bad does not declare a `getWithoutMatchingGetters()` method matching method android.pkg.Bad.BadBuilder.addWithoutMatchingGetter(String) [MissingGetterMatchingBuilder]
+ src/android/pkg/Bad.java:21: warning: android.pkg.Bad does not declare a `isWithoutMatchingGetter()` method matching method android.pkg.Bad.BadBuilder.setWithoutMatchingGetter(boolean) [MissingGetterMatchingBuilder]
+ src/android/pkg/Bad.java:32: warning: android.pkg.Bad does not declare a `isNotReturningBuilder()` method matching method android.pkg.Bad.BadBuilder.setNotReturningBuilder(boolean) [MissingGetterMatchingBuilder]
+ src/android/pkg/Bad.java:43: warning: Methods must return the builder object (return type android.pkg.Bad.BadGenericBuilder<T> instead of T): method android.pkg.Bad.BadGenericBuilder.setBoolean(boolean) [SetterReturnsThis] [Rule M4 in go/android-api-guidelines]
+ src/android/pkg/Bad.java:38: warning: android.pkg.Bad.NoBuildMethodBuilder does not declare a `build()` method, but builder classes are expected to [MissingBuildMethod]
src/android/pkg/TopLevelBuilder.java:3: warning: Builder should be defined as inner class: android.pkg.TopLevelBuilder [TopLevelBuilder]
src/android/pkg/TopLevelBuilder.java:3: warning: android.pkg.TopLevelBuilder does not declare a `build()` method, but builder classes are expected to [MissingBuildMethod]
""",
@@ -1073,7 +1140,7 @@
"""
package android.pkg;
- public class TopLevelBuilder {
+ public final class TopLevelBuilder {
}
"""
),
@@ -1082,34 +1149,113 @@
package android.pkg;
import androidx.annotation.NonNull;
-
- public class MyClass {
- public class Builder<T> {
- public void clearAll() { }
- public int getSomething() { return 0; }
- public void setSomething(int s) { }
- @NonNull
- public Builder<T> withFoo(int s) { return this; }
- @NonNull
- public Builder<T> setOk(int s) { return this; }
- }
- }
- """
- ),
- java(
- """
- package android.pkg;
-
- import androidx.annotation.NonNull;
+ import androidx.annotation.Nullable;
public class Ok {
- public class OkBuilder {
+
+ public int getInt();
+ @NonNull
+ public List<String> getStrings();
+ public boolean isBoolean();
+ public boolean hasBoolean2();
+ public boolean shouldBoolean3();
+
+ public static final class OkBuilder {
+ public OkBuilder(@NonNull String goodParameter, int goodParameter2) {}
+
+ @NonNull
+ public Ok build() { return null; }
+
+ @NonNull
+ public OkBuilder setInt(int value) { return this; }
+
+ @NonNull
+ public OkBuilder addString(@NonNull String value) { return this; }
+
+ @NonNull
+ public OkBuilder clearStrings() { return this; }
+
+ @NonNull
+ public OkBuilder setBoolean(boolean v) { return this; }
+
+ @NonNull
+ public OkBuilder setHasBoolean2(boolean v) { return this; }
+
+ @NonNull
+ public OkBuilder setShouldBoolean3(boolean v) { return this; }
+
+ @NonNull
+ public OkBuilder clear() { return this; }
+
+ @NonNull
+ public OkBuilder clearAll() { return this; }
+ }
+
+ public static final class GenericBuilder<B extends GenericBuilder> {
+ @NonNull
+ public B setBoolean(boolean value) { return this; }
+
@NonNull
public Ok build() { return null; }
}
}
"""
- )
+ ),
+ java(
+ """
+ package android.pkg;
+
+ import androidx.annotation.NonNull;
+ import androidx.annotation.Nullable;
+
+ public class Bad {
+
+ public boolean isBoolean();
+ public boolean getWithoutMatchingGetter();
+ public boolean isReturnsNullable();
+
+ public class BadBuilder {
+ public BadBuilder(@Nullable String badParameter) {}
+
+ @NonNull
+ public Bad build() { return null; }
+
+ @NonNull
+ public BadBuilder addWithoutMatchingGetter(@NonNull String value) { return this; }
+
+ @NonNull
+ public BadBuilder setWithoutMatchingGetter(boolean v) { return this; }
+
+ @NonNull
+ public BadBuilder withBadSetterStyle(boolean v) { return this; }
+
+ @Nullable
+ public BadBuilder setReturnsNullable(boolean v) { return this; }
+
+ public boolean getOnBuilder() { return true; }
+
+ public void setNotReturningBuilder(boolean v) { return this; }
+
+ @NonNull
+ public BadBuilder () { return this; }
+ }
+
+ public static final class NoBuildMethodBuilder {
+ public NoBuildMethodBuilder() {}
+ }
+
+ public static final class BadGenericBuilder<T extends Bad> {
+ @NonNull
+ public T setBoolean(boolean value) { return this; }
+
+ @NonNull
+ public Bad build() { return null; }
+ }
+ }
+ """
+ ),
+ androidxNonNullSource,
+ androidxNullableSource
)
)
}
@@ -1215,7 +1361,8 @@
public Bitmap wrong(@Nullable View view, @Nullable Bitmap bitmap) { return null; }
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -1319,7 +1466,8 @@
}
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -1462,7 +1610,8 @@
public BitSet reverse(@Nullable BitSet bitset) { return null; }
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -1506,7 +1655,8 @@
}
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -1543,7 +1693,8 @@
public Short getDouble(@Nullable Double l) { return null; }
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -1647,7 +1798,8 @@
public void wrong(int i, @Nullable ContentResolver resolver) { }
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -1685,7 +1837,8 @@
public abstract class MyCallback {
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -1786,7 +1939,8 @@
public abstract class MyCallback {
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -1875,7 +2029,8 @@
public void error(int i, @Nullable File file) { }
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -1907,7 +2062,8 @@
public void error(int i, @Nullable File file) { }
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -2217,7 +2373,7 @@
"""
package android.pkg;
- import com.android.annotations.NonNull;
+ import android.annotation.NonNull;
import java.util.TimeZone;
public abstract class MyErrorClass1 {
@@ -2227,7 +2383,8 @@
}
}
"""
- )
+ ),
+ nonNullSource
)
)
}
@@ -2248,7 +2405,7 @@
"""
package android.pkg;
- import com.android.annotations.NonNull;
+ import android.annotation.NonNull;
import java.util.TimeZone;
public abstract class MyErrorClass1 {
@@ -2258,7 +2415,8 @@
}
}
"""
- )
+ ),
+ nonNullSource
)
)
}
@@ -2289,7 +2447,9 @@
public final int as = 0; // error
}
"""
- )
+ ),
+ androidxNonNullSource,
+ androidxNullableSource
)
)
}
@@ -2326,7 +2486,8 @@
public void plusAssign(@Nullable JavaClass other) { }
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -2359,7 +2520,8 @@
public void ok(@Nullable Number... args) { }
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -2398,7 +2560,8 @@
public void error(int i, @Nullable UserHandle handle) {}
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -2534,7 +2697,8 @@
public CloneTest clone() { return super.clone(); } // error
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -2562,7 +2726,8 @@
public abstract java.text.BreakIterator foo(@Nullable java.text.Collator collator);
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -2608,7 +2773,7 @@
"""
package android.yada;
- import com.android.annotations.NonNull;
+ import android.annotation.NonNull;
public class YadaService extends android.app.Service {
@Override
@@ -2616,7 +2781,9 @@
}
}
"""
- )
+ ),
+ androidxNullableSource,
+ nonNullSource
)
)
}
@@ -2685,7 +2852,8 @@
public void foo() { }
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -2794,7 +2962,8 @@
}
}
"""
- )
+ ),
+ androidxNullableSource
)
)
}
@@ -2803,7 +2972,7 @@
fun `Test fields, parameters and returns require nullability`() {
check(
apiLint = "", // enabled
- extraArguments = arrayOf(ARG_API_LINT, ARG_HIDE, "AllUpper,StaticUtils"),
+ extraArguments = arrayOf(ARG_API_LINT, ARG_HIDE, "AllUpper,StaticUtils,Enum"),
compatibilityMode = false,
expectedIssues = """
src/android/pkg/Foo.java:11: error: Missing nullability on parameter `name` in method `Foo` [MissingNullability]
@@ -2844,6 +3013,24 @@
}
"""
),
+ java(
+ """
+ package test.pkg;
+ @SuppressWarnings("ALL")
+ public enum Foo {
+ A, B;
+ }
+ """
+ ),
+ kotlin(
+ """
+ package test.pkg
+ enum class Language {
+ KOTLIN,
+ JAVA
+ }
+ """
+ ),
kotlin(
"""
package android.pkg
@@ -2909,4 +3096,164 @@
)
)
}
+
+ @Test
+ fun `Nullability check for generic methods referencing parent type parameter`() {
+ check(
+ apiLint = "", // enabled
+ compatibilityMode = false,
+ expectedIssues = """
+ src/test/pkg/MyClass.java:13: error: Missing nullability on method `method4` return [MissingNullability]
+ src/test/pkg/MyClass.java:14: error: Missing nullability on parameter `input` in method `method4` [MissingNullability]
+ """,
+ expectedFail = """
+ 2 new API lint issues were found.
+ See tools/metalava/API-LINT.md for how to handle these.
+ """,
+ sourceFiles = arrayOf(
+ java(
+ """
+ package test.pkg;
+
+ import androidx.annotation.NonNull;
+ import androidx.annotation.Nullable;
+
+ public class MyClass extends HiddenParent<String> {
+ public void method1() { }
+
+ @Nullable
+ @Override
+ public String method3(@NonNull String input) { return null; }
+
+ @Override
+ public String method4(String input) { return null; }
+ }
+ """
+ ),
+ java(
+ """
+ package test.pkg;
+
+ class HiddenParent<T> {
+ public T method2(T t) { }
+ public T method3(T t) { }
+ public T method4(T t) { }
+ }
+ """
+ ),
+ androidxNullableSource,
+ androidxNonNullSource
+ )
+ )
+ }
+
+ @Test
+ fun `No new setting keys`() {
+ check(
+ apiLint = "", // enabled
+ compatibilityMode = false,
+ extraArguments = arrayOf(
+ ARG_ERROR,
+ "NoSettingsProvider"
+ ),
+ expectedIssues = """
+ src/android/provider/Settings.java:9: error: New setting keys are not allowed (Field: BAD1); use getters/setters in relevant manager class [NoSettingsProvider]
+ src/android/provider/Settings.java:11: error: Bare field okay2 must be marked final, or moved behind accessors if mutable [MutableBareField] [Rule F2 in go/android-api-guidelines]
+ src/android/provider/Settings.java:17: error: New setting keys are not allowed (Field: BAD1); use getters/setters in relevant manager class [NoSettingsProvider]
+ src/android/provider/Settings.java:21: error: New setting keys are not allowed (Field: BAD1); use getters/setters in relevant manager class [NoSettingsProvider]
+ """,
+ expectedFail = """
+ 4 new API lint issues were found.
+ See tools/metalava/API-LINT.md for how to handle these.
+ """,
+ sourceFiles = arrayOf(
+ java(
+ """
+ package android.provider;
+
+ import androidx.annotation.Nullable;
+
+ public class Settings {
+ private Settings() { }
+ public static class Global {
+ private Global() { }
+ public static final String BAD1 = "";
+ public final String okay1 = "";
+ @Nullable
+ public static String okay2 = "";
+ public static final int OKAY3 = 0;
+ }
+ public static class Secure {
+ private Secure() { }
+ public static final String BAD1 = "";
+ }
+ public static class System {
+ private System() { }
+ public static final String BAD1 = "";
+ }
+ public static class Other {
+ private Other() { }
+ public static final String OKAY1 = "";
+ }
+ }
+ """
+ ),
+ androidxNullableSource
+ )
+ )
+ }
+
+ @Test
+ fun `No issues for ignored packages`() {
+ check(
+ apiLint = """
+ package java.math {
+ public class BigInteger {
+ ctor public BigInteger();
+ }
+ }
+ """.trimIndent(),
+ compatibilityMode = false,
+ extraArguments = arrayOf(
+ ARG_API_LINT_IGNORE_PREFIX,
+ "java."
+ ),
+ expectedIssues = "",
+ sourceFiles = arrayOf(
+ java(
+ """
+ package java.math;
+
+ public class BigInteger {
+ public byte newMethod() {
+ return 0;
+ }
+ }
+ """
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `vararg use in annotations`() {
+ check(
+ apiLint = "", // enabled
+ compatibilityMode = false,
+ expectedIssues = "",
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ package test.pkg
+
+ import kotlin.reflect.KClass
+
+ annotation class MyAnnotation(
+ vararg val markerClass: KClass<out Annotation>
+ )
+ """
+ )
+ )
+ )
+ }
}
diff --git a/src/test/java/com/android/tools/metalava/BaselineTest.kt b/src/test/java/com/android/tools/metalava/BaselineTest.kt
index f6672a3..ae7f2fa 100644
--- a/src/test/java/com/android/tools/metalava/BaselineTest.kt
+++ b/src/test/java/com/android/tools/metalava/BaselineTest.kt
@@ -218,7 +218,8 @@
}
"""
),
- testApiSource
+ testApiSource,
+ androidxNullableSource
),
api = """
package android.pkg {
@@ -298,4 +299,4 @@
)
)
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckBaselineTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckBaselineTest.kt
index 0325a42..3be4fa0 100644
--- a/src/test/java/com/android/tools/metalava/CompatibilityCheckBaselineTest.kt
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckBaselineTest.kt
@@ -38,7 +38,7 @@
signatureSource = """
""",
expectedFail = """
- Aborting: Found compatibility problems checking the public API against the API in TESTROOT/project/released-api.txt
+ Aborting: Found compatibility problems checking the public API (TESTROOT/project/load-api.txt) against the API in TESTROOT/project/released-api.txt
*** release-api check failed ***
"""
)
@@ -63,7 +63,7 @@
signatureSource = """
""",
expectedFail = """
- Aborting: Found compatibility problems checking the public API against the API in TESTROOT/project/current-api.txt
+ Aborting: Found compatibility problems checking the public API (TESTROOT/project/load-api.txt) against the API in TESTROOT/project/current-api.txt
*** current-api check failed ***
"""
)
@@ -206,7 +206,7 @@
}
""",
expectedFail = """
- Aborting: Found compatibility problems checking the public API against the API in TESTROOT/project/current-api.txt
+ Aborting: Found compatibility problems checking the public API (TESTROOT/project/load-api.txt) against the API in TESTROOT/project/current-api.txt
"""
)
}
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
index b0d2bca..bde00a9 100644
--- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
@@ -688,7 +688,7 @@
fun `Add seal`() {
check(
expectedIssues = """
- src/test/pkg/Foo.kt: error: Cannot add 'sealed' modifier to class test.pkg.Foo: Incompatible change [AddSealed]
+ src/test/pkg/Foo.kt:2: error: Cannot add 'sealed' modifier to class test.pkg.Foo: Incompatible change [AddSealed]
""",
compatibilityMode = false,
checkCompatibilityApi = """
@@ -2480,9 +2480,9 @@
compatibilityMode = false,
inputKotlinStyleNulls = true,
expectedIssues = """
- src/test/pkg/test.kt:5: error: Method test.pkg.TestKt.add made type variable T reified: incompatible change [ChangedThrows]
- src/test/pkg/test.kt:8: error: Method test.pkg.TestKt.two made type variable S reified: incompatible change [ChangedThrows]
- """,
+ src/test/pkg/test.kt:5: error: Method test.pkg.TestKt.add made type variable T reified: incompatible change [ChangedThrows]
+ src/test/pkg/test.kt:8: error: Method test.pkg.TestKt.two made type variable S reified: incompatible change [ChangedThrows]
+ """,
checkCompatibilityApi = """
package test.pkg {
public final class TestKt {
diff --git a/src/test/java/com/android/tools/metalava/ConvertTest.kt b/src/test/java/com/android/tools/metalava/ConvertTest.kt
index d952d7d..176f82b 100644
--- a/src/test/java/com/android/tools/metalava/ConvertTest.kt
+++ b/src/test/java/com/android/tools/metalava/ConvertTest.kt
@@ -35,7 +35,7 @@
""",
outputFile =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<class name="MyTest1"
@@ -69,7 +69,7 @@
""",
outputFile =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<class name="MyTest2"
@@ -132,7 +132,7 @@
""",
outputFile =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<class name="MyTest1"
@@ -269,7 +269,7 @@
""",
outputFile =
"""
- <api name="convert-output1">
+ <api name="convert-output1" xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<class name="MyTest1"
@@ -451,7 +451,7 @@
""",
outputFile =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<class name="MyTest1"
diff --git a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
index 9e1fa41..cda9dfe 100644
--- a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
+++ b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
@@ -2054,7 +2054,7 @@
),
columnSource
),
- checkCompilation = true,
+ checkCompilation = false, // stubs contain Cursor.NONEXISTENT so it does not compile
expectedIssues = """
src/test/pkg/ColumnTest.java:12: warning: Cannot find feature field for Cursor.NONEXISTENT required by field ColumnTest.BOGUS (may be hidden or removed) [MissingColumn]
""",
@@ -2069,19 +2069,19 @@
/**
* This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link Cursor.NONEXISTENT}, and are read-only and cannot be mutated.
*/
- public static final java.lang.String BOGUS = "bogus";
+ @android.provider.Column(value=Cursor.NONEXISTENT, readOnly=true) public static final java.lang.String BOGUS = "bogus";
/**
* This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link android.database.Cursor#FIELD_TYPE_STRING Cursor#FIELD_TYPE_STRING} .
*/
- public static final java.lang.String DATA = "_data";
+ @android.provider.Column(android.database.Cursor.FIELD_TYPE_STRING) public static final java.lang.String DATA = "_data";
/**
* This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link android.database.Cursor#FIELD_TYPE_BLOB Cursor#FIELD_TYPE_BLOB} , and are read-only and cannot be mutated.
*/
- public static final java.lang.String HASH = "_hash";
+ @android.provider.Column(value=android.database.Cursor.FIELD_TYPE_BLOB, readOnly=true) public static final java.lang.String HASH = "_hash";
/**
* This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link android.database.Cursor#FIELD_TYPE_STRING Cursor#FIELD_TYPE_STRING} , and are read-only and cannot be mutated.
*/
- public static final java.lang.String TITLE = "title";
+ @android.provider.Column(value=android.database.Cursor.FIELD_TYPE_STRING, readOnly=true) public static final java.lang.String TITLE = "title";
}
"""
)
diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt
index 0f2e6ac..6649d19 100644
--- a/src/test/java/com/android/tools/metalava/DriverTest.kt
+++ b/src/test/java/com/android/tools/metalava/DriverTest.kt
@@ -23,14 +23,18 @@
import com.android.ide.common.process.LoggedProcessOutputHandler
import com.android.ide.common.process.ProcessException
import com.android.ide.common.process.ProcessInfoBuilder
+import com.android.tools.lint.LintCliClient
+import com.android.tools.lint.UastEnvironment
import com.android.tools.lint.checks.ApiLookup
import com.android.tools.lint.checks.infrastructure.ClassName
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestFiles.java
+import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
import com.android.tools.lint.checks.infrastructure.stripComments
-import com.android.tools.metalava.doclava1.ApiFile
+import com.android.tools.lint.client.api.LintClient
+import com.android.tools.metalava.model.text.ApiFile
import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
import com.android.tools.metalava.model.defaultConfiguration
import com.android.tools.metalava.model.parseDocument
@@ -60,7 +64,6 @@
import kotlin.text.Charsets.UTF_8
const val CHECK_JDIFF = false
-const val CHECK_STUB_COMPILATION = false
/**
* Marker class for stubs argument to [DriverTest.check] indicating that no
@@ -78,6 +81,7 @@
@Before
fun setup() {
System.setProperty(ENV_VAR_METALAVA_TESTS_RUNNING, SdkConstants.VALUE_TRUE)
+ Disposer.setDebugMode(true)
}
protected fun createProject(vararg files: TestFile): File {
@@ -136,11 +140,11 @@
}
val stdout = output.toString(UTF_8.name())
- if (!stdout.isEmpty()) {
+ if (stdout.isNotEmpty()) {
addError("Unexpected write to stdout:\n $stdout")
}
val stderr = error.toString(UTF_8.name())
- if (!stderr.isEmpty()) {
+ if (stderr.isNotEmpty()) {
addError("Unexpected write to stderr:\n $stderr")
}
@@ -149,6 +153,7 @@
fail("Printed newlines with nothing else")
}
+ UastEnvironment.checkApplicationEnvironmentDisposed()
Disposer.assertIsEmpty(true)
return printedOutput
@@ -214,10 +219,10 @@
}
private fun <T> buildOptionalArgs(option: T?, converter: (T) -> Array<String>): Array<String> {
- if (option != null) {
- return converter(option)
+ return if (option != null) {
+ converter(option)
} else {
- return emptyArray<String>()
+ emptyArray()
}
}
@@ -234,31 +239,22 @@
/** Any jars to add to the class path */
classpath: Array<TestFile>? = null,
/** The API signature content (corresponds to --api) */
- @Language("TEXT")
+ // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859
api: String? = null,
/** The API signature content (corresponds to --api-xml) */
- @Language("XML")
+ // @Language("XML") https://youtrack.jetbrains.com/issue/KT-35859
apiXml: String? = null,
- /** The exact API signature content (corresponds to --exact-api) */
- exactApi: String? = null,
/** The removed API (corresponds to --removed-api) */
removedApi: String? = null,
/** The removed dex API (corresponds to --removed-dex-api) */
removedDexApi: String? = null,
- /** The private API (corresponds to --private-api) */
- privateApi: String? = null,
- /** The private DEX API (corresponds to --private-dex-api) */
- privateDexApi: String? = null,
- /** The DEX API (corresponds to --dex-api) */
- dexApi: String? = null,
- /** The DEX mapping API (corresponds to --dex-api-mapping) */
- dexApiMapping: String? = null,
/** The subtract api signature content (corresponds to --subtract-api) */
- @Language("TEXT")
+ // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859
subtractApi: String? = null,
/** Expected stubs (corresponds to --stubs) in order corresponding to [sourceFiles]. Use
* [NO_STUB] as a marker for source files that are not expected to generate stubs */
- @Language("JAVA") stubs: Array<String> = emptyArray(),
+ // @Language("JAVA") https://youtrack.jetbrains.com/issue/KT-35859
+ stubs: Array<String> = emptyArray(),
/** Stub source file list generated */
stubsSourceList: String? = null,
/** Doc Stub source file list generated */
@@ -280,37 +276,48 @@
errorSeverityExpectedIssues: String? = null,
checkCompilation: Boolean = false,
/** Annotations to merge in (in .xml format) */
- @Language("XML") mergeXmlAnnotations: String? = null,
+ // @Language("XML") https://youtrack.jetbrains.com/issue/KT-35859
+ mergeXmlAnnotations: String? = null,
/** Annotations to merge in (in .txt/.signature format) */
- @Language("TEXT") mergeSignatureAnnotations: String? = null,
+ // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859
+ mergeSignatureAnnotations: String? = null,
/** Qualifier annotations to merge in (in Java stub format) */
- @Language("JAVA") mergeJavaStubAnnotations: String? = null,
+ // @Language("JAVA") https://youtrack.jetbrains.com/issue/KT-35859
+ mergeJavaStubAnnotations: String? = null,
/** Inclusion annotations to merge in (in Java stub format) */
- @Language("JAVA") mergeInclusionAnnotations: String? = null,
- /** Otional API signature files content to load **instead** of Java/Kotlin source files */
- @Language("TEXT") signatureSources: Array<String> = emptyArray(),
+ // @Language("JAVA") https://youtrack.jetbrains.com/issue/KT-35859
+ mergeInclusionAnnotations: String? = null,
+ /** Optional API signature files content to load **instead** of Java/Kotlin source files */
+ // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859
+ signatureSources: Array<String> = emptyArray(),
/**
- * An otional API signature file content to load **instead** of Java/Kotlin source files.
+ * An optional API signature file content to load **instead** of Java/Kotlin source files.
* This is added to [signatureSources]. This argument exists for backward compatibility.
*/
- @Language("TEXT") signatureSource: String? = null,
+ // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859
+ signatureSource: String? = null,
/** An optional API jar file content to load **instead** of Java/Kotlin source files */
apiJar: File? = null,
/** An optional API signature to check the current API's compatibility with */
- @Language("TEXT") checkCompatibilityApi: String? = null,
+ // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859
+ checkCompatibilityApi: String? = null,
/** An optional API signature to check the last released API's compatibility with */
- @Language("TEXT") checkCompatibilityApiReleased: String? = null,
+ // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859
+ checkCompatibilityApiReleased: String? = null,
/** An optional API signature to check the current removed API's compatibility with */
- @Language("TEXT") checkCompatibilityRemovedApiCurrent: String? = null,
+ // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859
+ checkCompatibilityRemovedApiCurrent: String? = null,
/** An optional API signature to check the last released removed API's compatibility with */
- @Language("TEXT") checkCompatibilityRemovedApiReleased: String? = null,
+ // @Language("TEXT")
+ checkCompatibilityRemovedApiReleased: String? = null,
/** An optional API signature to compute nullness migration status from */
allowCompatibleDifferences: Boolean = true,
- @Language("TEXT") migrateNullsApi: String? = null,
- /** An optional Proguard keep file to generate */
- @Language("Proguard") proguard: String? = null,
+ // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859
+ migrateNullsApi: String? = null,
/** Show annotations (--show-annotation arguments) */
showAnnotations: Array<String> = emptyArray(),
+ /** "Show for stub purposes" API annotation ([ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION]) */
+ showForStubPurposesAnnotations: Array<String> = emptyArray(),
/** Hide annotations (--hide-annotation arguments) */
hideAnnotations: Array<String> = emptyArray(),
/** Hide meta-annotations (--hide-meta-annotation arguments) */
@@ -332,7 +339,7 @@
/** List of extra jar files to record annotation coverage from */
coverageJars: Array<TestFile>? = null,
/** Optional manifest to load and associate with the codebase */
- @Language("XML")
+ // @Language("XML") https://youtrack.jetbrains.com/issue/KT-35859
manifest: String? = null,
/** Packages to pre-import (these will therefore NOT be included in emitted stubs, signature files etc */
importedPackages: List<String> = emptyList(),
@@ -415,9 +422,12 @@
* If non null, enable API lint. If non-blank, a codebase where only new APIs not in the codebase
* are linted.
*/
- @Language("TEXT") apiLint: String? = null,
+ // @Language("TEXT") https://youtrack.jetbrains.com/issue/KT-35859
+ apiLint: String? = null,
/** The source files to pass to the analyzer */
- sourceFiles: Array<TestFile> = emptyArray()
+ sourceFiles: Array<TestFile> = emptyArray(),
+ /** [ARG_REPEAT_ERRORS_MAX] */
+ repeatErrorsMax: Int = 0
) {
// Ensure different API clients don't interfere with each other
try {
@@ -428,6 +438,9 @@
ignore.printStackTrace()
}
+ // Ensure that lint infrastructure (for UAST) knows it's dealing with a test
+ LintCliClient(LintClient.CLIENT_UNIT_TESTS)
+
if (compatibilityMode && mergeXmlAnnotations != null) {
fail(
"Can't specify both compatibilityMode and mergeXmlAnnotations: there were no " +
@@ -458,7 +471,7 @@
checkCompatibilityApiReleased != null ||
checkCompatibilityRemovedApiCurrent != null ||
checkCompatibilityRemovedApiReleased != null) &&
- (expectedIssues != null && !expectedIssues.trim().isEmpty())
+ (expectedIssues != null && expectedIssues.trim().isNotEmpty())
) {
"Aborting: Found compatibility problems with --check-compatibility"
} else {
@@ -483,7 +496,7 @@
}
val sourceList =
- if (!signatureSources.isEmpty() || signatureSource != null) {
+ if (signatureSources.isNotEmpty() || signatureSource != null) {
sourcePathDir.mkdirs()
// if signatureSource is set, add it to signatureSources.
@@ -491,9 +504,12 @@
signatureSource?. let { sources.add(it) }
var num = 0
- var args = mutableListOf<String>()
+ val args = mutableListOf<String>()
sources.forEach { file ->
- val signatureFile = File(project, "load-api${ if (++num == 1) "" else num }.txt")
+ val signatureFile = File(
+ project,
+ "load-api${ if (++num == 1) "" else num.toString() }.txt"
+ )
signatureFile.writeText(file.trimIndent())
args.add(signatureFile.path)
}
@@ -722,14 +738,6 @@
emptyArray()
}
- var proguardFile: File? = null
- val proguardKeepArguments = if (proguard != null) {
- proguardFile = File(project, "proguard.cfg")
- arrayOf(ARG_PROGUARD, proguardFile.path)
- } else {
- emptyArray()
- }
-
val showAnnotationArguments = if (showAnnotations.isNotEmpty() || includeSystemApiAnnotations) {
val args = mutableListOf<String>()
for (annotation in showAnnotations) {
@@ -760,6 +768,17 @@
emptyArray()
}
+ val showForStubPurposesAnnotationArguments = if (showForStubPurposesAnnotations.isNotEmpty()) {
+ val args = mutableListOf<String>()
+ for (annotation in showForStubPurposesAnnotations) {
+ args.add(ARG_SHOW_FOR_STUB_PURPOSES_ANNOTATION)
+ args.add(annotation)
+ }
+ args.toTypedArray()
+ } else {
+ emptyArray()
+ }
+
val hideMetaAnnotationArguments = if (hideMetaAnnotations.isNotEmpty()) {
val args = mutableListOf<String>()
for (annotation in hideMetaAnnotations) {
@@ -809,14 +828,6 @@
emptyArray()
}
- var exactApiFile: File? = null
- val exactApiArgs = if (exactApi != null) {
- exactApiFile = temporaryFolder.newFile("exact-api.txt")
- arrayOf(ARG_EXACT_API, exactApiFile.path)
- } else {
- emptyArray()
- }
-
var apiXmlFile: File? = null
val apiXmlArgs = if (apiXml != null) {
apiXmlFile = temporaryFolder.newFile("public-api-xml.txt")
@@ -825,39 +836,7 @@
emptyArray()
}
- var privateApiFile: File? = null
- val privateApiArgs = if (privateApi != null) {
- privateApiFile = temporaryFolder.newFile("private.txt")
- arrayOf(ARG_PRIVATE_API, privateApiFile.path)
- } else {
- emptyArray()
- }
-
- var dexApiFile: File? = null
- val dexApiArgs = if (dexApi != null) {
- dexApiFile = temporaryFolder.newFile("public-dex.txt")
- arrayOf(ARG_DEX_API, dexApiFile.path)
- } else {
- emptyArray()
- }
-
- var dexApiMappingFile: File? = null
- val dexApiMappingArgs = if (dexApiMapping != null) {
- dexApiMappingFile = temporaryFolder.newFile("api-mapping.txt")
- arrayOf(ARG_DEX_API_MAPPING, dexApiMappingFile.path)
- } else {
- emptyArray()
- }
-
- var privateDexApiFile: File? = null
- val privateDexApiArgs = if (privateDexApi != null) {
- privateDexApiFile = temporaryFolder.newFile("private-dex.txt")
- arrayOf(ARG_PRIVATE_DEX_API, privateDexApiFile.path)
- } else {
- emptyArray()
- }
-
- var subtractApiFile: File?
+ val subtractApiFile: File?
val subtractApiArgs = if (subtractApi != null) {
subtractApiFile = temporaryFolder.newFile("subtract-api.txt")
subtractApiFile.writeText(subtractApi.trimIndent())
@@ -1095,6 +1074,12 @@
arrayOf(ARG_ERROR_MESSAGE_CHECK_COMPATIBILITY_CURRENT, it)
}
+ val repeatErrorsMaxArgs = if (repeatErrorsMax > 0) {
+ arrayOf(ARG_REPEAT_ERRORS_MAX, repeatErrorsMax.toString())
+ } else {
+ emptyArray()
+ }
+
// Run optional additional setup steps on the project directory
projectSetup?.invoke(project)
@@ -1108,12 +1093,6 @@
"--temp-folder",
temporaryFolder.newFolder("temp").path,
- // For the tests we want to treat references to APIs like java.io.Closeable
- // as a class that is part of the API surface, not as a hidden class as would
- // be the case when analyzing a complete API surface
- // ARG_UNHIDE_CLASSPATH_CLASSES,
- ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES,
-
// Annotation generation temporarily turned off by default while integrating with
// SDK builds; tests need these
ARG_INCLUDE_ANNOTATIONS,
@@ -1128,11 +1107,6 @@
*removedDexArgs,
*apiArgs,
*apiXmlArgs,
- *exactApiArgs,
- *privateApiArgs,
- *dexApiArgs,
- *privateDexApiArgs,
- *dexApiMappingArgs,
*subtractApiArgs,
*stubsArgs,
*stubsSourceListArgs,
@@ -1153,7 +1127,6 @@
*checkCompatibilityApiReleasedArguments,
*checkCompatibilityRemovedCurrentArguments,
*checkCompatibilityRemovedReleasedArguments,
- *proguardKeepArguments,
*manifestFileArgs,
*convertArgs,
*applyApiLevelsXmlArgs,
@@ -1163,6 +1136,7 @@
*showAnnotationArguments,
*hideAnnotationArguments,
*hideMetaAnnotationArguments,
+ *showForStubPurposesAnnotationArguments,
*showUnannotatedArgs,
*includeSourceRetentionAnnotationArgs,
*apiLintArgs,
@@ -1179,9 +1153,23 @@
*errorMessageApiLintArgs,
*errorMessageCheckCompatibilityReleasedArgs,
*errorMessageCheckCompatibilityCurrentArgs,
+ *repeatErrorsMaxArgs,
expectedFail = expectedFail
)
+ if (expectedIssues != null) {
+ assertEquals(
+ expectedIssues.trimIndent().trim(),
+ cleanupString(allReportedIssues.toString(), project)
+ )
+ }
+ if (errorSeverityExpectedIssues != null) {
+ assertEquals(
+ errorSeverityExpectedIssues.trimIndent().trim(),
+ cleanupString(errorSeverityReportedIssues.toString(), project)
+ )
+ }
+
if (expectedOutput != null) {
assertEquals(expectedOutput.trimIndent().trim(), actualOutput.trim())
}
@@ -1229,7 +1217,7 @@
)
if (convertFiles.isNotEmpty()) {
- for (i in 0 until convertToJDiff.size) {
+ for (i in convertToJDiff.indices) {
val expected = convertToJDiff[i].outputFile
val converted = convertFiles[i].outputFile
if (convertToJDiff[i].baseApi != null &&
@@ -1270,64 +1258,6 @@
assertEquals(stripComments(removedDexApi, stripLineComments = false).trimIndent(), actualText)
}
- if (exactApi != null && exactApiFile != null) {
- assertTrue(
- "${exactApiFile.path} does not exist even though --exact-api was used",
- exactApiFile.exists()
- )
- val actualText = readFile(exactApiFile, stripBlankLines, trim)
- assertEquals(stripComments(exactApi, stripLineComments = false).trimIndent(), actualText)
- // Make sure we can read back the files we write
- ApiFile.parseApi(exactApiFile, options.outputKotlinStyleNulls)
- }
-
- if (privateApi != null && privateApiFile != null) {
- assertTrue(
- "${privateApiFile.path} does not exist even though --private-api was used",
- privateApiFile.exists()
- )
- val actualText = readFile(privateApiFile, stripBlankLines, trim)
- assertEquals(stripComments(privateApi, stripLineComments = false).trimIndent(), actualText)
- // Make sure we can read back the files we write
- ApiFile.parseApi(privateApiFile, options.outputKotlinStyleNulls)
- }
-
- if (dexApi != null && dexApiFile != null) {
- assertTrue(
- "${dexApiFile.path} does not exist even though --dex-api was used",
- dexApiFile.exists()
- )
- val actualText = readFile(dexApiFile, stripBlankLines, trim)
- assertEquals(stripComments(dexApi, stripLineComments = false).trimIndent(), actualText)
- }
-
- if (privateDexApi != null && privateDexApiFile != null) {
- assertTrue(
- "${privateDexApiFile.path} does not exist even though --private-dex-api was used",
- privateDexApiFile.exists()
- )
- val actualText = readFile(privateDexApiFile, stripBlankLines, trim)
- assertEquals(stripComments(privateDexApi, stripLineComments = false).trimIndent(), actualText)
- }
-
- if (dexApiMapping != null && dexApiMappingFile != null) {
- assertTrue(
- "${dexApiMappingFile.path} does not exist even though --dex-api-maping was used",
- dexApiMappingFile.exists()
- )
- val actualText = readFile(dexApiMappingFile, stripBlankLines, trim)
- assertEquals(stripComments(dexApiMapping, stripLineComments = false).trimIndent(), actualText)
- }
-
- if (proguard != null && proguardFile != null) {
- val expectedProguard = readFile(proguardFile)
- assertTrue(
- "${proguardFile.path} does not exist even though --proguard was used",
- proguardFile.exists()
- )
- assertEquals(stripComments(proguard, stripLineComments = false).trimIndent(), expectedProguard.trim())
- }
-
if (sdk_broadcast_actions != null) {
val actual = readFile(File(sdkFilesDir, "broadcast_actions.txt"), stripBlankLines, trim)
assertEquals(sdk_broadcast_actions.trimIndent().trim(), actual.trim())
@@ -1358,19 +1288,6 @@
assertEquals(sdk_widgets.trimIndent().trim(), actual.trim())
}
- if (expectedIssues != null) {
- assertEquals(
- expectedIssues.trimIndent().trim(),
- cleanupString(allReportedIssues.toString(), project)
- )
- }
- if (errorSeverityExpectedIssues != null) {
- assertEquals(
- errorSeverityExpectedIssues.trimIndent().trim(),
- cleanupString(errorSeverityReportedIssues.toString(), project)
- )
- }
-
if (extractAnnotations != null && extractedAnnotationsZip != null) {
assertTrue(
"Using --extract-annotations but $extractedAnnotationsZip was not created",
@@ -1392,7 +1309,7 @@
}
if (stubs.isNotEmpty() && stubsDir != null) {
- for (i in 0 until stubs.size) {
+ for (i in stubs.indices) {
var stub = stubs[i].trimIndent()
var targetPath: String
@@ -1470,7 +1387,7 @@
assertEquals(stripComments(docStubsSourceList, stripLineComments = false).trimIndent(), actualText)
}
- if (checkCompilation && stubsDir != null && CHECK_STUB_COMPILATION) {
+ if (checkCompilation && stubsDir != null) {
val generated = gatherSources(listOf(stubsDir)).asSequence().map { it.path }.toList().toTypedArray()
// Also need to include on the compile path annotation classes referenced in the stubs
@@ -1713,8 +1630,20 @@
@Retention(SOURCE)
@Target({TYPE_USE})
public @interface NonNull {
- int from() default Integer.MIN_VALUE;
- int to() default Integer.MAX_VALUE;
+ }
+ """
+).indented()
+
+val libcoreNullFromTypeParamSource: TestFile = java(
+ """
+ package libcore.util;
+ import static java.lang.annotation.ElementType.*;
+ import static java.lang.annotation.RetentionPolicy.SOURCE;
+ import java.lang.annotation.*;
+ @Documented
+ @Retention(SOURCE)
+ @Target({TYPE_USE})
+ public @interface NullFromTypeParam {
}
"""
).indented()
@@ -1729,8 +1658,6 @@
@Retention(SOURCE)
@Target({TYPE_USE})
public @interface Nullable {
- int from() default Integer.MIN_VALUE;
- int to() default Integer.MAX_VALUE;
}
"""
).indented()
@@ -1849,7 +1776,7 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
@SuppressWarnings("WeakerAccess")
@Retention(SOURCE)
- @Target({METHOD, PARAMETER, FIELD, TYPE_USE})
+ @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, TYPE_USE, ANNOTATION_TYPE, PACKAGE})
public @interface NonNull {
}
"""
@@ -1863,7 +1790,7 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
@SuppressWarnings("WeakerAccess")
@Retention(SOURCE)
- @Target({METHOD, PARAMETER, FIELD, TYPE_USE})
+ @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, TYPE_USE, ANNOTATION_TYPE, PACKAGE})
public @interface Nullable {
}
"""
@@ -1897,6 +1824,21 @@
"""
).indented()
+val androidxIntRangeSource: TestFile = java(
+ """
+ package androidx.annotation;
+ import java.lang.annotation.*;
+ import static java.lang.annotation.ElementType.*;
+ import static java.lang.annotation.RetentionPolicy.SOURCE;
+ @Retention(CLASS)
+ @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE})
+ public @interface IntRange {
+ long from() default Long.MIN_VALUE;
+ long to() default Long.MAX_VALUE;
+ }
+ """
+).indented()
+
val supportParameterName: TestFile = java(
"""
package androidx.annotation;
@@ -2100,3 +2042,24 @@
}
"""
).indented()
+
+val publishedApiSource: TestFile = kotlin(
+ """
+ /**
+ * When applied to a class or a member with internal visibility allows to use it from public inline functions and
+ * makes it effectively public.
+ *
+ * Public inline functions cannot use non-public API, since if they are inlined, those non-public API references
+ * would violate access restrictions at a call site (https://kotlinlang.org/docs/reference/inline-functions.html#public-inline-restrictions).
+ *
+ * To overcome this restriction an `internal` declaration can be annotated with the `@PublishedApi` annotation:
+ * - this allows to call that declaration from public inline functions;
+ * - the declaration becomes effectively public, and this should be considered with respect to binary compatibility maintaining.
+ */
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
+ @Retention(AnnotationRetention.BINARY)
+ @MustBeDocumented
+ @SinceKotlin("1.1")
+ public annotation class PublishedApi
+ """
+).indented()
diff --git a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
index 3e5cf95..2ae6a89 100644
--- a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
+++ b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
@@ -487,7 +487,8 @@
}
"""
).indented(),
- intDefAnnotationSource
+ intDefAnnotationSource,
+ intRangeAnnotationSource
),
extractAnnotations = mapOf(
"test.pkg" to """
@@ -616,4 +617,4 @@
"""
)
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/com/android/tools/metalava/FileReadSandboxTest.kt b/src/test/java/com/android/tools/metalava/FileReadSandboxTest.kt
index 8341dfd..97eb557 100644
--- a/src/test/java/com/android/tools/metalava/FileReadSandboxTest.kt
+++ b/src/test/java/com/android/tools/metalava/FileReadSandboxTest.kt
@@ -49,7 +49,7 @@
// Structure:
// - * Explicitly allowed
- // - ** Implicitly allowed (parent dirctories of whitelisted paths)
+ // - ** Implicitly allowed (parent directories of allowed paths)
// - @ Not allowed
// root **
// |-goodFile *
@@ -73,13 +73,12 @@
FileReadSandbox.allowAccess(goodDir)
FileReadSandbox.allowAccess(subSubDirGoodFile)
- var allowedSet = mutableSetOf(root, goodFile, goodDir, goodDirFile, subDir, subSubDir, subSubDirGoodFile).map { it.absolutePath }
- var emptySet = setOf<String>()
+ val allowedSet = mutableSetOf(root, goodFile, goodDir, goodDirFile, subDir, subSubDir, subSubDirGoodFile).map { it.absolutePath }
+ val emptySet = setOf<String>()
+ val violations = mutableSetOf<String>()
- var violations = mutableSetOf<String>()
-
- // Make sure whitelisted files are not in the violation list.
- fun assertWhitelistedFilesNotReported() {
+ // Make sure allowed files are not in the violation list.
+ fun assertAllowedFilesNotReported() {
assertEquals(emptySet, violations.intersect(allowedSet))
}
@@ -112,14 +111,14 @@
val newFile1 = File(root, "newFile1").apply { createNewFile() }
newFile1.readBytes()
- assertWhitelistedFilesNotReported()
+ assertAllowedFilesNotReported()
assertViolationNotReported(badFile, badDir, badDirFile, subSubDirBadFile)
// Access "bad" files.
badFile.readBytes()
badDirFile.readBytes()
- assertWhitelistedFilesNotReported()
+ assertAllowedFilesNotReported()
assertViolationReported(badFile, badDirFile)
assertViolationNotReported(subSubDirBadFile)
@@ -128,7 +127,7 @@
badDir.listFiles()
root.listFiles()
- assertWhitelistedFilesNotReported()
+ assertAllowedFilesNotReported()
assertViolationReported(badDir)
assertViolationNotReported(badFile, badDirFile, subSubDirBadFile)
@@ -141,7 +140,7 @@
subSubDirBadFile.readBytes() // *Not* allowed.
root.listFiles()
- assertWhitelistedFilesNotReported()
+ assertAllowedFilesNotReported()
assertViolationReported(subSubDir2, subSubDirBadFile) // These are not allowed to read.
} finally {
diff --git a/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt b/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt
index 633c49f..66e62b9 100644
--- a/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt
+++ b/src/test/java/com/android/tools/metalava/JDiffXmlTest.kt
@@ -38,7 +38,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<class name="MyTest"
@@ -127,7 +127,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<interface name="MyBaseInterface"
@@ -195,7 +195,7 @@
signatureSource = source,
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="a.b.c"
>
<interface name="MyStream"
@@ -392,7 +392,7 @@
signatureSource = source,
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<class name="Foo"
@@ -497,7 +497,7 @@
signatureSource = source,
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<class name="Foo"
@@ -599,7 +599,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="android.accounts"
>
<interface name="AccountManagerFuture"
@@ -666,7 +666,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="android.accounts"
>
<class name="ArgbEvaluator"
@@ -706,7 +706,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<interface name="AbstractList"
@@ -755,7 +755,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<class name="Test"
@@ -808,7 +808,7 @@
),
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<class name="Test"
@@ -859,7 +859,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="android.companion"
>
<interface name="DeviceFilter"
@@ -892,7 +892,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="android.companion"
>
<interface name="DeviceFilter"
@@ -925,7 +925,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="test.pkg"
>
<interface name="MethodHandleInfo"
@@ -977,7 +977,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="android"
>
<class name="Manifest.permission"
@@ -1062,7 +1062,7 @@
""",
apiXml =
"""
- <api>
+ <api xmlns:metalava="http://www.android.com/metalava/">
<package name="org.apache.http.impl.conn.tsccm"
>
<class name="ConnPoolByRoute"
diff --git a/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt b/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt
index 1820bd8..6e51e9e 100644
--- a/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt
+++ b/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt
@@ -52,6 +52,46 @@
}
@Test
+ fun `Kotlin language level`() {
+ // See https://kotlinlang.org/docs/reference/whatsnew13.html
+ check(
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ package test.pkg
+ interface Foo {
+ companion object {
+ @JvmField
+ val answer: Int = 42
+ @JvmStatic
+ fun sayHello() {
+ println("Hello, world!")
+ }
+ }
+ }
+ """
+ )
+ ),
+ api =
+ """
+ package test.pkg {
+ public abstract interface Foo {
+ method public default static void sayHello();
+ field public static final test.pkg.Foo.Companion Companion;
+ field public static final int answer = 42; // 0x2a
+ }
+ public static final class Foo.Companion {
+ method public void sayHello();
+ }
+ }
+ """,
+ // The above source uses 1.3 features, though UAST currently
+ // seems to still treat it as 1.3 despite being passed 1.2
+ extraArguments = arrayOf(ARG_KOTLIN_SOURCE, "1.2")
+ )
+ }
+
+ @Test
fun `Basic class signature extraction`() {
// Basic class; also checks that default constructor is made explicit
check(
@@ -144,4 +184,33 @@
extraArguments = arrayOf(ARG_JAVA_SOURCE, "1.9")
)
}
-}
\ No newline at end of file
+
+ @Test
+ fun `Using JDK APIs`() {
+ // Non-Android example
+ val jdk = System.getProperty("java.home") ?: error("Expected java.home to be set")
+ check(
+ sourceFiles = arrayOf(
+ java(
+ """
+ package test.pkg;
+ import javax.swing.JButton;
+ public class SwingTest extends JButton {
+ public JButton button;
+ }
+ """
+ )
+ ),
+ api =
+ """
+ package test.pkg {
+ public class SwingTest extends javax.swing.JButton {
+ ctor public SwingTest();
+ field public javax.swing.JButton button;
+ }
+ }
+ """,
+ extraArguments = arrayOf(ARG_JDK_HOME, jdk)
+ )
+ }
+}
diff --git a/src/test/java/com/android/tools/metalava/KeepFileTest.kt b/src/test/java/com/android/tools/metalava/KeepFileTest.kt
deleted file mode 100644
index 178b653..0000000
--- a/src/test/java/com/android/tools/metalava/KeepFileTest.kt
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Copyright (C) 2017 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 KeepFileTest : DriverTest() {
- @Test
- fun `Generate Keep file`() {
- check(
- sourceFiles = arrayOf(
- java(
- """
- package test.pkg;
- @SuppressWarnings("ALL")
- public interface MyInterface<T extends Object>
- extends MyBaseInterface {
- }
- """
- ), java(
- """
- package a.b.c;
- @SuppressWarnings("ALL")
- public interface MyStream<T, S extends MyStream<T, S>> extends java.lang.AutoCloseable {
- }
- """
- ), java(
- """
- package test.pkg;
- @SuppressWarnings("ALL")
- public interface MyInterface2<T extends Number>
- extends MyBaseInterface {
- class TtsSpan<C extends MyInterface<?>> { }
- abstract class Range<T extends Comparable<? super T>> {
- protected String myString;
- }
- }
- """
- ),
- java(
- """
- package test.pkg;
- public interface MyBaseInterface {
- void fun(int a, String b);
- }
- """
- )
- ),
- proguard = """
- -keep class a.b.c.MyStream {
- }
- -keep class test.pkg.MyBaseInterface {
- public abstract void fun(int, java.lang.String);
- }
- -keep class test.pkg.MyInterface {
- }
- -keep class test.pkg.MyInterface2 {
- }
- -keep class test.pkg.MyInterface2${"$"}Range {
- <init>();
- protected java.lang.String myString;
- }
- -keep class test.pkg.MyInterface2${"$"}TtsSpan {
- <init>();
- }
- """,
- extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
- )
- }
-
- @Test
- fun `Primitive types`() {
- check(
- sourceFiles = arrayOf(
- java(
- """
- package test.pkg;
- public class MyClass {
- public int testMethodA(int a) {}
- public boolean testMethodB(boolean a) {}
- public float testMethodC(float a) {}
- public double testMethodD(double a) {}
- public byte testMethodE(byte a) {}
- }
- """
- )
- ),
- proguard = """
- -keep class test.pkg.MyClass {
- <init>();
- public int testMethodA(int);
- public boolean testMethodB(boolean);
- public float testMethodC(float);
- public double testMethodD(double);
- public byte testMethodE(byte);
- }
- """,
- extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
- )
- }
-
- @Test
- fun `Primitive array types`() {
- check(
- sourceFiles = arrayOf(
- java(
- """
- package test.pkg;
- public class MyClass {
- public int[] testMethodA(int[] a) {}
- public float[][] testMethodB(float[][] a) {}
- public double[][][] testMethodC(double[][][] a) {}
- public byte testMethodD(byte... a) {}
- }
- """
- )
- ),
- proguard = """
- -keep class test.pkg.MyClass {
- <init>();
- public int[] testMethodA(int[]);
- public float[][] testMethodB(float[][]);
- public double[][][] testMethodC(double[][][]);
- public byte testMethodD(byte[]);
- }
- """,
- extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
- )
- }
-
- @Test
- fun `Object Array parameters`() {
- check(
- sourceFiles = arrayOf(
- java(
- """
- package test.pkg;
- public class MyClass {
- public void testMethodA(String a) {}
- public void testMethodB(Boolean[] a) {}
- public void testMethodC(Integer... a) {}
- }
- """
- )
- ),
- proguard = """
- -keep class test.pkg.MyClass {
- <init>();
- public void testMethodA(java.lang.String);
- public void testMethodB(java.lang.Boolean[]);
- public void testMethodC(java.lang.Integer[]);
- }
- """,
- extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
- )
- }
-
- @Test
- fun `Arrays with Inner class`() {
- check(
- sourceFiles = arrayOf(
- java(
- """
- package test.pkg;
- public class MyClass {
- public void testMethodA(InnerClass a) {}
- public void testMethodB(InnerClass[] a) {}
- public void testMethodC(InnerClass... a) {}
- public class InnerClass {}
- }
- """
- )
- ),
- proguard = """
- -keep class test.pkg.MyClass {
- <init>();
- public void testMethodA(test.pkg.MyClass${"$"}InnerClass);
- public void testMethodB(test.pkg.MyClass${"$"}InnerClass[]);
- public void testMethodC(test.pkg.MyClass${"$"}InnerClass[]);
- }
- -keep class test.pkg.MyClass${"$"}InnerClass {
- <init>();
- }
- """,
- extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
- )
- }
-
- @Test
- fun `Conflicting Class Names in parameters`() {
- check(
- sourceFiles = arrayOf(
- java(
- """
- package test.pkg;
- public class String {}
- """
- ),
- java(
- """
- package test.pkg;
- public class MyClass {
- public void testMethodA(String a, String b) {}
- public void testMethodB(String a, test.pkg.String b) {}
- public void testMethodC(String a, java.lang.String b) {}
- public void testMethodD(java.lang.String a, test.pkg.String b) {}
- }
- """
- ),
- java(
- """
- package test.pkg;
- public class MyClassArrays {
- public void testMethodA(String[] a, String[] b) {}
- public void testMethodB(String[] a, test.pkg.String[] b) {}
- public void testMethodC(String[] a, java.lang.String[] b) {}
- public void testMethodD(java.lang.String... a, test.pkg.String... b) {}
- }
- """
- )
- ),
- proguard = """
- -keep class test.pkg.MyClass {
- <init>();
- public void testMethodA(test.pkg.String, test.pkg.String);
- public void testMethodB(test.pkg.String, test.pkg.String);
- public void testMethodC(test.pkg.String, java.lang.String);
- public void testMethodD(java.lang.String, test.pkg.String);
- }
- -keep class test.pkg.MyClassArrays {
- <init>();
- public void testMethodA(test.pkg.String[], test.pkg.String[]);
- public void testMethodB(test.pkg.String[], test.pkg.String[]);
- public void testMethodC(test.pkg.String[], java.lang.String[]);
- public void testMethodD(java.lang.String[], test.pkg.String[]);
- }
- -keep class test.pkg.String {
- <init>();
- }
- """,
- extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
- )
- }
-
- @Test
- fun `Multi dimensional arrays`() {
- check(
- sourceFiles = arrayOf(
- java(
- """
- package test.pkg;
- public class String {}
- """
- ),
- java(
- """
- package test.pkg;
- public class MyClassArrays {
- public void testMethodA(String[][] a, String[][] b) {}
- public void testMethodB(String[][][] a, test.pkg.String[][][] b) {}
- public void testMethodC(String[][] a, java.lang.String[][] b) {}
- public class InnerClass {}
- public void testMethodD(InnerClass[][] a) {}
- }
- """
- )
- ),
- proguard = """
- -keep class test.pkg.MyClassArrays {
- <init>();
- public void testMethodA(test.pkg.String[][], test.pkg.String[][]);
- public void testMethodB(test.pkg.String[][][], test.pkg.String[][][]);
- public void testMethodC(test.pkg.String[][], java.lang.String[][]);
- public void testMethodD(test.pkg.MyClassArrays${"$"}InnerClass[][]);
- }
- -keep class test.pkg.MyClassArrays${"$"}InnerClass {
- <init>();
- }
- -keep class test.pkg.String {
- <init>();
- }
- """,
- extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
- )
- }
-
- @Test
- fun `Methods with arrays as the return type`() {
- check(
- sourceFiles = arrayOf(
- java(
- """
- package test.pkg;
- public class MyClass {
- public String[] testMethodA() {}
- public String[][] testMethodB() {}
- public String[][][] testMethodC() {}
- }
- """
- ),
- java(
- """
- package test.pkg;
- public class MyOtherClass {
- public java.lang.String[] testMethodA() {}
- public String[][] testMethodB() {}
- public test.pkg.String[][][] testMethodC() {}
- }
- """
- ),
- java(
- """
- package test.pkg;
- public class String {}
- """
- )
- ),
- proguard = """
- -keep class test.pkg.MyClass {
- <init>();
- public test.pkg.String[] testMethodA();
- public test.pkg.String[][] testMethodB();
- public test.pkg.String[][][] testMethodC();
- }
- -keep class test.pkg.MyOtherClass {
- <init>();
- public java.lang.String[] testMethodA();
- public test.pkg.String[][] testMethodB();
- public test.pkg.String[][][] testMethodC();
- }
- -keep class test.pkg.String {
- <init>();
- }
- """,
- extraArguments = arrayOf(ARG_HIDE, "KotlinKeyword")
- )
- }
-}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
index d6568df..6131fa5 100644
--- a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
+++ b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt
@@ -22,26 +22,34 @@
@Test
fun `Hard Kotlin keywords`() {
check(
- extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
+ apiLint = "",
expectedIssues = """
- 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]
+ src/test/pkg/Test.java:7: 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:8: 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:9: error: Avoid field names that are Kotlin hard keywords ("object"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords [KotlinKeyword]
""",
+ expectedFail = """
+ 3 new API lint issues were found.
+ See tools/metalava/API-LINT.md for how to handle these.
+ """,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
+
+ import androidx.annotation.NonNull;
import androidx.annotation.ParameterName;
public class Test {
public void fun() { }
public void foo(int fun, @ParameterName("typealias") int internalName) { }
- public Object object = null;
+ @NonNull
+ public final Object object = null;
}
"""
),
- supportParameterName
+ supportParameterName,
+ androidxNonNullSource
)
)
}
@@ -49,10 +57,10 @@
@Test
fun `Sam-compatible parameters should be last`() {
check(
- extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
+ apiLint = "",
expectedIssues = """
- 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.java:20: 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:21: 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(
@@ -60,6 +68,8 @@
"""
package test.pkg;
+ import androidx.annotation.Nullable;
+ import androidx.annotation.NonNull;
import java.lang.Runnable;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -68,21 +78,21 @@
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) { }
+ public void ok3(@Nullable Runnable run) { }
+ public void ok4(int x, @Nullable Runnable run) { }
+ public void ok5(@Nullable Runnable run1, @Nullable Runnable run2) { }
+ public void ok6(@Nullable 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) { }
+ public void error1(@NonNull 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) { }
+ public void ok8(@Nullable Iterable<String> iterable, int x) { }
}
"""
),
@@ -96,7 +106,9 @@
fun ok4(foo: Int, bar: (Int) -> Int, baz: (Int) -> Int) { }
fun error(bar: (Int) -> Int, foo: Int) { }
"""
- )
+ ),
+ androidxNullableSource,
+ androidxNonNullSource
)
)
}
@@ -104,12 +116,13 @@
@Test
fun `Companion object methods should be marked with JvmStatic`() {
check(
- extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
+ apiLint = "",
+ extraArguments = arrayOf(ARG_HIDE, "AllUpper", ARG_HIDE, "AcronymName"),
expectedIssues = """
src/test/pkg/Foo.kt:8: warning: Companion object constants like BIG_INTEGER_ONE should be marked @JvmField for Java interoperability; see https://developer.android.com/kotlin/interop#companion_constants [MissingJvmstatic]
- src/test/pkg/Foo.kt:10: warning: Companion object constants like WRONG should be using @JvmField, not @JvmStatic; see https://developer.android.com/kotlin/interop#companion_constants [MissingJvmstatic]
- src/test/pkg/Foo.kt:11: warning: Companion object constants like WRONG2 should be using @JvmField, not @JvmStatic; see https://developer.android.com/kotlin/interop#companion_constants [MissingJvmstatic]
- src/test/pkg/Foo.kt:14: warning: Companion object methods like missing should be marked @JvmStatic for Java interoperability; see https://developer.android.com/kotlin/interop#companion_functions [MissingJvmstatic]
+ src/test/pkg/Foo.kt:11: warning: Companion object constants like WRONG should be using @JvmField, not @JvmStatic; see https://developer.android.com/kotlin/interop#companion_constants [MissingJvmstatic]
+ src/test/pkg/Foo.kt:12: warning: Companion object constants like WRONG2 should be using @JvmField, not @JvmStatic; see https://developer.android.com/kotlin/interop#companion_constants [MissingJvmstatic]
+ src/test/pkg/Foo.kt:15: warning: Companion object methods like missing should be marked @JvmStatic for Java interoperability; see https://developer.android.com/kotlin/interop#companion_functions [MissingJvmstatic]
""",
sourceFiles = arrayOf(
kotlin(
@@ -122,6 +135,7 @@
companion object {
const val INTEGER_ONE = 1
val BIG_INTEGER_ONE = BigInteger.ONE
+ private val PRIVATE_BIG_INTEGER = BigInteger.ONE
var ok = 1
@JvmStatic val WRONG = 2
@JvmStatic @JvmField val WRONG2 = 2
@@ -142,7 +156,7 @@
@Test
fun `Methods with default parameters should specify JvmOverloads`() {
check(
- extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
+ apiLint = "",
expectedIssues = """
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]
""",
@@ -173,13 +187,18 @@
@Test
fun `Methods which throw exceptions should document them`() {
check(
- extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP),
+ apiLint = "",
+ extraArguments = arrayOf(ARG_HIDE, "BannedThrow", ARG_HIDE, "GenericException"),
expectedIssues = """
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]
""",
+ expectedFail = """
+ 4 new API lint issues were found.
+ See tools/metalava/API-LINT.md for how to handle these.
+ """.trimIndent(),
sourceFiles = arrayOf(
kotlin(
"""
diff --git a/src/test/java/com/android/tools/metalava/MarkPackagesAsRecentTest.kt b/src/test/java/com/android/tools/metalava/MarkPackagesAsRecentTest.kt
index 17ed741..352264a 100644
--- a/src/test/java/com/android/tools/metalava/MarkPackagesAsRecentTest.kt
+++ b/src/test/java/com/android/tools/metalava/MarkPackagesAsRecentTest.kt
@@ -21,7 +21,8 @@
public void method() { }
}
"""
- )
+ ),
+ androidxNullableSource
),
extraArguments = arrayOf(
@@ -55,7 +56,8 @@
public void method() { }
}
"""
- )
+ ),
+ androidxNullableSource
),
extraArguments = arrayOf(
diff --git a/src/test/java/com/android/tools/metalava/NullabilityAnnotationsValidatorTest.kt b/src/test/java/com/android/tools/metalava/NullabilityAnnotationsValidatorTest.kt
index 7f7a6b1..13322ab 100644
--- a/src/test/java/com/android/tools/metalava/NullabilityAnnotationsValidatorTest.kt
+++ b/src/test/java/com/android/tools/metalava/NullabilityAnnotationsValidatorTest.kt
@@ -41,7 +41,10 @@
NotAnnotated combine(NotAnnotated other);
}
"""
- )
+ ),
+ libcoreNonNullSource,
+ libcoreNullableSource,
+ libcoreNullFromTypeParamSource
),
compatibilityMode = false,
outputKotlinStyleNulls = false,
@@ -82,7 +85,9 @@
T get(int index);
}
"""
- )
+ ),
+ libcoreNonNullSource,
+ libcoreNullFromTypeParamSource
),
compatibilityMode = false,
outputKotlinStyleNulls = false,
@@ -124,7 +129,8 @@
T get(int index);
}
"""
- )
+ ),
+ libcoreNullableSource
),
compatibilityMode = false,
outputKotlinStyleNulls = false,
@@ -165,7 +171,10 @@
T get(int index);
}
"""
- )
+ ),
+ libcoreNonNullSource,
+ libcoreNullableSource,
+ libcoreNullFromTypeParamSource
),
compatibilityMode = false,
outputKotlinStyleNulls = false,
@@ -208,7 +217,9 @@
T get(int index);
}
"""
- )
+ ),
+ libcoreNullableSource,
+ libcoreNullFromTypeParamSource
),
compatibilityMode = false,
outputKotlinStyleNulls = false,
diff --git a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
index 3a69a0c..d7d054b 100644
--- a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
+++ b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
@@ -788,7 +788,8 @@
}
"""
),
- androidxNonNullSource
+ androidxNonNullSource,
+ androidxNullableSource
),
stubs = arrayOf(
"""
@@ -814,4 +815,4 @@
)
)
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt
index 33cfc7f..adef553 100644
--- a/src/test/java/com/android/tools/metalava/OptionsTest.kt
+++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt
@@ -38,172 +38,180 @@
General:
---help
+--help
This message.
---version
+--version
Show the version of metalava.
---quiet
+--quiet
Only include vital output
---verbose
+--verbose
Include extra diagnostic output
---color
+--color
Attempt to colorize the output (defaults to true if ${"$"}TERM is xterm)
---no-color
+--no-color
Do not attempt to colorize the output
---no-docs
+--no-docs
Cancel any other documentation flags supplied to metalava. This is here to
make it easier customize build system tasks.
---only-update-api
+--only-update-api
Cancel any other "action" flags other than generating signature files. This
is here to make it easier customize build system tasks, particularly for
the "make update-api" task.
---only-check-api
+--only-check-api
Cancel any other "action" flags other than checking signature files. This
is here to make it easier customize build system tasks, particularly for
the "make checkapi" task.
+--repeat-errors-max <N>
+ When specified, repeat at most N errors before finishing.
API sources:
---source-files <files>
+--source-files <files>
A comma separated list of source files to be parsed. Can also be @ followed
by a path to a text file containing paths to the full set of files to
parse.
---source-path <paths>
+--source-path <paths>
One or more directories (separated by `:`) containing source files (within
- a package hierarchy)
---classpath <paths>
+ a package hierarchy). If --strict-input-files, --strict-input-files:warn,
+ or --strict-input-files:stack are used, files accessed under --source-path
+ that are not explicitly specified in --source-files are reported as
+ violations.
+--classpath <paths>
One or more directories or jars (separated by `:`) containing classes that
should be on the classpath when parsing the source files
---merge-qualifier-annotations <file>
+--merge-qualifier-annotations <file>
An external annotations file to merge and overlay the sources, or a
directory of such files. Should be used for annotations intended for
inclusion in the API to be written out, e.g. nullability. Formats supported
are: IntelliJ's external annotations database format, .jar or .zip files
containing those, Android signature files, and Java stub files.
---merge-inclusion-annotations <file>
+--merge-inclusion-annotations <file>
An external annotations file to merge and overlay the sources, or a
directory of such files. Should be used for annotations which determine
inclusion in the API to be written out, i.e. show and hide. The only format
supported is Java stub files.
---validate-nullability-from-merged-stubs
+--validate-nullability-from-merged-stubs
Triggers validation of nullability annotations for any class where
--merge-qualifier-annotations includes a Java stub file.
---validate-nullability-from-list
+--validate-nullability-from-list
Triggers validation of nullability annotations for any class listed in the
named file (one top-level class per line, # prefix for comment line).
---nullability-warnings-txt <file>
+--nullability-warnings-txt <file>
Specifies where to write warnings encountered during validation of
nullability annotations. (Does not trigger validation by itself.)
---nullability-errors-non-fatal
+--nullability-errors-non-fatal
Specifies that errors encountered during validation of nullability
annotations should not be treated as errors. They will be written out to
the file specified in --nullability-warnings-txt instead.
---input-api-jar <file>
+--input-api-jar <file>
A .jar file to read APIs from directly
---manifest <file>
+--manifest <file>
A manifest file, used to for check permissions to cross check APIs
---replace-documentation <p> <r> <t>
+--replace-documentation <p> <r> <t>
Amongst nonempty documentation of items from Java packages <p> and their
subpackages, replaces any matches of regular expression <r> with
replacement text <t>. <p> is given as a nonempty list of Java package names
separated by ':' (e.g. "java:android.util"); <t> may contain backreferences
($1, $2 etc.) to matching groups from <r>.
---hide-package <package>
+--hide-package <package>
Remove the given packages from the API even if they have not been marked
with @hide
---show-annotation <annotation class>
+--show-annotation <annotation class>
Unhide any hidden elements that are also annotated with the given
annotation
---show-single-annotation <annotation>
+--show-single-annotation <annotation>
Like --show-annotation, but does not apply to members; these must also be
explicitly annotated
---hide-annotation <annotation class>
+--show-for-stub-purposes-annotation <annotation class>
+ Like --show-annotation, but elements annotated with it are assumed to be
+ "implicitly" included in the API surface, and they'll be included in
+ certain kinds of output such as stubs, but not in others, such as the
+ signature file and API lint
+--hide-annotation <annotation class>
Treat any elements annotated with the given annotation as hidden
--hide-meta-annotation <meta-annotation class>
Treat as hidden any elements annotated with an annotation which is itself
annotated with the given meta-annotation
---show-unannotated
+--show-unannotated
Include un-annotated public APIs in the signature file as well
---java-source <level>
+--java-source <level>
Sets the source level for Java source files; default is 1.8.
---stub-packages <package-list>
+--kotlin-source <level>
+ Sets the source level for Kotlin source files; default is 1.3.
+--sdk-home <dir>
+ If set, locate the `android.jar` file from the given Android SDK
+--compile-sdk-version <api>
+ Use the given API level
+--jdk-home <dir>
+ If set, add the Java APIs from the given JDK to the classpath
+--stub-packages <package-list>
List of packages (separated by :) which will be used to filter out
irrelevant code. If specified, only code in these packages will be included
in signature files, stubs, etc. (This is not limited to just the stubs; the
name is historical.) You can also use ".*" at the end to match subpackages,
so `foo.*` will match both `foo` and `foo.bar`.
---subtract-api <api file>
+--subtract-api <api file>
Subtracts the API in the given signature or jar file from the current API
being emitted via --api, --stubs, --doc-stubs, etc. Note that the
subtraction only applies to classes; it does not subtract members.
---typedefs-in-signatures <ref|inline>
+--typedefs-in-signatures <ref|inline>
Whether to include typedef annotations in signature files.
`--typedefs-in-signatures ref` will include just a reference to the typedef
class, which is not itself part of the API and is not included as a class,
and `--typedefs-in-signatures inline` will include the constants themselves
into each usage site. You can also supply `--typedefs-in-signatures none`
to explicitly turn it off, if the default ever changes.
---ignore-classes-on-classpath
+--ignore-classes-on-classpath
Prevents references to classes on the classpath from being added to the
generated stub files.
Documentation:
---public
+--public
Only include elements that are public
---protected
+--protected
Only include elements that are public or protected
---package
+--package
Only include elements that are public, protected or package protected
---private
+--private
Include all elements except those that are marked hidden
---hidden
+--hidden
Include all elements, including hidden
Extracting Signature Files:
---api <file>
+--api <file>
Generate a signature descriptor file
---private-api <file>
- Generate a signature descriptor file listing the exact private APIs
---dex-api <file>
- Generate a DEX signature descriptor file listing the APIs
---private-dex-api <file>
- Generate a DEX signature descriptor file listing the exact private APIs
---dex-api-mapping <file>
- Generate a DEX signature descriptor along with file and line numbers
---removed-api <file>
+--removed-api <file>
Generate a signature descriptor file for APIs that have been removed
---format=<v1,v2,v3,...>
+--format=<v1,v2,v3,...>
Sets the output signature file format to be the given version.
---output-kotlin-nulls[=yes|no]
+--output-kotlin-nulls[=yes|no]
Controls whether nullness annotations should be formatted as in Kotlin
(with "?" for nullable types, "" for non nullable types, and "!" for
unknown. The default is yes.
---output-default-values[=yes|no]
+--output-default-values[=yes|no]
Controls whether default values should be included in signature files. The
default is yes.
---compatible-output=[yes|no]
+--compatible-output=[yes|no]
Controls whether to keep signature files compatible with the historical
format (with its various quirks) or to generate the new format (which will
also include annotations that are part of the API, etc.)
---omit-common-packages[=yes|no]
+--omit-common-packages[=yes|no]
Skip common package prefixes like java.lang.* and kotlin.* in signature
files, along with packages for well known annotations like @Nullable and
@NonNull.
---include-signature-version[=yes|no]
+--include-signature-version[=yes|no]
Whether the signature files should include a comment listing the format
version of the signature file.
---proguard <file>
- Write a ProGuard keep file for the API
---sdk-values <dir>
+--sdk-values <dir>
Write SDK values files to the given directory
Generating Stubs:
---stubs <dir>
+--stubs <dir>
Generate stub source files for the API
---doc-stubs <dir>
+--doc-stubs <dir>
Generate documentation stub source files for the API. Documentation stub
files are similar to regular stub files, but there are some differences.
For example, in the stub files, we'll use special annotations like
@@ -211,74 +219,74 @@
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.
---include-annotations
+--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
+--exclude-annotations
Exclude annotations such as @Nullable from the stub files; the default.
--pass-through-annotation <annotation classes>
A comma separated list of fully qualified names of annotation classes that
must be passed through unchanged.
---exclude-documentation-from-stubs
+--exclude-documentation-from-stubs
Exclude element documentation (javadoc and kdoc) from the generated stubs.
(Copyright notices are not affected by this, they are always included.
Documentation stubs (--doc-stubs) are not affected.)
---write-stubs-source-list <file>
+--write-stubs-source-list <file>
Write the list of generated stub files into the given source list file. If
generating documentation stubs and you haven't also specified
--write-doc-stubs-source-list, this list will refer to the documentation
stubs; otherwise it's the non-documentation stubs.
---write-doc-stubs-source-list <file>
+--write-doc-stubs-source-list <file>
Write the list of generated doc stub files into the given source list file
---register-artifact <api-file> <id>
+--register-artifact <api-file> <id>
Registers the given id for the packages found in the given signature file.
metalava will inject an @artifactId <id> tag into every top level stub
class in that API.
Diffs and Checks:
---input-kotlin-nulls[=yes|no]
+--input-kotlin-nulls[=yes|no]
Whether the signature file being read should be interpreted as having
encoded its types using Kotlin style types: a suffix of "?" for nullable
types, no suffix for non nullable types, and "!" for unknown. The default
is no.
---check-compatibility:type:state <file>
+--check-compatibility:type:state <file>
Check compatibility. Type is one of 'api' and 'removed', which checks
either the public api or the removed api. State is one of 'current' and
'released', to check either the currently in development API or the last
publicly released API, respectively. Different compatibility checks apply
in the two scenarios. For example, to check the code base against the
current public API, use --check-compatibility:api:current.
---api-lint [api file]
+--api-lint [api file]
Check API for Android API best practices. If a signature file is provided,
only the APIs that are new since the API will be checked.
---api-lint-ignore-prefix [prefix]
+--api-lint-ignore-prefix [prefix]
A list of package prefixes to ignore API issues in when running with
--api-lint.
---check-kotlin-interop
- Check API intended to be used from both Kotlin and Java for
- interoperability issues
---migrate-nullness <api file>
+--migrate-nullness <api file>
Compare nullness information with the previous stable API and mark newly
annotated APIs as under migration.
---warnings-as-errors
+--warnings-as-errors
Promote all warnings to errors
---lints-as-errors
+--lints-as-errors
Promote all API lint warnings to errors
---error <id>
+--error <id>
Report issues of the given id as errors
---warning <id>
+--warning <id>
Report issues of the given id as warnings
---lint <id>
+--lint <id>
Report issues of the given id as having lint-severity
---hide <id>
+--hide <id>
Hide/skip issues of the given id
---report-even-if-suppressed <file>
+--report-even-if-suppressed <file>
Write all issues into the given file, even if suppressed (via annotation or
baseline) but not if hidden (by '--hide')
---baseline <file>
+--baseline <file>
Filter out any errors already reported in the given baseline file, or
create if it does not already exist
---update-baseline [file]
+--update-baseline [file]
Rewrite the existing baseline file with the current set of warnings. If
some warnings have been fixed, this will delete them from the baseline
files. If a file is provided, the updated baseline is written to the given
@@ -291,78 +299,78 @@
specifically for API compatibility issues performed by
--check-compatibility:api:released and
--check-compatibility:removed:released.
---merge-baseline [file]
+--merge-baseline [file]
Like --update-baseline, but instead of always replacing entries in the
baseline, it will merge the existing baseline with the new baseline. This
is useful if metalava runs multiple times on the same source tree with
different flags at different times, such as occasionally with --api-lint.
---pass-baseline-updates
+--pass-baseline-updates
Normally, encountering error will fail the build, even when updating
baselines. This flag allows you to tell metalava to continue without
errors, such that all the baselines in the source tree can be updated in
one go.
---delete-empty-baselines
+--delete-empty-baselines
Whether to delete baseline files if they are updated and there is nothing
to include.
---error-message:api-lint <message>
+--error-message:api-lint <message>
If set, metalava shows it when errors are detected in --api-lint.
--error-message:compatibility:released <message>
- If set, metalava shows it when errors are detected in
+ If set, metalava shows it when errors are detected in
--check-compatibility:api:released and
--check-compatibility:removed:released.
--error-message:compatibility:current <message>
- If set, metalava shows it when errors are detected in
+ If set, metalava shows it when errors are detected in
--check-compatibility:api:current and
--check-compatibility:removed:current.
JDiff:
---api-xml <file>
+--api-xml <file>
Like --api, but emits the API in the JDiff XML format instead
---convert-to-jdiff <sig> <xml>
+--convert-to-jdiff <sig> <xml>
Reads in the given signature file, and writes it out in the JDiff XML
format. Can be specified multiple times.
---convert-new-to-jdiff <old> <new> <xml>
+--convert-new-to-jdiff <old> <new> <xml>
Reads in the given old and new api files, computes the difference, and
writes out only the new parts of the API in the JDiff XML format.
---convert-to-v1 <sig> <sig>
+--convert-to-v1 <sig> <sig>
Reads in the given signature file and writes it out as a signature file in
the original v1/doclava format.
---convert-to-v2 <sig> <sig>
+--convert-to-v2 <sig> <sig>
Reads in the given signature file and writes it out as a signature file in
the new signature format, v2.
---convert-new-to-v2 <old> <new> <sig>
+--convert-new-to-v2 <old> <new> <sig>
Reads in the given old and new api files, computes the difference, and
writes out only the new parts of the API in the v2 format.
Statistics:
---annotation-coverage-stats
+--annotation-coverage-stats
Whether metalava should emit coverage statistics for annotations, listing
the percentage of the API that has been annotated with nullness
information.
---annotation-coverage-of <paths>
+--annotation-coverage-of <paths>
One or more jars (separated by `:`) containing existing apps that we want
to measure annotation coverage statistics for. The set of API usages in
those apps are counted up and the most frequently used APIs that are
missing annotation metadata are listed in descending order.
---skip-java-in-coverage-report
+--skip-java-in-coverage-report
In the coverage annotation report, skip java.** and kotlin.** to narrow the
focus down to the Android framework APIs.
---write-class-coverage-to <path>
+--write-class-coverage-to <path>
Specifies a file to write the annotation coverage report for classes to.
---write-member-coverage-to <path>
+--write-member-coverage-to <path>
Specifies a file to write the annotation coverage report for members to.
Extracting Annotations:
---extract-annotations <zipfile>
+--extract-annotations <zipfile>
Extracts source annotations from the source files and writes them into the
given zip file
---include-annotation-classes <dir>
+--include-annotation-classes <dir>
Copies the given stub annotation source files into the generated stub
sources; <dir> is typically metalava/stub-annotations/src/main/java/.
---rewrite-annotations <dir/jar>
+--rewrite-annotations <dir/jar>
For a bytecode folder or output jar, rewrites the androidx annotations to
be package private
--force-convert-to-warning-nullability-annotations <package1:-package2:...>
@@ -370,66 +378,66 @@
nullability issues appear to callers as warnings rather than errors by
replacing @Nullable/@NonNull in these APIs with
@RecentlyNullable/@RecentlyNonNull
---copy-annotations <source> <dest>
+--copy-annotations <source> <dest>
For a source folder full of annotation sources, generates corresponding
package private versions of the same annotations.
---include-source-retention
+--include-source-retention
If true, include source-retention annotations in the stub files. Does not
apply to signature files. Source retention annotations are extracted into
the external annotations files instead.
Injecting API Levels:
---apply-api-levels <api-versions.xml>
+--apply-api-levels <api-versions.xml>
Reads an XML file containing API level descriptions and merges the
information into the documentation
Extracting API Levels:
---generate-api-levels <xmlfile>
+--generate-api-levels <xmlfile>
Reads android.jar SDK files and generates an XML file recording the API
level for each class, method and field
---android-jar-pattern <pattern>
+--android-jar-pattern <pattern>
Patterns to use to locate Android JAR files. The default is
${"$"}ANDROID_HOME/platforms/android-%/android.jar.
---current-version
+--current-version
Sets the current API level of the current source code
---current-codename
+--current-codename
Sets the code name for the current source code
---current-jar
+--current-jar
Points to the current API jar, if any
Sandboxing:
---no-implicit-root
+--no-implicit-root
Disable implicit root directory detection. Otherwise, metalava adds in
source roots implied by the source files
---strict-input-files <file>
+--strict-input-files <file>
Do not read files that are not explicitly specified in the command line.
All violations are written to the given file. Reads on directories are
always allowed, but metalava still tracks reads on directories that are not
specified in the command line, and write them to the file.
---strict-input-files:warn <file>
+--strict-input-files:warn <file>
Warn when files not explicitly specified on the command line are read. All
violations are written to the given file. Reads on directories not
specified in the command line are allowed but also logged.
---strict-input-files:stack <file>
+--strict-input-files:stack <file>
Same as --strict-input-files but also print stacktraces.
---strict-input-files-exempt <files or dirs>
+--strict-input-files-exempt <files or dirs>
Used with --strict-input-files. Explicitly allow access to files and/or
directories (separated by `:). Can also be @ followed by a path to a text
file containing paths to the full set of files and/or directories.
Environment Variables:
-METALAVA_DUMP_ARGV
+METALAVA_DUMP_ARGV
Set to true to have metalava emit all the arguments it was invoked with.
Helpful when debugging or reproducing under a debugger what the build
system is doing.
-METALAVA_PREPEND_ARGS
+METALAVA_PREPEND_ARGS
One or more arguments (concatenated by space) to insert into the command
line, before the documentation flags.
-METALAVA_APPEND_ARGS
+METALAVA_APPEND_ARGS
One or more arguments (concatenated by space) to append to the end of the
command line, after the generate documentation flags.
@@ -450,7 +458,7 @@
assertEquals(
"""
-Invalid argument --blah-blah-blah
+Aborting: Invalid argument --blah-blah-blah
$FLAGS
@@ -544,7 +552,7 @@
fun `Test issue severity options with non-existing issue`() {
check(
extraArguments = arrayOf("--hide", "ThisIssueDoesNotExist"),
- expectedFail = "Unknown issue id: --hide ThisIssueDoesNotExist"
+ expectedFail = "Aborting: Unknown issue id: --hide ThisIssueDoesNotExist"
)
}
diff --git a/src/test/java/com/android/tools/metalava/ReporterTest.kt b/src/test/java/com/android/tools/metalava/ReporterTest.kt
index 0368bf6..f324349 100644
--- a/src/test/java/com/android/tools/metalava/ReporterTest.kt
+++ b/src/test/java/com/android/tools/metalava/ReporterTest.kt
@@ -47,4 +47,156 @@
)
)
}
-}
\ No newline at end of file
+
+ @Test
+ fun `Test suppression annotations`() {
+ check(
+ apiLint = "",
+ expectedIssues = """
+ src/test/pkg/Bar.kt:10: error: Method name must start with lowercase char: Unsuppressed [StartWithLower] [Rule S1 in go/android-api-guidelines]
+ src/test/pkg/Foo.java:10: error: Method name must start with lowercase char: Unsuppressed [StartWithLower] [Rule S1 in go/android-api-guidelines]
+ """,
+ expectedFail = """
+ 2 new API lint issues were found.
+ See tools/metalava/API-LINT.md for how to handle these.
+ """,
+ sourceFiles = arrayOf(
+ java("""
+ package test.pkg;
+ import android.annotation.SuppressLint;
+
+ public class Foo {
+ @SuppressLint("StartWithLower")
+ public void SuppressedWithSuppressLint() { }
+ @SuppressWarnings("StartWithLower")
+ public void SuppressedWithSuppressWarnings() { }
+
+ public void Unsuppressed() { }
+ }
+ """),
+ kotlin("""
+ package test.pkg
+ import android.annotation.SuppressLint;
+
+ class Bar {
+ @SuppressLint("StartWithLower")
+ fun SuppressedWithSuppressLint() { }
+ @Suppress("StartWithLower")
+ fun SuppressedWithSuppress() { }
+
+ fun Unsuppressed() { }
+ }
+ """),
+ suppressLintSource
+ )
+ )
+ }
+
+ @Test
+ fun `Test repeat errors with 1 error`() {
+ check(
+ apiLint = "",
+ expectedIssues = """
+ src/test/pkg/Foo.java:4: error: Missing nullability on parameter `a` in method `foo1` [MissingNullability]
+ """,
+ expectedFail = """
+ 1 new API lint issues were found.
+ See tools/metalava/API-LINT.md for how to handle these.
+ Error: metalava detected the following problems:
+ src/test/pkg/Foo.java:4: error: Missing nullability on parameter `a` in method `foo1` [MissingNullability]
+ """,
+ repeatErrorsMax = 5,
+ sourceFiles = arrayOf(
+ java("""
+ package test.pkg;
+
+ public class Foo {
+ public void foo1(String a) {}
+ }
+ """),
+ suppressLintSource
+ )
+ )
+ }
+
+ @Test
+ fun `Test repeat errors with 5 errors`() {
+ check(
+ apiLint = "",
+ expectedIssues = """
+ src/test/pkg/Foo.java:4: error: Missing nullability on parameter `a` in method `foo1` [MissingNullability]
+ src/test/pkg/Foo.java:5: error: Missing nullability on parameter `a` in method `foo2` [MissingNullability]
+ src/test/pkg/Foo.java:6: error: Missing nullability on parameter `a` in method `foo3` [MissingNullability]
+ src/test/pkg/Foo.java:7: error: Missing nullability on parameter `a` in method `foo4` [MissingNullability]
+ src/test/pkg/Foo.java:8: error: Missing nullability on parameter `a` in method `foo5` [MissingNullability]
+ """,
+ expectedFail = """
+ 5 new API lint issues were found.
+ See tools/metalava/API-LINT.md for how to handle these.
+ Error: metalava detected the following problems:
+ src/test/pkg/Foo.java:4: error: Missing nullability on parameter `a` in method `foo1` [MissingNullability]
+ src/test/pkg/Foo.java:5: error: Missing nullability on parameter `a` in method `foo2` [MissingNullability]
+ src/test/pkg/Foo.java:6: error: Missing nullability on parameter `a` in method `foo3` [MissingNullability]
+ src/test/pkg/Foo.java:7: error: Missing nullability on parameter `a` in method `foo4` [MissingNullability]
+ src/test/pkg/Foo.java:8: error: Missing nullability on parameter `a` in method `foo5` [MissingNullability]
+ """,
+ repeatErrorsMax = 5,
+ sourceFiles = arrayOf(
+ java("""
+ package test.pkg;
+
+ public class Foo {
+ public void foo1(String a) {}
+ public void foo2(String a) {}
+ public void foo3(String a) {}
+ public void foo4(String a) {}
+ public void foo5(String a) {}
+ }
+ """),
+ suppressLintSource
+ )
+ )
+ }
+
+ @Test
+ fun `Test repeat errors with 6 errors`() {
+ check(
+ apiLint = "",
+ expectedIssues = """
+ src/test/pkg/Foo.java:4: error: Missing nullability on parameter `a` in method `foo1` [MissingNullability]
+ src/test/pkg/Foo.java:5: error: Missing nullability on parameter `a` in method `foo2` [MissingNullability]
+ src/test/pkg/Foo.java:6: error: Missing nullability on parameter `a` in method `foo3` [MissingNullability]
+ src/test/pkg/Foo.java:7: error: Missing nullability on parameter `a` in method `foo4` [MissingNullability]
+ src/test/pkg/Foo.java:8: error: Missing nullability on parameter `a` in method `foo5` [MissingNullability]
+ src/test/pkg/Foo.java:9: error: Missing nullability on parameter `a` in method `foo6` [MissingNullability]
+ """,
+ expectedFail = """
+ 6 new API lint issues were found.
+ See tools/metalava/API-LINT.md for how to handle these.
+ Error: metalava detected the following problems:
+ src/test/pkg/Foo.java:4: error: Missing nullability on parameter `a` in method `foo1` [MissingNullability]
+ src/test/pkg/Foo.java:5: error: Missing nullability on parameter `a` in method `foo2` [MissingNullability]
+ src/test/pkg/Foo.java:6: error: Missing nullability on parameter `a` in method `foo3` [MissingNullability]
+ src/test/pkg/Foo.java:7: error: Missing nullability on parameter `a` in method `foo4` [MissingNullability]
+ src/test/pkg/Foo.java:8: error: Missing nullability on parameter `a` in method `foo5` [MissingNullability]
+ 1 more error(s) omitted. Search the log for 'error:' to find all of them.
+ """,
+ repeatErrorsMax = 5,
+ sourceFiles = arrayOf(
+ java("""
+ package test.pkg;
+
+ public class Foo {
+ public void foo1(String a) {}
+ public void foo2(String a) {}
+ public void foo3(String a) {}
+ public void foo4(String a) {}
+ public void foo5(String a) {}
+ public void foo6(String a) {}
+ }
+ """),
+ suppressLintSource
+ )
+ )
+ }
+}
diff --git a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
index 0da5465..70e9436 100644
--- a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
+++ b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
@@ -308,6 +308,56 @@
}
@Test
+ fun `Can't expose item from a hidden parent `() {
+ check(
+ sourceFiles = arrayOf(
+ java(
+ """
+ package test.pkg;
+ import android.annotation.SystemApi;
+
+ /** @hide */
+ public class Class1 {
+ /** @hide */
+ @SystemApi
+ public void method1() { }
+
+ /** @hide */
+ @SystemApi
+ public static class InnerClass1 {
+ }
+ }
+ """
+ ),
+ java(
+ """
+ package test.pkg;
+ import android.annotation.SystemApi;
+
+ /** @hide */
+ @SystemApi
+ public class Class2 {
+ /** @hide */
+ public static class InnerClass2 {
+ /** @hide */
+ @SystemApi
+ public void method2() { }
+ }
+ }
+ """
+ ),
+ systemApiSource
+ ),
+ showAnnotations = arrayOf("android.annotation.SystemApi"),
+ expectedIssues = """
+ src/test/pkg/Class1.java:6: error: Attempting to unhide method test.pkg.Class1.method1(), but surrounding class test.pkg.Class1 is hidden and should also be annotated with @android.annotation.SystemApi [ShowingMemberInHiddenClass]
+ src/test/pkg/Class1.java:10: error: Attempting to unhide class test.pkg.Class1.InnerClass1, but surrounding class test.pkg.Class1 is hidden and should also be annotated with @android.annotation.SystemApi [ShowingMemberInHiddenClass]
+ src/test/pkg/Class2.java:9: error: Attempting to unhide method test.pkg.Class2.InnerClass2.method2(), but surrounding class test.pkg.Class2.InnerClass2 is hidden and should also be annotated with @android.annotation.SystemApi [ShowingMemberInHiddenClass]
+ """
+ )
+ }
+
+ @Test
fun `showAnnotation with parameters`() {
check(
sourceFiles = arrayOf(
@@ -435,6 +485,9 @@
"""
)
),
+ expectedIssues = """
+ src/androidx/room/OnConflictStrategy.java:3: info: Unresolved import: `androidx.annotation.IntDef` [UnresolvedImport]
+ """,
api = """
// Signature format: 3.0
package androidx.room {
@@ -443,6 +496,7 @@
}
}
""",
+
extraArguments = arrayOf(
ARG_HIDE_ANNOTATION, "androidx.annotation.IntDef"
)
@@ -591,4 +645,134 @@
"""
)
}
+
+ @Test
+ fun `Check @PublishedApi handling`() {
+ check(
+ format = FileFormat.V3,
+ sourceFiles = arrayOf(
+ kotlin(
+ """
+ package test.pkg
+ /**
+ * @suppress
+ */
+ @PublishedApi
+ internal class WeAreSoCool()
+ """
+ ),
+ publishedApiSource
+ ),
+
+ extraArguments = arrayOf(
+ ARG_SHOW_ANNOTATION, "kotlin.PublishedApi"
+ ),
+ api = """
+ // Signature format: 3.0
+ package test.pkg {
+ @kotlin.PublishedApi internal final class WeAreSoCool {
+ ctor public WeAreSoCool();
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Methods inherit showAnnotations but fields and classes don't`() {
+ // "ShowAnnotations" are implicitly inheritted between functions, but but between
+ // fields or clases. In this test:
+ // - Class2.member() is implicitly a @SystemApi, so the stub class includes it.
+ // (Though it's not included in the API file because it's redundant.)
+ // - However, there's no inheritance for fields, so Class2.FIELD is *not* in the stub class,
+ // and if a client refers to Class2.FIELD, that resolves to Class*1*.FIELD.
+ // - Class3 is (very naturally) hidden even though the super class is visible.
+ check(
+ sourceFiles = arrayOf(
+ java(
+ """
+ package test.pkg;
+ import android.annotation.SystemApi;
+
+ /** @hide */
+ @SystemApi
+ public class Class1 {
+ /** @hide */
+ @SystemApi
+ public static final String FIELD = "Class1.FIELD";
+
+ /** @hide */
+ @SystemApi
+ public void member() {}
+ }
+ """
+ ),
+ java(
+ """
+ package test.pkg;
+ import android.annotation.SystemApi;
+
+ /** @hide */
+ @SystemApi
+ public class Class2 extends Class1 {
+ /** @hide */
+ public static final String FIELD = "Class2.FIELD";
+
+ /** @hide */
+ public void member() {}
+ }
+ """
+ ),
+ java(
+ """
+ package test.pkg;
+ import android.annotation.SystemApi;
+
+ /** @hide */
+ public class Class3 extends Class1 {
+ }
+ """
+ ),
+ systemApiSource
+ ),
+ showAnnotations = arrayOf("android.annotation.SystemApi"),
+ expectedIssues = """
+ """,
+ api = """
+ package test.pkg {
+ public class Class1 {
+ ctor public Class1();
+ method public void member();
+ field public static final java.lang.String FIELD = "Class1.FIELD";
+ }
+ public class Class2 extends test.pkg.Class1 {
+ ctor public Class2();
+ }
+ }
+ """,
+ stubs = arrayOf("""
+ package test.pkg;
+ /** @hide */
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class Class1 {
+ public Class1() { throw new RuntimeException("Stub!"); }
+ /** @hide */
+ public void member() { throw new RuntimeException("Stub!"); }
+ /** @hide */
+ public static final java.lang.String FIELD = "Class1.FIELD";
+ }
+ """,
+ """
+ package test.pkg;
+ /** @hide */
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class Class2 extends test.pkg.Class1 {
+ public Class2() { throw new RuntimeException("Stub!"); }
+ /** @hide */
+ public void member() { throw new RuntimeException("Stub!"); }
+ }
+ """
+ )
+ )
+ }
}
diff --git a/src/test/java/com/android/tools/metalava/ShowForStubPurposesAnnotationTest.kt b/src/test/java/com/android/tools/metalava/ShowForStubPurposesAnnotationTest.kt
new file mode 100644
index 0000000..4254a31
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/ShowForStubPurposesAnnotationTest.kt
@@ -0,0 +1,676 @@
+/*
+ * 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
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.metalava.doclava1.Issues
+import org.junit.Test
+
+class ShowForStubPurposesAnnotationTest : DriverTest() {
+ val HIDE_ANNOTATION = "test.annotation.Hide"
+ val MODULE_API = "test.annotation.ModuleApi" // @ModuleApi is assumed to "contain" @SystemApi
+ val SYSTEM_API = "test.annotation.SystemApi"
+
+ val EXTRA_ARGS = arrayOf(
+ ARG_ERROR,
+ Issues.UNAVAILABLE_SYMBOL.name,
+ ARG_ERROR,
+ Issues.HIDDEN_TYPE_PARAMETER.name,
+ ARG_ERROR,
+ Issues.REFERENCES_HIDDEN.name
+ )
+
+ val SOURCE1 = """
+ package test.pkg;
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public class SystemClass {
+ public void noAnnotation() {
+ }
+
+ @test.annotation.Hide
+ public void hidden() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public void system() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public void module() {
+ }
+
+ public static class NestedDefault {
+ public void noAnnotation() {
+ }
+
+ @test.annotation.Hide
+ public void hidden() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public void system() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public void module() {
+ }
+ }
+
+ @test.annotation.Hide
+ public static class NestedHidden {
+ public void noAnnotation() {
+ }
+
+ @test.annotation.Hide
+ public void hidden() {
+ }
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public static class NestedModule {
+ public void noAnnotation() {
+ }
+
+ @test.annotation.Hide
+ public void hidden() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public void module() {
+ }
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public void referFromModuleToSystem(SystemClass2 arg) {
+ }
+ }
+ """
+
+ val SOURCE2 = """
+ package test.pkg;
+
+ public class PublicClass {
+ public void noAnnotation() {
+ }
+
+ @test.annotation.Hide
+ public void hidden() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public void system() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public void module() {
+ }
+
+ public static class NestedDefault {
+ public void noAnnotation() {
+ }
+
+ @test.annotation.Hide
+ public void hidden() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public void system() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public void module() {
+ }
+ }
+
+ @test.annotation.Hide
+ public static class NestedHidden {
+ public void noAnnotation() {
+ }
+
+ @test.annotation.Hide
+ public void hidden() {
+ }
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public static class NestedModule {
+ public void noAnnotation() {
+ }
+
+ @test.annotation.Hide
+ public void hidden() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public void module() {
+ }
+ }
+ }
+ """
+
+ val SOURCE3 = """
+ package test.pkg;
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public class SystemClass2 {
+ }
+ """
+
+ val SOURCE4 = """
+ package test.pkg;
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public class ModuleClass {
+ public void noAnnotation() {
+ }
+
+ @test.annotation.Hide
+ public void hidden() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public void module() {
+ }
+ }
+ """
+
+ val SOURCE5 = """
+ package test.pkg;
+
+ @test.annotation.Hide
+ public class HiddenClass {
+ }
+ """
+
+ val SOURCE_FILES_1_2_3_4_5 = arrayOf<TestFile>(java(SOURCE1), java(SOURCE2), java(SOURCE3), java(SOURCE4), java(SOURCE5))
+
+ @Test
+ fun `Hierarchy test - SystemApi + ModuleApi`() {
+ check(
+ extraArguments = EXTRA_ARGS,
+ format = FileFormat.V2,
+ hideAnnotations = arrayOf(HIDE_ANNOTATION),
+ showAnnotations = arrayOf(MODULE_API, SYSTEM_API),
+ sourceFiles = SOURCE_FILES_1_2_3_4_5,
+ api = """
+ // Signature format: 2.0
+ package test.pkg {
+ public class ModuleClass {
+ ctor public ModuleClass();
+ method public void module();
+ method public void noAnnotation();
+ }
+ public class PublicClass {
+ method public void module();
+ method public void system();
+ }
+ public static class PublicClass.NestedDefault {
+ method public void module();
+ method public void system();
+ }
+ public static class PublicClass.NestedModule {
+ ctor public PublicClass.NestedModule();
+ method public void module();
+ method public void noAnnotation();
+ }
+ public class SystemClass {
+ ctor public SystemClass();
+ method public void module();
+ method public void noAnnotation();
+ method public void referFromModuleToSystem(test.pkg.SystemClass2);
+ method public void system();
+ }
+ public static class SystemClass.NestedDefault {
+ ctor public SystemClass.NestedDefault();
+ method public void module();
+ method public void noAnnotation();
+ method public void system();
+ }
+ public static class SystemClass.NestedModule {
+ ctor public SystemClass.NestedModule();
+ method public void module();
+ method public void noAnnotation();
+ }
+ public class SystemClass2 {
+ ctor public SystemClass2();
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Hierarchy test - SystemApi only`() {
+ check(
+ extraArguments = EXTRA_ARGS,
+ format = FileFormat.V2,
+ hideAnnotations = arrayOf(HIDE_ANNOTATION),
+ showAnnotations = arrayOf(SYSTEM_API),
+ sourceFiles = SOURCE_FILES_1_2_3_4_5,
+ api = """
+ // Signature format: 2.0
+ package test.pkg {
+ public class PublicClass {
+ method public void system();
+ }
+ public static class PublicClass.NestedDefault {
+ method public void system();
+ }
+ public class SystemClass {
+ ctor public SystemClass();
+ method public void noAnnotation();
+ method public void system();
+ }
+ public static class SystemClass.NestedDefault {
+ ctor public SystemClass.NestedDefault();
+ method public void noAnnotation();
+ method public void system();
+ }
+ public class SystemClass2 {
+ ctor public SystemClass2();
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Hierarchy test - ModuleApi Only, also check the stub files`() {
+ check(
+ extraArguments = EXTRA_ARGS,
+ format = FileFormat.V2,
+ hideAnnotations = arrayOf(HIDE_ANNOTATION),
+ showAnnotations = arrayOf(MODULE_API),
+ showForStubPurposesAnnotations = arrayOf(SYSTEM_API),
+ sourceFiles = SOURCE_FILES_1_2_3_4_5,
+ api = """
+ // Signature format: 2.0
+ package test.pkg {
+ public class ModuleClass {
+ ctor public ModuleClass();
+ method public void module();
+ method public void noAnnotation();
+ }
+ public class PublicClass {
+ method public void module();
+ }
+ public static class PublicClass.NestedDefault {
+ method public void module();
+ }
+ public static class PublicClass.NestedModule {
+ ctor public PublicClass.NestedModule();
+ method public void module();
+ method public void noAnnotation();
+ }
+ public class SystemClass {
+ method public void module();
+ method public void referFromModuleToSystem(test.pkg.SystemClass2);
+ }
+ public static class SystemClass.NestedDefault {
+ method public void module();
+ }
+ public static class SystemClass.NestedModule {
+ ctor public SystemClass.NestedModule();
+ method public void module();
+ method public void noAnnotation();
+ }
+ }
+ """,
+ stubs = arrayOf("""
+ package test.pkg;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class SystemClass {
+ public SystemClass() { throw new RuntimeException("Stub!"); }
+ public void noAnnotation() { throw new RuntimeException("Stub!"); }
+ public void system() { throw new RuntimeException("Stub!"); }
+ public void module() { throw new RuntimeException("Stub!"); }
+ public void referFromModuleToSystem(test.pkg.SystemClass2 arg) { throw new RuntimeException("Stub!"); }
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public static class NestedDefault {
+ public NestedDefault() { throw new RuntimeException("Stub!"); }
+ public void noAnnotation() { throw new RuntimeException("Stub!"); }
+ public void system() { throw new RuntimeException("Stub!"); }
+ public void module() { throw new RuntimeException("Stub!"); }
+ }
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public static class NestedModule {
+ public NestedModule() { throw new RuntimeException("Stub!"); }
+ public void noAnnotation() { throw new RuntimeException("Stub!"); }
+ public void module() { throw new RuntimeException("Stub!"); }
+ }
+ }
+ """,
+ """
+ package test.pkg;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class PublicClass {
+ public PublicClass() { throw new RuntimeException("Stub!"); }
+ public void noAnnotation() { throw new RuntimeException("Stub!"); }
+ public void system() { throw new RuntimeException("Stub!"); }
+ public void module() { throw new RuntimeException("Stub!"); }
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public static class NestedDefault {
+ public NestedDefault() { throw new RuntimeException("Stub!"); }
+ public void noAnnotation() { throw new RuntimeException("Stub!"); }
+ public void system() { throw new RuntimeException("Stub!"); }
+ public void module() { throw new RuntimeException("Stub!"); }
+ }
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public static class NestedModule {
+ public NestedModule() { throw new RuntimeException("Stub!"); }
+ public void noAnnotation() { throw new RuntimeException("Stub!"); }
+ public void module() { throw new RuntimeException("Stub!"); }
+ }
+ }
+ """,
+ """
+ package test.pkg;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class SystemClass2 {
+ public SystemClass2() { throw new RuntimeException("Stub!"); }
+ }
+ """,
+ """
+ package test.pkg;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class ModuleClass {
+ public ModuleClass() { throw new RuntimeException("Stub!"); }
+ public void noAnnotation() { throw new RuntimeException("Stub!"); }
+ public void module() { throw new RuntimeException("Stub!"); }
+ }
+ """
+ )
+ )
+ }
+
+ @Test
+ fun `Hierarchy test - Can't refer from system to module`() {
+ check(
+ extraArguments = EXTRA_ARGS,
+ format = FileFormat.V2,
+ hideAnnotations = arrayOf(HIDE_ANNOTATION),
+ showAnnotations = arrayOf(SYSTEM_API),
+ sourceFiles = arrayOf(
+ java("""
+ package test.pkg;
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public class SystemClass {
+ public void foo(ModuleClass arg) {
+ }
+ }
+ """
+ ),
+ java("""
+ package test.pkg;
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public class ModuleClass {
+ }
+ """
+ )
+ ),
+ expectedIssues = """
+ src/test/pkg/SystemClass.java:6: error: Class test.pkg.ModuleClass is hidden but was referenced (as parameter type) from public parameter arg in test.pkg.SystemClass.foo(test.pkg.ModuleClass arg) [ReferencesHidden]
+ src/test/pkg/SystemClass.java:6: error: Parameter of unavailable type test.pkg.ModuleClass in test.pkg.SystemClass.foo() [UnavailableSymbol]
+ src/test/pkg/SystemClass.java:6: error: Parameter arg references hidden type test.pkg.ModuleClass. [HiddenTypeParameter]
+ """
+ )
+ }
+
+ val SOURCE6 = """
+ package test.pkg;
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public class ModuleClass {
+ public void foo() {
+ }
+ }
+ """
+ val SOURCE7 = """
+ package test.pkg;
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public class ModuleClassExtendingPublic extends PublicSubClass {
+ public void bar() {
+ }
+
+ // This is a @SystemApi, so this shouldn't show up in the signature file.
+ @Override
+ public void abstractMethodOverridden() {
+ }
+ }
+ """
+ // Note all the methods in the following classes are "@SystemApi", so
+ // none of them should show up in the MODULE_API signature file.
+ val SOURCE8 = """
+ package test.pkg;
+
+ public class PublicClass {
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public abstract void abstractMethodOverridden();
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public abstract void abstractMethodNotOverridden();
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public abstract void abstractMethodOverriddenByAbstract();
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public void systemMethodOverridden() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public void systemMethodNotOverridden() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public void systemMethodHiddenInSub() {
+ }
+ }
+ """
+ val SOURCE9 = """
+ package test.pkg;
+
+ public class PublicSubClass extends PublicClass {
+ @Override
+ public void abstractMethodOverridden() {
+ }
+
+ @Override
+ public abstract void abstractMethodOverriddenByAbstract();
+
+ @Override
+ public void systemMethodOverridden() {
+ }
+
+ @test.annotation.Hide
+ public void systemMethodHiddenInSub() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public void subMethod() {
+ }
+ }
+ """
+
+ val SOURCE_FILES_6_7_8_9 = arrayOf<TestFile>(java(SOURCE6), java(SOURCE7), java(SOURCE8), java(SOURCE9))
+
+ @Test
+ fun `Complecated case - SystemApi + ModuleApi`() {
+ check(
+ extraArguments = EXTRA_ARGS,
+ format = FileFormat.V2,
+ hideAnnotations = arrayOf(HIDE_ANNOTATION),
+ showAnnotations = arrayOf(SYSTEM_API, MODULE_API),
+ sourceFiles = SOURCE_FILES_6_7_8_9,
+ api = """
+ // Signature format: 2.0
+ package test.pkg {
+ public class ModuleClass {
+ ctor public ModuleClass();
+ method public void foo();
+ }
+ public class ModuleClassExtendingPublic extends test.pkg.PublicSubClass {
+ ctor public ModuleClassExtendingPublic();
+ method public void bar();
+ }
+ public class PublicClass {
+ method public abstract void abstractMethodNotOverridden();
+ method public abstract void abstractMethodOverridden();
+ method public abstract void abstractMethodOverriddenByAbstract();
+ method public void systemMethodHiddenInSub();
+ method public void systemMethodNotOverridden();
+ method public void systemMethodOverridden();
+ }
+ public class PublicSubClass extends test.pkg.PublicClass {
+ method public void abstractMethodOverridden();
+ method public void subMethod();
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Complecated case - SystemApi only`() {
+ check(
+ extraArguments = EXTRA_ARGS,
+ format = FileFormat.V2,
+ hideAnnotations = arrayOf(HIDE_ANNOTATION),
+ showAnnotations = arrayOf(SYSTEM_API),
+ sourceFiles = SOURCE_FILES_6_7_8_9,
+ api = """
+ // Signature format: 2.0
+ package test.pkg {
+ public class PublicClass {
+ method public abstract void abstractMethodNotOverridden();
+ method public abstract void abstractMethodOverridden();
+ method public abstract void abstractMethodOverriddenByAbstract();
+ method public void systemMethodHiddenInSub();
+ method public void systemMethodNotOverridden();
+ method public void systemMethodOverridden();
+ }
+ public class PublicSubClass extends test.pkg.PublicClass {
+ method public void abstractMethodOverridden();
+ method public void subMethod();
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Hierarchy test - Module API only`() {
+
+ check(
+ extraArguments = EXTRA_ARGS,
+ format = FileFormat.V2,
+ hideAnnotations = arrayOf(HIDE_ANNOTATION),
+ showAnnotations = arrayOf(MODULE_API),
+ showForStubPurposesAnnotations = arrayOf(SYSTEM_API),
+ sourceFiles = SOURCE_FILES_6_7_8_9,
+ api = """
+ // Signature format: 2.0
+ package test.pkg {
+ public class ModuleClass {
+ ctor public ModuleClass();
+ method public void foo();
+ }
+ public class ModuleClassExtendingPublic extends test.pkg.PublicSubClass {
+ ctor public ModuleClassExtendingPublic();
+ method public void bar();
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Hierarchy test - Module API only with lint`() {
+ check(
+ extraArguments = EXTRA_ARGS,
+ format = FileFormat.V2,
+ hideAnnotations = arrayOf(HIDE_ANNOTATION),
+ showAnnotations = arrayOf(MODULE_API),
+ showForStubPurposesAnnotations = arrayOf(SYSTEM_API),
+
+ // The methods are missing nullability anotations, the lint shouldn't report for
+ // the SystemApi one.
+ sourceFiles = arrayOf(java("""
+ package test.pkg;
+
+ @test.annotation.Hide
+ @test.annotation.SystemApi
+ public class ModuleClassExtendingPublic extends PublicSubClass {
+ public Object method1() {
+ }
+
+ @test.annotation.Hide
+ @test.annotation.ModuleApi
+ public Object method2() {
+ }
+ }
+ """)),
+ api = """
+ // Signature format: 2.0
+ package test.pkg {
+ public class ModuleClassExtendingPublic {
+ method public Object method2();
+ }
+ }
+ """,
+ apiLint = "",
+ expectedFail = """
+ 1 new API lint issues were found.
+ See tools/metalava/API-LINT.md for how to handle these.
+ """,
+ expectedIssues = """
+ src/test/pkg/ModuleClassExtendingPublic.java:9: error: Missing nullability on method `method2` return [MissingNullability]
+ """
+ )
+ }
+}
diff --git a/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt b/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
index e7ac68e..9250aa6 100644
--- a/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
@@ -24,8 +24,8 @@
import java.io.File
class TextBackedAnnotationItemTest {
- // Dummy for use in test where we don't need codebase functionality
- private val dummyCodebase = object : DefaultCodebase(File("").canonicalFile) {
+ // Placeholder for use in test where we don't need codebase functionality
+ private val placeholderCodebase = object : DefaultCodebase(File("").canonicalFile) {
override fun supportsDocumentation(): Boolean = false
override var description: String = ""
override fun getPackages(): PackageList = unsupported()
@@ -38,7 +38,7 @@
@Test
fun testSimple() {
val annotation = TextBackedAnnotationItem(
- dummyCodebase,
+ placeholderCodebase,
"@androidx.annotation.Nullable"
)
assertEquals("@androidx.annotation.Nullable", annotation.toSource())
@@ -49,7 +49,7 @@
@Test
fun testIntRange() {
val annotation = TextBackedAnnotationItem(
- dummyCodebase,
+ placeholderCodebase,
"@androidx.annotation.IntRange(from = 20, to = 40)"
)
assertEquals("@androidx.annotation.IntRange(from = 20, to = 40)", annotation.toSource())
@@ -64,7 +64,7 @@
@Test
fun testIntDef() {
val annotation = TextBackedAnnotationItem(
- dummyCodebase,
+ placeholderCodebase,
"@androidx.annotation.IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})"
)
assertEquals(
diff --git a/src/test/java/com/android/tools/metalava/model/psi/JavadocTest.kt b/src/test/java/com/android/tools/metalava/model/psi/JavadocTest.kt
index 6073824..b3365b2 100644
--- a/src/test/java/com/android/tools/metalava/model/psi/JavadocTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/psi/JavadocTest.kt
@@ -288,7 +288,6 @@
"""
package test.pkg1;
import java.io.IOException;
- import test.pkg2.OtherClass;
@SuppressWarnings("all")
public class R {
@@ -402,6 +401,12 @@
}
}
"""
+ ),
+ java(
+ """
+ package android.view.accessibility;
+ public class AccessibilityNodeInfo {}
+ """
)
),
warnings = "",
@@ -435,7 +440,7 @@
import android.os.OperationCanceledException;
@SuppressWarnings("all")
- public abstract class AsyncTaskLoader {
+ public abstract class AsyncTaskLoader<D> {
/**
* Called if the task was canceled before it was completed. Gives the class a chance
* to clean up post-cancellation and to properly dispose of the result.
@@ -506,7 +511,7 @@
package android.content;
import android.os.OperationCanceledException;
@SuppressWarnings({"unchecked", "deprecation", "all"})
- public abstract class AsyncTaskLoader {
+ public abstract class AsyncTaskLoader<D> {
public AsyncTaskLoader() { throw new RuntimeException("Stub!"); }
/**
* Called if the task was canceled before it was completed. Gives the class a chance
@@ -656,6 +661,8 @@
import test.pkg1.MyParent;
@SuppressWarnings("all")
public class MyChild extends MyParent implements MyConstants {
+ @Override
+ public void close() {}
}
"""
)
@@ -812,7 +819,7 @@
import java.nio.ByteBuffer;
@SuppressWarnings("all")
- public class Test {
+ public abstract class Test {
/**
* Blah blah
* <blockquote><pre>
@@ -832,7 +839,7 @@
package test.pkg1;
import java.nio.ByteBuffer;
@SuppressWarnings({"unchecked", "deprecation", "all"})
- public class Test {
+ public abstract class Test {
public Test() { throw new RuntimeException("Stub!"); }
/**
* Blah blah
@@ -908,7 +915,6 @@
package android.view;
import android.graphics.Insets;
-
public final class WindowInsets {
/**
* Returns a copy of this WindowInsets with selected system window insets replaced
@@ -940,12 +946,20 @@
}
}
"""
+ ),
+ java(
+ """
+ package android.graphics;
+ public class Insets {
+ }
+ """
)
),
docStubs = true,
stubs = arrayOf(
"""
package android.view;
+ import android.graphics.Insets;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public final class WindowInsets {
public WindowInsets() { throw new RuntimeException("Stub!"); }
@@ -959,7 +973,7 @@
* @param bottom New bottom inset in pixels
* @return A modified copy of this WindowInsets
* @deprecated use {@link android.view.WindowInsets.Builder#Builder(android.view.WindowInsets) Builder#Builder(WindowInsets)} with
- * {@link android.view.WindowInsets.Builder#setSystemWindowInsets(Insets) Builder#setSystemWindowInsets(Insets)} instead.
+ * {@link android.view.WindowInsets.Builder#setSystemWindowInsets(android.graphics.Insets) Builder#setSystemWindowInsets(Insets)} instead.
*/
@Deprecated
public android.view.WindowInsets replaceSystemWindowInsets(int left, int top, int right, int bottom) { throw new RuntimeException("Stub!"); }
@@ -967,7 +981,7 @@
public static class Builder {
public Builder() { throw new RuntimeException("Stub!"); }
public Builder(android.view.WindowInsets insets) { throw new RuntimeException("Stub!"); }
- public android.view.WindowInsets.Builder setSystemWindowInsets(Insets systemWindowInsets) { throw new RuntimeException("Stub!"); }
+ public android.view.WindowInsets.Builder setSystemWindowInsets(android.graphics.Insets systemWindowInsets) { throw new RuntimeException("Stub!"); }
}
}
"""
@@ -1058,4 +1072,4 @@
)
)
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/com/android/tools/metalava/model/psi/PsiBasedCodebaseTest.kt b/src/test/java/com/android/tools/metalava/model/psi/PsiBasedCodebaseTest.kt
index d95b678..4f7a5d3 100644
--- a/src/test/java/com/android/tools/metalava/model/psi/PsiBasedCodebaseTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/psi/PsiBasedCodebaseTest.kt
@@ -108,11 +108,14 @@
fun `Invalid syntax`() {
check(
expectedIssues = """
- src/test/pkg/Foo.java:3: error: Syntax error: `'{' or ';' expected` [InvalidSyntax]
+ src/test/pkg/Foo.java:1: info: Unresolved import: `nonexistent.path` [UnresolvedImport]
+ src/test/pkg/Foo.java:5: error: Syntax error: `'{' or ';' expected` [InvalidSyntax]
""",
sourceFiles = arrayOf(
java(
"""
+ import nonexistent.path;
+
package test.pkg;
public class Foo {
public void foo()
@@ -122,4 +125,4 @@
)
)
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/com/android/tools/metalava/model/psi/PsiTypePrinterTest.kt b/src/test/java/com/android/tools/metalava/model/psi/PsiTypePrinterTest.kt
index 9992ee6..3cee501 100644
--- a/src/test/java/com/android/tools/metalava/model/psi/PsiTypePrinterTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/psi/PsiTypePrinterTest.kt
@@ -16,9 +16,10 @@
package com.android.tools.metalava.model.psi
-import com.android.tools.lint.LintCoreApplicationEnvironment
import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.metalava.Compatibility
import com.android.tools.metalava.DriverTest
+import com.android.tools.metalava.compatibility
import com.android.tools.metalava.libcoreNonNullSource
import com.android.tools.metalava.libcoreNullableSource
import com.android.tools.metalava.model.AnnotationItem
@@ -26,7 +27,6 @@
import com.android.tools.metalava.nonNullSource
import com.android.tools.metalava.nullableSource
import com.android.tools.metalava.parseSources
-import com.intellij.openapi.util.Disposer
import com.intellij.psi.JavaRecursiveElementVisitor
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiType
@@ -45,7 +45,7 @@
import java.util.function.Predicate
class PsiTypePrinterTest : DriverTest() {
- // @Test
+ @Test
fun `Test class reference types`() {
assertEquals(
"""
@@ -80,7 +80,7 @@
Type: PsiClassReferenceType
Canonical: java.util.Map<java.lang.String,java.lang.Number>
- Printed: java.util.Map<java.lang.String!, java.lang.Number!>!
+ Printed: java.util.Map<java.lang.String!,java.lang.Number!>!
Type: PsiClassReferenceType
Canonical: java.lang.Number
@@ -122,7 +122,7 @@
)
}
- // @Test
+ @Test
fun `Test class reference types without Kotlin style nulls`() {
assertEquals(
"""
@@ -175,7 +175,7 @@
)
}
- // @Test
+ @Test
fun `Test merge annotations`() {
assertEquals(
"""
@@ -197,7 +197,7 @@
Type: PsiClassReferenceType
Canonical: java.util.Map<java.lang.String,java.lang.Number>
Merged: [@Nullable]
- Printed: java.util.Map<java.lang.String!, java.lang.Number!>?
+ Printed: java.util.Map<java.lang.String!,java.lang.Number!>?
Type: PsiClassReferenceType
Canonical: java.lang.Number
@@ -240,7 +240,7 @@
)
}
- // @Test
+ @Test
fun `Check other annotations than nullness annotations`() {
assertEquals(
"""
@@ -275,7 +275,7 @@
)
}
- // @Test
+ @Test
fun `Test negative filtering`() {
assertEquals(
"""
@@ -312,7 +312,7 @@
)
}
- // @Test
+ @Test
fun `Test positive filtering`() {
assertEquals(
"""
@@ -445,7 +445,7 @@
)
}
- // @Test
+ @Test
fun `Test arrays`() {
assertEquals(
"""
@@ -498,7 +498,7 @@
)
}
- // @Test
+ @Test
fun `Test ellipsis types`() {
assertEquals(
"""
@@ -538,7 +538,7 @@
)
}
- // @Test
+ @Test
fun `Test wildcard type`() {
assertEquals(
"""
@@ -597,7 +597,7 @@
)
}
- // @Test
+ @Test
fun `Test primitives in arrays cannot be null`() {
assertEquals(
"""
@@ -721,7 +721,7 @@
)
}
- // @Test
+ @Test
fun `Test type bounds`() {
assertEquals(
"""
@@ -739,7 +739,7 @@
Type: PsiClassReferenceType
Canonical: java.util.Map<? extends java.lang.Number,? super java.lang.Number>
- Printed: java.util.Map<? extends java.lang.Number, ? super java.lang.Number>!
+ Printed: java.util.Map<? extends java.lang.Number,? super java.lang.Number>!
Type: PsiWildcardType
Canonical: ? super java.lang.Number
@@ -808,7 +808,10 @@
classPath.add(file)
}
}
+ classPath.add(getPlatformFile("android.jar"))
+ // TestDriver#check normally sets this for all the other tests
+ compatibility = Compatibility(false)
val codebase = parseSources(
sourceFiles, "test project",
sourcePath = sourcePath, classpath = classPath
@@ -858,7 +861,7 @@
for (unit in codebase.units) {
unit.toUElement()?.accept(object : AbstractUastVisitor() {
override fun visitMethod(node: UMethod): Boolean {
- handle(node.returnType, node.annotations)
+ handle(node.returnType, node.uAnnotations)
// Visit all the type elements in the method: this helps us pick up
// the type parameter lists for example which contains some interesting
@@ -874,7 +877,7 @@
}
override fun visitVariable(node: UVariable): Boolean {
- handle(node.type, node.annotations)
+ handle(node.type, node.uAnnotations)
return super.visitVariable(node)
}
@@ -927,8 +930,6 @@
printWriter.printf("Printed: %s\n\n", string)
}
- Disposer.dispose(LintCoreApplicationEnvironment.get().parentDisposable)
-
return writer.toString().removeSuffix("\n\n")
}
diff --git a/src/test/java/com/android/tools/metalava/model/text/TextModifiersTest.kt b/src/test/java/com/android/tools/metalava/model/text/TextModifiersTest.kt
index 78271b2..0be75e9 100644
--- a/src/test/java/com/android/tools/metalava/model/text/TextModifiersTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/text/TextModifiersTest.kt
@@ -16,7 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.ApiFile
import com.android.tools.metalava.model.DefaultModifierList
import org.junit.Test
import kotlin.test.assertFalse
diff --git a/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt b/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt
index 0d97bef..0316cf5 100644
--- a/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt
@@ -16,7 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.ApiFile
import com.google.common.truth.Truth.assertThat
import org.junit.Test
diff --git a/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt b/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt
index 8dc559c..44d4195 100644
--- a/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/text/TextTypeParameterItemTest.kt
@@ -16,7 +16,6 @@
package com.android.tools.metalava.model.text
-import com.android.tools.metalava.doclava1.ApiFile
import com.android.tools.metalava.model.text.TextTypeParameterItem.Companion.bounds
import com.google.common.truth.Truth.assertThat
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 bd3b9a4..cef474e 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
@@ -32,7 +33,9 @@
import com.android.tools.metalava.gatherSources
import com.android.tools.metalava.intDefAnnotationSource
import com.android.tools.metalava.intRangeAnnotationSource
+import com.android.tools.metalava.libcoreNonNullSource
import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
+import com.android.tools.metalava.requiresApiSource
import com.android.tools.metalava.requiresPermissionSource
import com.android.tools.metalava.restrictToSource
import com.android.tools.metalava.supportParameterName
@@ -567,7 +570,7 @@
api = """
package test.pkg {
public final class Alignment extends java.lang.Enum {
- method public static test.pkg.Alignment valueOf(java.lang.String);
+ method public static test.pkg.Alignment valueOf(java.lang.String) throws java.lang.IllegalArgumentException;
method public static final test.pkg.Alignment[] values();
enum_constant public static final test.pkg.Alignment ALIGN_CENTER;
enum_constant public static final test.pkg.Alignment ALIGN_NORMAL;
@@ -736,6 +739,7 @@
B,
A;
public java.lang.String valueOf(int x) { throw new RuntimeException("Stub!"); }
+ public java.lang.String values(java.lang.String separator) { throw new RuntimeException("Stub!"); }
public java.lang.String toString() { throw new RuntimeException("Stub!"); }
}
"""
@@ -1954,7 +1958,9 @@
fun `Pass through libcore annotations`() {
check(
checkCompilation = true,
- extraArguments = arrayOf(ARG_PASS_THROUGH_ANNOTATION, "libcore.util.NonNull"),
+ extraArguments = arrayOf(
+ ARG_PASS_THROUGH_ANNOTATION, "libcore.util.NonNull"
+ ),
sourceFiles = arrayOf(
java(
"""
@@ -1963,10 +1969,15 @@
public String(@libcore.util.NonNull char[] value) { throw new RuntimeException("Stub!"); }
}
"""
- )
+ ),
+ libcoreNonNullSource
),
expectedIssues = "",
api = """
+ package libcore.util {
+ public abstract class NonNull implements java.lang.annotation.Annotation {
+ }
+ }
package my.pkg {
public class String {
ctor public String(char[]);
@@ -1988,29 +1999,33 @@
fun `Pass through multiple annotations`() {
checkStubs(
extraArguments = arrayOf(
- ARG_PASS_THROUGH_ANNOTATION, "android.support.annotation.RequiresApi,android.support.annotation.Nullable"),
+ ARG_PASS_THROUGH_ANNOTATION, "androidx.annotation.RequiresApi,androidx.annotation.Nullable",
+ ARG_HIDE_PACKAGE, "androidx.annotation"
+ ),
sourceFiles = arrayOf(
java(
"""
package my.pkg;
public class MyClass {
- @android.support.annotation.RequiresApi(21)
+ @androidx.annotation.RequiresApi(21)
public void testMethod() {}
- @android.support.annotation.Nullable
+ @androidx.annotation.Nullable
public String anotherTestMethod() { return null; }
}
"""
),
- supportParameterName
+ supportParameterName,
+ requiresApiSource,
+ androidxNullableSource
),
source = """
package my.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass {
public MyClass() { throw new RuntimeException("Stub!"); }
- @android.support.annotation.RequiresApi(21)
+ @androidx.annotation.RequiresApi(21)
public void testMethod() { throw new RuntimeException("Stub!"); }
- @android.support.annotation.Nullable
+ @androidx.annotation.Nullable
public java.lang.String anotherTestMethod() { throw new RuntimeException("Stub!"); }
}
"""
@@ -2126,7 +2141,7 @@
checkStubs(
sourceFiles = arrayOf(
// TODO: Try using prefixes like "A", and "AA" to make sure my generics
- // variable renaming doesn't do something really dumb
+ // variable renaming doesn't do something really unexpected
java(
"""
package test.pkg;
@@ -3260,7 +3275,7 @@
sourceFiles = arrayOf(
java(
"""
- package java.lang;
+ package com.android.metalava.test;
import java.lang.annotation.*;
@@ -3273,7 +3288,7 @@
),
warnings = "",
source = """
- package java.lang;
+ package com.android.metalava.test;
@SuppressWarnings({"unchecked", "deprecation", "all"})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
@java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD)
@@ -3291,7 +3306,7 @@
sourceFiles = arrayOf(
java(
"""
- package java.lang;
+ package com.android.metalava.test;
@SuppressWarnings("something") @FunctionalInterface
public interface MyInterface {
@@ -3302,7 +3317,7 @@
),
warnings = "",
source = """
- package java.lang;
+ package com.android.metalava.test;
@SuppressWarnings({"unchecked", "deprecation", "all"})
@java.lang.FunctionalInterface
public interface MyInterface {
@@ -3331,7 +3346,6 @@
}
"""
),
-
androidxNullableSource
),
warnings = "",
@@ -3343,10 +3357,15 @@
}
""", // WRONG: I should include package annotations in the signature file!
source = """
- @android.annotation.Nullable
+ @androidx.annotation.Nullable
package test.pkg;
""",
- extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
+ extraArguments = arrayOf(
+ ARG_HIDE_PACKAGE, "androidx.annotation",
+ // By default metalava rewrites androidx.annotation.Nullable to
+ // android.annotation.Nullable, but the latter does not have target PACKAGE thus
+ // fails to compile. This forces stubs keep the androidx annotation.
+ ARG_PASS_THROUGH_ANNOTATION, "androidx.annotation.Nullable")
)
}
@@ -3394,7 +3413,7 @@
java(
"""
@RestrictTo(RestrictTo.Scope.SUBCLASSES)
- package test.pkg;1
+ package test.pkg;
import androidx.annotation.RestrictTo;
"""
@@ -3404,7 +3423,7 @@
),
api = """
- package @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) test.pkg {
+ package @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) test.pkg {
public abstract class Class1 {
ctor public Class1();
}
@@ -3724,6 +3743,17 @@
),
java(
"""
+ package android.view;
+
+ public class Gravity {
+ public static final int NO_GRAVITY = 0;
+ public static final int TOP = 1;
+ public static final int BOTTOM = 2;
+ }
+ """
+ ),
+ java(
+ """
package test.pkg;
import java.lang.annotation.ElementType;
@@ -3942,19 +3972,6 @@
public class PublicInterface {
public PublicInterface() { throw new RuntimeException("Stub!"); }
}
- """,
- """
- package test.pkg;
- @SuppressWarnings({"unchecked", "deprecation", "all"})
- final class HiddenType {
- }
- """,
- """
- package test.pkg;
- /** @hide */
- @SuppressWarnings({"unchecked", "deprecation", "all"})
- public class HiddenType4 {
- }
"""
)
)
@@ -3998,10 +4015,6 @@
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class PublicApi {
public PublicApi(test.pkg.PublicApi.HiddenInner inner) { throw new RuntimeException("Stub!"); }
- /** @hide */
- @SuppressWarnings({"unchecked", "deprecation", "all"})
- public static class HiddenInner {
- }
}
"""
)
@@ -4212,6 +4225,155 @@
)
}
+ @Test
+ fun `Basic Kotlin stubs`() {
+ check(
+ 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
+ }
+ """
+ ),
+ kotlin("""
+ package test.pkg
+ open class ExtendableClass<T>
+ """
+ )
+ ),
+ stubs = arrayOf(
+ """
+ /* My file header */
+ // Another comment
+ package test.pkg
+ /** My class doc */
+ @file:Suppress("ALL")
+ class Kotlin : test.pkg.Parent() {
+ open fun Kotlin(open property1: java.lang.String!, open arg2: int): test.pkg.Kotlin! = error("Stub!")
+ open fun method(): java.lang.String = error("Stub!")
+ /** My method doc */
+ open fun otherMethod(open ok: boolean, open times: int): void = error("Stub!")
+ open fun getProperty1(): java.lang.String = error("Stub!")
+ }
+ """,
+ """
+ package test.pkg
+ @file:Suppress("ALL")
+ open class ExtendableClass<T> {
+ open fun ExtendableClass(): test.pkg.ExtendableClass<T!>! = error("Stub!")
+ }
+ """
+ )
+ )
+ }
+
+ @Test
+ fun `Extends and implements multiple interfaces in Kotlin Stubs`() {
+ check(
+ extraArguments = arrayOf(
+ ARG_KOTLIN_STUBS
+ ),
+ sourceFiles = arrayOf(
+ kotlin("""
+ package test.pkg
+ class MainClass: MyParentClass(), MyInterface1, MyInterface2
+
+ open class MyParentClass
+ interface MyInterface1
+ interface MyInterface2
+ """)
+ ),
+ stubs = arrayOf(
+ """
+ package test.pkg
+ @file:Suppress("ALL")
+ class MainClass : test.pkg.MyParentClass(), test.pkg.MyInterface1, test.pkg.MyInterface2 {
+ open fun MainClass(): test.pkg.MainClass! = error("Stub!")
+ }
+ """
+ )
+ )
+ }
+
+ @Test
+ fun `Extends and implements multiple interfaces`() {
+ check(
+ checkCompilation = true,
+ sourceFiles = arrayOf(
+ java(
+ """
+ package test.pkg;
+
+ public class MainClass extends MyParentClass implements MyInterface1, MyInterface2 {
+ }
+ """
+ ),
+ java(
+ """
+ package test.pkg;
+
+ public interface MyInterface1 { }
+ """
+ ),
+ java(
+ """
+ package test.pkg;
+
+ public interface MyInterface2 { }
+ """
+ ),
+ java(
+ """
+ package test.pkg;
+
+ public class MyParentClass { }
+ """
+ )
+ ),
+ stubs = arrayOf(
+ """
+ package test.pkg;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class MainClass extends test.pkg.MyParentClass implements test.pkg.MyInterface1, test.pkg.MyInterface2 {
+ public MainClass() { throw new RuntimeException("Stub!"); }
+ }
+ """
+ )
+ )
+ }
+
// 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.