diff --git a/API-LINT.md b/API-LINT.md index 76b2d09277dda64a60a6685a5e4aa7d79a887c41..f32cbb062ce5672704c5242d5a1b50c97df8ebfd 100644 --- a/API-LINT.md +++ b/API-LINT.md @@ -44,18 +44,40 @@ it is silently ignored. You can pass a flag to metalava ("--update-baseline") to tell it to update the baseline files with any new errors it comes across instead of reporting -them. With soong, you'd do something like this: +them. With soong, if you specify the baseline explicitly, like this: --- a/Android.bp +++ b/Android.bp @@ -1678,6 +1678,7 @@ droidstubs { }, api_lint: true, - baseline_filename: "baseline.txt", - + update_baseline: true, + ==> baseline_filename: "api/baseline.txt", <== jdiff_enabled: true, } +then the build system will automatically supply `--update-baseline` on your +behalf to a generated file in the out/ folder, and if there's a failure metalava +will tell you where to find the updated baseline which you can then copy into +place: + + ... + 93 new API lint issues were found. See tools/metalava/API-LINT.md for how to handle these. + ************************************************************ + Your API changes are triggering API Lint warnings or errors. + To make these errors go away, you have two choices: + + 1. You can suppress the errors with @SuppressLint("<id>") + 2. You can update the baseline by executing the following + command: + cp \ + out/soong/.intermediates/frameworks/base/system-api-stubs-docs/android_common/api/system-baseline.txt \ + frameworks/base/api/system-baseline.txt + To submit the revised baseline.txt to the main Android + repository, you will need approval. + ************************************************************ + + + Then re-run the build and you should now see diffs to the baseline file; git diff to make sure you're really only marking the issues you intended to include. diff --git a/Android.bp b/Android.bp index 669e14a3cb8e78e8e157518c0a464581e4e573dc..c6ad0b3545efd0bffba24e17e9a944a422c30889 100644 --- a/Android.bp +++ b/Android.bp @@ -18,7 +18,7 @@ java_binary_host { "src/main/java/**/*.java", "src/main/java/**/*.kt", ], - java_resources: ["src/main/resources/**/*"], + java_resource_dirs: ["src/main/resources/"], static_libs: [ "kotlin-reflect", "metalava-tools-common-m2-deps", diff --git a/build.gradle b/build.gradle index eedd62afb7b259591a580a5bcf9b253cd010713c..1fb56b1b6507ea6b2384e4893c5240c44b04ade8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { - ext.gradle_version = '3.2.1' - ext.studio_version = '26.2.1' - ext.kotlin_version = '1.3.11' + ext.gradle_version = '3.4.0-beta01' + ext.studio_version = '26.4.0-beta01' + ext.kotlin_version = '1.3.20' repositories { google() jcenter() @@ -66,7 +66,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2' + classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.4' } } apply plugin: 'com.github.johnrengelman.shadow' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7e98c337e7d3b987786c1e9498d294ccdc673c90..eb78b8ef41b470cbf6f61351497b5981d1d3008a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,4 +4,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-milestone-1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1-all.zip diff --git a/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt b/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt index d9a276de3a3ee9920491f3d8c6b8b4517b53d553..760ebfbb821e05609c9b977dcf81cfcac7d66e74 100644 --- a/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt +++ b/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt @@ -26,6 +26,7 @@ import java.io.IOException import java.io.PrintWriter import java.io.StringWriter import java.util.function.Predicate +import kotlin.text.Charsets.UTF_8 /** * The [AnnotationsDiffer] can take a codebase with annotations, and subtract @@ -135,7 +136,7 @@ class AnnotationsDiffer( } } - apiFile.writeText(cleanedUp, Charsets.UTF_8) + apiFile.writeText(cleanedUp, UTF_8) } catch (e: IOException) { reporter.report(Errors.IO_ERROR, apiFile, "Cannot open file for write.") } diff --git a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt index e7814c1e761679fe3aa0745b1ca42f8bf9740c61..76d430d61207d99750541ffda78e42a2b6bfdd72 100644 --- a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt +++ b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt @@ -56,13 +56,17 @@ import com.android.tools.metalava.model.ClassItem import com.android.tools.metalava.model.Codebase import com.android.tools.metalava.model.DefaultAnnotationItem import com.android.tools.metalava.model.DefaultAnnotationValue +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.ModifierList +import com.android.tools.metalava.model.ParameterItem +import com.android.tools.metalava.model.TypeItem import com.android.tools.metalava.model.parseDocument import com.android.tools.metalava.model.psi.PsiAnnotationItem import com.android.tools.metalava.model.psi.PsiBasedCodebase +import com.android.tools.metalava.model.psi.PsiTypeItem import com.android.tools.metalava.model.visitors.ApiVisitor -import com.google.common.base.Charsets import com.google.common.io.ByteStreams import com.google.common.io.Closeables import com.google.common.io.Files @@ -76,6 +80,7 @@ import java.lang.reflect.Field import java.util.jar.JarInputStream import java.util.regex.Pattern import java.util.zip.ZipEntry +import kotlin.text.Charsets.UTF_8 /** Merges annotations into classes already registered in the given [Codebase] */ class AnnotationsMerger( @@ -152,7 +157,7 @@ class AnnotationsMerger( mergeFromJar(file) } else if (file.path.endsWith(DOT_XML)) { try { - val xml = Files.asCharSource(file, Charsets.UTF_8).read() + val xml = Files.asCharSource(file, UTF_8).read() mergeAnnotationsXml(file.path, xml) } catch (e: IOException) { error("Aborting: I/O problem during transform: " + e.toString()) @@ -182,7 +187,7 @@ class AnnotationsMerger( while (entry != null) { if (entry.name.endsWith(".xml")) { val bytes = ByteStreams.toByteArray(zis) - val xml = String(bytes, Charsets.UTF_8) + val xml = String(bytes, UTF_8) mergeAnnotationsXml(jar.path + ": " + entry, xml) } entry = zis.nextEntry @@ -240,32 +245,70 @@ class AnnotationsMerger( override fun compare(old: Item, new: Item) { val newModifiers = new.modifiers for (annotation in old.modifiers.annotations()) { - var addAnnotation = false - if (annotation.isNullnessAnnotation()) { - if (!newModifiers.hasNullnessInfo()) { - addAnnotation = true - } - } else { - // TODO: Check for other incompatibilities than nullness? - val qualifiedName = annotation.qualifiedName() ?: continue - if (newModifiers.findAnnotation(qualifiedName) == null) { - addAnnotation = true - } + mergeAnnotation(annotation, newModifiers, new) + } + } + + private fun mergeAnnotation( + annotation: AnnotationItem, + newModifiers: ModifierList, + new: Item + ) { + var addAnnotation = false + if (annotation.isNullnessAnnotation()) { + if (!newModifiers.hasNullnessInfo()) { + addAnnotation = true } + } else { + // TODO: Check for other incompatibilities than nullness? + val qualifiedName = annotation.qualifiedName() ?: return + if (newModifiers.findAnnotation(qualifiedName) == null) { + addAnnotation = true + } + } - if (addAnnotation) { - // Don't map annotation names - this would turn newly non null back into non null - new.mutableModifiers().addAnnotation( - new.codebase.createAnnotation( - annotation.toSource(), - new, - mapName = false - ) + if (addAnnotation) { + // Don't map annotation names - this would turn newly non null back into non null + new.mutableModifiers().addAnnotation( + new.codebase.createAnnotation( + annotation.toSource(), + new, + mapName = false ) + ) + } + } + + override fun compare(old: ParameterItem, new: ParameterItem) { + mergeTypeAnnotations(old.type(), new) + } + + override fun compare(old: FieldItem, new: FieldItem) { + mergeTypeAnnotations(old.type(), new) + } + + override fun compare(old: MethodItem, new: MethodItem) { + mergeTypeAnnotations(old.returnType(), new) + } + + // Merge in type annotations + private fun mergeTypeAnnotations( + typeItem: TypeItem?, + new: Item + ) { + typeItem ?: return + val type = (typeItem as? PsiTypeItem)?.psiType ?: return + val typeAnnotations = type.annotations + if (typeAnnotations.isNotEmpty()) { + for (annotation in typeAnnotations) { + val codebase = new.codebase as PsiBasedCodebase + val annotationItem = PsiAnnotationItem.create(codebase, annotation) + mergeAnnotation(annotationItem, new.modifiers, new) } } } } + CodebaseComparator().compare( visitor, externalCodebase, codebase, ApiPredicate() ) diff --git a/src/main/java/com/android/tools/metalava/ApiLint.kt b/src/main/java/com/android/tools/metalava/ApiLint.kt index abe0b2db9f773c7ce0288b2e60303d92ad446add..26cda4335e8cc84c9b306a6a01a88f2ac3719228 100644 --- a/src/main/java/com/android/tools/metalava/ApiLint.kt +++ b/src/main/java/com/android/tools/metalava/ApiLint.kt @@ -197,22 +197,26 @@ class ApiLint(private val codebase: Codebase, private val oldCodebase: Codebase? if (apiLintIssues > 0) { // We've reported API lint violations; emit some verbiage to explain // how to suppress the error rules. - options.stdout.println("\n$apiLintIssues new API lint issues were found. See tools/metalava/API-LINT.md for how to handle these.") + options.stdout.println("\n$apiLintIssues new API lint issues were found.") val baseline = options.baseline if (baseline?.updateFile != null && baseline.file != null && !baseline.silentUpdate) { options.stdout.println(""" - ****************************** + ************************************************************ Your API changes are triggering API Lint warnings or errors. To make these errors go away, you have two choices: - 1. You can suppress the errors with @SuppressLint("<id>") - 2. You can update the baseline by executing the following command: - cp \ - ${baseline.updateFile} \ - ${baseline.file} - To submit the revised baseline.txt to the main Android repository, - you will need approval. - ****************************** + + 1. You can suppress the errors with @SuppressLint("<id>") + 2. You can update the baseline by executing the following + command: + cp \ + ${baseline.updateFile} \ + ${baseline.file} + To submit the revised baseline.txt to the main Android + repository, you will need approval. + ************************************************************ """.trimIndent()) + } else { + options.stdout.println("See tools/metalava/API-LINT.md for how to handle these.") } } } diff --git a/src/main/java/com/android/tools/metalava/Baseline.kt b/src/main/java/com/android/tools/metalava/Baseline.kt index 335cee4abfa1c381ef11604c0ab5b20e9ed537f4..02c4f4c04d74137940b24cd928300373533bb9dc 100644 --- a/src/main/java/com/android/tools/metalava/Baseline.kt +++ b/src/main/java/com/android/tools/metalava/Baseline.kt @@ -35,6 +35,7 @@ import com.intellij.psi.PsiParameter import org.jetbrains.kotlin.psi.psiUtil.parameterIndex import java.io.File import java.io.PrintWriter +import kotlin.text.Charsets.UTF_8 const val DEFAULT_BASELINE_NAME = "baseline.txt" @@ -181,7 +182,7 @@ class Baseline( private fun read() { val file = this.file ?: return - val lines = file.readLines(Charsets.UTF_8) + val lines = file.readLines(UTF_8) for (i in 0 until lines.size - 1) { val line = lines[i] if (line.startsWith("//") || @@ -218,7 +219,7 @@ class Baseline( private fun write() { val updateFile = this.updateFile ?: return - if (!map.isEmpty()) { + if (!map.isEmpty() || !options.deleteEmptyBaselines) { val sb = StringBuilder() sb.append(format.header()) sb.append(headerComment) @@ -234,8 +235,13 @@ class Baseline( } sb.append("\n\n") } + + if (sb.endsWith("\n\n")) { + sb.setLength(sb.length - 2) + } + updateFile.parentFile?.mkdirs() - updateFile.writeText(sb.toString(), Charsets.UTF_8) + updateFile.writeText(sb.toString(), UTF_8) } else { updateFile.delete() } diff --git a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt index 77d3dc070ae397093bd82e6eb0c4775abc2afde7..6b703c8007b9ab84c790e5bc7c8a462ba2e80bd1 100644 --- a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt +++ b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt @@ -333,7 +333,17 @@ class CodebaseComparator { val simpleType2 = parameter2.type().toCanonicalType() delta = simpleType1.compareTo(simpleType2) if (delta != 0) { - break + // Special case: Kotlin coroutines + if (simpleType1.startsWith("kotlin.coroutines.") && simpleType2.startsWith("kotlin.coroutines.")) { + val t1 = simpleType1.removePrefix("kotlin.coroutines.").removePrefix("experimental.") + val t2 = simpleType2.removePrefix("kotlin.coroutines.").removePrefix("experimental.") + delta = t1.compareTo(t2) + if (delta != 0) { + break + } + } else { + break + } } } } diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt index 78aebe16ff64d43041b399eaee7c5cb14bc0392d..481aae827fde73dc209788b13f03296259e3b00a 100644 --- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt +++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt @@ -114,6 +114,9 @@ class CompatibilityCheck( return } val name = AnnotationItem.simpleName(oldNullnessAnnotation) + if (old.type()?.primitive == true) { + return + } report( Errors.INVALID_NULL_CONVERSION, new, "Attempted to remove $name annotation from ${describe(new)}" @@ -510,7 +513,7 @@ class CompatibilityCheck( } } - if (new.modifiers.isInline() && new.isKotlin()) { + if (new.modifiers.isInline()) { val oldTypes = old.typeParameterList().typeParameters() val newTypes = new.typeParameterList().typeParameters() for (i in 0 until oldTypes.size) { diff --git a/src/main/java/com/android/tools/metalava/Constants.kt b/src/main/java/com/android/tools/metalava/Constants.kt index 4e0a3c7318296344175a75d0a49690a90e8b5f50..59ffda5afd20f5534e256fea05f8b5f5d72e4903 100644 --- a/src/main/java/com/android/tools/metalava/Constants.kt +++ b/src/main/java/com/android/tools/metalava/Constants.kt @@ -32,6 +32,11 @@ const val ANDROID_SYSTEM_API = "android.annotation.SystemApi" const val ANDROID_REQUIRES_PERMISSION = "android.annotation.RequiresPermission" const val RECENTLY_NULLABLE = "androidx.annotation.RecentlyNullable" const val RECENTLY_NONNULL = "androidx.annotation.RecentlyNonNull" +const val ANDROID_NULLABLE = "android.annotation.Nullable" +const val ANDROID_NONNULL = "android.annotation.NonNull" +const val ANDROIDX_VISIBLE_FOR_TESTING = "androidx.annotation.VisibleForTesting" +const val ANDROID_SUPPORT_VISIBLE_FOR_TESTING = "android.support.annotation.VisibleForTesting" +const val ATTR_OTHERWISE = "otherwise" const val ENV_VAR_METALAVA_TESTS_RUNNING = "METALAVA_TESTS_RUNNING" const val ENV_VAR_METALAVA_DUMP_ARGV = "METALAVA_DUMP_ARGV" diff --git a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt index 44eed3b410b3c0c72b61d468d2f5bfad8846b55b..b831a5b39dccb1e9c8117f4f7ac00ed08ae60f0c 100644 --- a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt +++ b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt @@ -224,7 +224,8 @@ class DocAnalyzer( } private fun handleKotlinDeprecation(annotation: AnnotationItem, item: Item) { - val text = annotation.findAttribute(ATTR_VALUE)?.value?.value()?.toString() ?: return + val text = (annotation.findAttribute("message") ?: annotation.findAttribute(ATTR_VALUE)) + ?.value?.value()?.toString() ?: return if (text.isBlank() || item.documentation.contains(text)) { return } diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt index 400c139b48cbaaccce9d7590f0b54c1546039bac..c455923ac760d109a489fd7a2f007e5c6caaa45f 100644 --- a/src/main/java/com/android/tools/metalava/Driver.kt +++ b/src/main/java/com/android/tools/metalava/Driver.kt @@ -48,9 +48,13 @@ import com.android.utils.StdLogger.Level.ERROR import com.google.common.base.Stopwatch import com.google.common.collect.Lists 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.psi.javadoc.CustomJavadocTagProvider +import com.intellij.psi.javadoc.JavadocTagInfo import com.intellij.util.execution.ParametersListUtil import java.io.File import java.io.IOException @@ -139,7 +143,7 @@ fun run( options = Options(modifiedArgs, stdout, stderr) processFlags() - if (reporter.hasErrors() && !options.updateBaseline) { + if (reporter.hasErrors() && !options.passBaselineUpdates) { exitCode = -1 } exitValue = true @@ -154,13 +158,17 @@ fun run( } exitCode = e.exitCode exitValue = false + } finally { + Disposer.dispose(LintCoreApplicationEnvironment.get().parentDisposable) } if (options.updateBaseline) { if (options.verbose) { options.baseline?.dumpStats(options.stdout) } - stdout.println("$PROGRAM_NAME wrote updated baseline to ${options.baseline?.file}") + if (!options.quiet) { + stdout.println("$PROGRAM_NAME wrote updated baseline to ${options.baseline?.updateFile}") + } } options.baseline?.close() @@ -432,8 +440,6 @@ private fun processFlags() { AnnotationStatistics(codebase).measureCoverageOf(options.annotationCoverageOf) } - Disposer.dispose(LintCoreApplicationEnvironment.get().parentDisposable) - if (options.verbose) { val packageCount = codebase.size() options.stdout.println("\n$PROGRAM_NAME finished handling $packageCount packages in $stopwatch") @@ -731,7 +737,7 @@ fun invokeDocumentationTool() { class PrintWriterOutputStream(private val writer: PrintWriter) : OutputStream() { override fun write(b: ByteArray) { - writer.write(String(b, Charsets.UTF_8)) + writer.write(String(b, UTF_8)) } override fun write(b: Int) { @@ -739,7 +745,7 @@ class PrintWriterOutputStream(private val writer: PrintWriter) : OutputStream() } override fun write(b: ByteArray, off: Int, len: Int) { - writer.write(String(b, off, len, Charsets.UTF_8)) + writer.write(String(b, off, len, UTF_8)) } override fun flush() { @@ -898,7 +904,7 @@ fun loadFromJarFile(apiJar: File, manifest: File? = null, preFiltered: Boolean = private fun createProjectEnvironment(): LintCoreProjectEnvironment { ensurePsiFileCapacity() val appEnv = LintCoreApplicationEnvironment.get() - val parentDisposable = Disposer.newDisposable() + val parentDisposable = appEnv.parentDisposable if (!assertionsEnabled() && System.getenv(ENV_VAR_METALAVA_DUMP_ARGV) == null && @@ -907,7 +913,20 @@ private fun createProjectEnvironment(): LintCoreProjectEnvironment { DefaultLogger.disableStderrDumping(parentDisposable) } - return LintCoreProjectEnvironment.create(parentDisposable, appEnv) + val environment = LintCoreProjectEnvironment.create(parentDisposable, appEnv) + + // Missing service needed in metalava but not in lint: javadoc handling + environment.project.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 + ) + + return environment } private fun ensurePsiFileCapacity() { @@ -1018,7 +1037,7 @@ fun createReportFile( } val localTimer = Stopwatch.createStarted() try { - val writer = PrintWriter(Files.asCharSink(apiFile, Charsets.UTF_8).openBufferedStream()) + val writer = PrintWriter(Files.asCharSink(apiFile, UTF_8).openBufferedStream()) writer.use { printWriter -> val apiWriter = createVisitor(printWriter) codebase.accept(apiWriter) @@ -1139,7 +1158,7 @@ private fun addHiddenPackages( } else -> return } - var contents = Files.asCharSource(file, Charsets.UTF_8).read() + var contents = Files.asCharSource(file, UTF_8).read() if (javadoc) { contents = packageHtmlToJavadoc(contents) } @@ -1226,7 +1245,7 @@ private fun findRoot(file: File): File? { /** Finds the package of the given Java/Kotlin source file, if possible */ fun findPackage(file: File): String? { - val source = Files.asCharSource(file, Charsets.UTF_8).read() + val source = Files.asCharSource(file, UTF_8).read() return findPackage(source) } diff --git a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt index dc33b27f2c46bb9bba98c961e3dc0d21339196c5..9076f31d2d5fc73dd4b0bd037650b7fd9c6bc68b 100644 --- a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt +++ b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt @@ -34,8 +34,8 @@ import com.android.tools.metalava.model.psi.CodePrinter import com.android.tools.metalava.model.psi.PsiAnnotationItem import com.android.tools.metalava.model.psi.PsiClassItem import com.android.tools.metalava.model.psi.PsiMethodItem +import com.android.tools.metalava.model.psi.UAnnotationItem import com.android.tools.metalava.model.visitors.ApiVisitor -import com.google.common.base.Charsets import com.google.common.xml.XmlEscapers import com.intellij.psi.JavaRecursiveElementVisitor import com.intellij.psi.PsiAnnotation @@ -63,6 +63,7 @@ import java.io.StringWriter import java.util.ArrayList import java.util.jar.JarEntry import java.util.jar.JarOutputStream +import kotlin.text.Charsets.UTF_8 // Like the tools/base Extractor class, but limited to our own (mapped) AnnotationItems, // and only those with source retention (and in particular right now that just means the @@ -148,7 +149,7 @@ class ExtractAnnotations( } writer.println("</root>\n") writer.close() - val bytes = writer.contents.toByteArray(Charsets.UTF_8) + val bytes = writer.contents.toByteArray(UTF_8) zos.write(bytes) zos.closeEntry() } @@ -234,26 +235,33 @@ class ExtractAnnotations( ) } - if (typeDefAnnotation is PsiAnnotationItem && typeDefClass is PsiClassItem) { - val result = AnnotationHolder( - typeDefClass, typeDefAnnotation, - annotationLookup.findRealAnnotation( - typeDefAnnotation.psiAnnotation, - typeDefClass.psiClass, - null + val result = + if (typeDefAnnotation is PsiAnnotationItem && typeDefClass is PsiClassItem) { + AnnotationHolder( + typeDefClass, typeDefAnnotation, + annotationLookup.findRealAnnotation( + typeDefAnnotation.psiAnnotation, + typeDefClass.psiClass, + null + ) ) - ) - classToAnnotationHolder[className] = result - addItem(item, result) - - if (item is PsiMethodItem && result.uAnnotation != null && - !reporter.isSuppressed(Errors.RETURNING_UNEXPECTED_CONSTANT) - ) { - verifyReturnedConstants(item, result.uAnnotation, result, className) + } else if (typeDefAnnotation is UAnnotationItem && typeDefClass is PsiClassItem) { + AnnotationHolder( + typeDefClass, typeDefAnnotation, typeDefAnnotation.uAnnotation + ) + } else { + continue } - continue + classToAnnotationHolder[className] = result + addItem(item, result) + + if (item is PsiMethodItem && result.uAnnotation != null && + !reporter.isSuppressed(Errors.RETURNING_UNEXPECTED_CONSTANT) + ) { + verifyReturnedConstants(item, result.uAnnotation, result, className) } + continue } } } @@ -469,11 +477,12 @@ class ExtractAnnotations( ) { val annotationItem = annotationHolder.annotationItem val uAnnotation = annotationHolder.uAnnotation - ?: if (annotationItem is PsiAnnotationItem) { - // Imported annotation - JavaUAnnotation.wrap(annotationItem.psiAnnotation) - } else { - return + ?: when (annotationItem) { + is UAnnotationItem -> annotationItem.uAnnotation + is PsiAnnotationItem -> + // Imported annotation + JavaUAnnotation.wrap(annotationItem.psiAnnotation) + else -> return } val qualifiedName = annotationItem.qualifiedName() diff --git a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt index a904af59aa4c320d1c3fcbc719203bbc40cf4a78..278d1c7fc4b92671307817852735d12a61b596cc 100644 --- a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt +++ b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt @@ -95,10 +95,8 @@ class KotlinInteropChecks { if (checked) { val annotation = method.modifiers.findAnnotation("kotlin.jvm.Throws") if (annotation != null) { - val attribute = - annotation.findAttribute("exceptionClasses") ?: annotation.findAttribute("value") - ?: annotation.attributes().firstOrNull() - if (attribute != null) { + // There can be multiple values + for (attribute in annotation.attributes()) { for (v in attribute.leafValues()) { val source = v.toSource() if (source.endsWith(exception.simpleName() + "::class")) { diff --git a/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt b/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt index a5833b288658b246cc562d24018bf5c560d56027..0bdba6725546a49c68e0a6d50be2c1d6291d76c9 100644 --- a/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt +++ b/src/main/java/com/android/tools/metalava/NullabilityAnnotationsValidator.kt @@ -28,7 +28,7 @@ import com.android.tools.metalava.model.visitors.ApiVisitor import com.google.common.io.Files import java.io.File import java.io.PrintWriter -import java.nio.charset.StandardCharsets +import kotlin.text.Charsets.UTF_8 private const val RETURN_LABEL = "return value" @@ -106,7 +106,7 @@ class NullabilityAnnotationsValidator { fun validateAllFrom(codebase: Codebase, topLevelClassesList: File?) { if (topLevelClassesList != null) { val classes = - Files.readLines(topLevelClassesList, StandardCharsets.UTF_8) + Files.readLines(topLevelClassesList, UTF_8) .filterNot { it.isBlank() } .map { it.trim() } .filterNot { it.startsWith("#") } @@ -212,7 +212,7 @@ class NullabilityAnnotationsValidator { // Non-fatal issues are written to the warnings .txt file if present, else logged. if (warningsTxtFile != null) { - PrintWriter(Files.asCharSink(warningsTxtFile, Charsets.UTF_8).openBufferedStream()).use { w -> + PrintWriter(Files.asCharSink(warningsTxtFile, UTF_8).openBufferedStream()).use { w -> nonFatalIssues.forEach { w.println(it) } } } else { diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt index 33a26da263eca01836e683ffc9d7a7fb77963d92..81f980fb5b63ea747a47aea6ef3ab1983f4d291e 100644 --- a/src/main/java/com/android/tools/metalava/Options.kt +++ b/src/main/java/com/android/tools/metalava/Options.kt @@ -33,6 +33,7 @@ import java.io.StringWriter import java.util.Locale import kotlin.reflect.KMutableProperty1 import kotlin.reflect.full.memberProperties +import kotlin.text.Charsets.UTF_8 /** Global options for the metadata extraction tool */ var options = Options(emptyArray()) @@ -136,6 +137,7 @@ const val ARG_REWRITE_ANNOTATIONS = "--rewrite-annotations" const val ARG_INCLUDE_SOURCE_RETENTION = "--include-source-retention" const val ARG_INCLUDE_SIG_VERSION = "--include-signature-version" const val ARG_UPDATE_API = "--update-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_BASELINE = "--baseline" @@ -143,9 +145,10 @@ const val ARG_UPDATE_BASELINE = "--update-baseline" const val ARG_MERGE_BASELINE = "--merge-baseline" const val ARG_STUB_PACKAGES = "--stub-packages" const val ARG_STUB_IMPORT_PACKAGES = "--stub-import-packages" +const val ARG_DELETE_EMPTY_BASELINES = "--delete-empty-baselines" class Options( - args: Array<String>, + private val args: Array<String>, /** Writer to direct output to */ var stdout: PrintWriter = PrintWriter(OutputStreamWriter(System.out)), /** Writer to direct error messages to */ @@ -502,6 +505,12 @@ class Options( /** Whether all baseline files need to be updated */ var updateBaseline = false + /** If updating baselines, don't fail the build */ + var passBaselineUpdates = false + + /** If updating baselines and the baseline is empty, delete the file */ + var deleteEmptyBaselines = false + /** Whether the baseline should only contain errors */ var baselineErrorsOnly = false @@ -568,6 +577,7 @@ class Options( var baselineFile: File? = null var mergeBaseline = false var delayedCheckApiFiles = false + var skipGenerateAnnotations = false var index = 0 while (index < args.size) { @@ -750,6 +760,8 @@ class Options( } } } + ARG_PASS_BASELINE_UPDATES -> passBaselineUpdates = true + ARG_DELETE_EMPTY_BASELINES -> deleteEmptyBaselines = true ARG_PUBLIC, "-public" -> docLevel = DocLevel.PUBLIC ARG_PROTECTED, "-protected" -> docLevel = DocLevel.PROTECTED @@ -1237,18 +1249,19 @@ class Options( true else yesNo(arg.substring(ARG_INCLUDE_SIG_VERSION.length + 1)) } else if (arg.startsWith(ARG_FORMAT)) { - when (arg) { + outputFormat = when (arg) { "$ARG_FORMAT=v1" -> { - FileFormat.V1.configureOptions(this, compatibility) + FileFormat.V1 } "$ARG_FORMAT=v2" -> { - FileFormat.V2.configureOptions(this, compatibility) + FileFormat.V2 } "$ARG_FORMAT=v3" -> { - FileFormat.V3.configureOptions(this, compatibility) + FileFormat.V3 } else -> throw DriverException(stderr = "Unexpected signature format; expected v1, v2 or v3") } + outputFormat.configureOptions(this, compatibility) } else if (arg.startsWith("-")) { // Compatibility flag; map to mutable properties in the Compatibility // class and assign it @@ -1293,6 +1306,12 @@ class Options( } else { // All args that don't start with "-" are taken to be filenames mutableSources.addAll(stringToExistingFiles(arg)) + + // Temporary workaround for + // aosp/I73ff403bfc3d9dfec71789a3e90f9f4ea95eabe3 + if (arg.endsWith("hwbinder-stubs-docs-stubs.srcjar.rsp")) { + skipGenerateAnnotations = true + } } } } @@ -1302,13 +1321,19 @@ class Options( } if (generateApiLevelXml != null) { - if (androidJarPatterns == null) { - androidJarPatterns = mutableListOf( - "prebuilts/tools/common/api-versions/android-%/android.jar", - "prebuilts/sdk/%/public/android.jar" - ) + val patterns = androidJarPatterns ?: run { + mutableListOf<String>() } - apiLevelJars = findAndroidJars(androidJarPatterns!!, currentApiLevel, currentCodeName, currentJar) + // Fallbacks + patterns.add("prebuilts/tools/common/api-versions/android-%/android.jar") + patterns.add("prebuilts/sdk/%/public/android.jar") + apiLevelJars = findAndroidJars(patterns, currentApiLevel, currentCodeName, currentJar) + } + + // outputKotlinStyleNulls implies format=v3 + if (outputKotlinStyleNulls) { + outputFormat = FileFormat.V3 + outputFormat.configureOptions(this, compatibility) } // If the caller has not explicitly requested that unannotated classes and @@ -1321,6 +1346,10 @@ class Options( allowReferencingUnknownClasses = false } + if (skipGenerateAnnotations) { + generateAnnotations = false + } + if (updateApi) { // We're running in update API mode: cancel other "action" flags; only signature file generation // flags count @@ -1349,6 +1378,9 @@ class Options( if (baselineFile == null) { val defaultBaselineFile = getDefaultBaselineFile() if (defaultBaselineFile != null && defaultBaselineFile.isFile) { + if (updateBaseline && updateBaselineFile == null) { + updateBaselineFile = defaultBaselineFile + } baseline = Baseline(defaultBaselineFile, updateBaselineFile, mergeBaseline) } else if (updateBaselineFile != null) { baseline = Baseline(null, updateBaselineFile, mergeBaseline) @@ -1359,6 +1391,9 @@ class Options( "// See tools/metalava/API-LINT.md for how to update this file.\n\n" else "" + if (updateBaseline && updateBaselineFile == null) { + updateBaselineFile = baselineFile + } baseline = Baseline(baselineFile, updateBaselineFile, mergeBaseline, headerComment) } @@ -1440,6 +1475,19 @@ class Options( if (verbose) { stdout.println("Last API level found: ${apiLevel - 1}") } + + if (apiLevel < 28) { + // Clearly something is wrong with the patterns; this should result in a build error + val argList = mutableListOf<String>() + args.forEachIndexed { index, arg -> + if (arg == ARG_ANDROID_JAR_PATTERN) { + argList.add(args[index + 1]) + } + } + throw DriverException(stderr = "Could not find android.jar for API level $apiLevel; the " + + "$ARG_ANDROID_JAR_PATTERN set might be invalid: ${argList.joinToString()}") + } + break } if (verbose) { @@ -1609,7 +1657,7 @@ class Options( if (!listFile.isFile) { throw DriverException("$listFile is not a file") } - val contents = Files.asCharSource(listFile, Charsets.UTF_8).read() + val contents = Files.asCharSource(listFile, UTF_8).read() val pathList = Splitter.on(CharMatcher.whitespace()).trimResults().omitEmptyStrings().split( contents ) @@ -1886,6 +1934,11 @@ class Options( "in the baseline, it will merge the existing baseline with the new baseline. This is useful " + "if $PROGRAM_NAME runs multiple times on the same source tree with different flags at different " + "times, such as occasionally with $ARG_API_LINT.", + ARG_PASS_BASELINE_UPDATES, "Normally, encountering error will fail the build, even when updating " + + "baselines. This flag allows you to tell $PROGRAM_NAME to continue without errors, such that " + + "all the baselines in the source tree can be updated in one go.", + ARG_DELETE_EMPTY_BASELINES, "Whether to delete baseline files if they are updated and there is nothing " + + "to include.", "", "\nJDiff:", "$ARG_XML_API <file>", "Like $ARG_API, but emits the API in the JDiff XML format instead", diff --git a/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt b/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt index 38f6a263d54fd12a0c7e354ad45026e4961407fe..d256e10631ea397fd3d37c32ffd92aedb4d233e8 100644 --- a/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt +++ b/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt @@ -37,6 +37,7 @@ import java.nio.file.attribute.FileTime import java.util.jar.JarEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream +import kotlin.text.Charsets.UTF_8 /** * Converts public stub annotation sources into package private annotation sources. @@ -61,7 +62,7 @@ class RewriteAnnotations { // Copy and convert target.parentFile.mkdirs() target.writeText( - source.readText(Charsets.UTF_8).replace( + source.readText(UTF_8).replace( "\npublic @interface", "\n@interface" ) @@ -102,9 +103,13 @@ class RewriteAnnotations { for (file in files) { // Jump directly into androidx/annotation if it appears we were invoked at the top level if (file.isDirectory) { - val annotations = File(file, "androidx${File.separator}annotation/") - if (annotations.isDirectory) { - rewriteAnnotations(annotations) + val android = File(file, "android${File.separator}annotation/") + if (android.isDirectory) { + rewriteAnnotations(android) + val androidx = File(file, "androidx${File.separator}annotation/") + if (androidx.isDirectory) { + rewriteAnnotations(androidx) + } continue } } @@ -118,8 +123,10 @@ class RewriteAnnotations { */ private fun hasSourceRetention(codebase: Codebase?, qualifiedName: String): Boolean { when { - qualifiedName == "androidx.annotation.RecentlyNullable" || - qualifiedName == "androidx.annotation.RecentlyNonNull" -> return false + qualifiedName == RECENTLY_NULLABLE || + qualifiedName == RECENTLY_NONNULL || + qualifiedName == ANDROID_NULLABLE || + qualifiedName == ANDROID_NONNULL -> return false qualifiedName.startsWith("androidx.annotation.") -> return true } @@ -171,10 +178,10 @@ class RewriteAnnotations { superName: String?, interfaces: Array<out String>? ) { - // Only process public annotations in androidx.annotation + // Only process public annotations in android.annotation and androidx.annotation if (access and Opcodes.ACC_PUBLIC != 0 && access and Opcodes.ACC_ANNOTATION != 0 && - name.startsWith("androidx/annotation/") + (name.startsWith("android/annotation/") || name.startsWith("androidx/annotation/")) ) { skip = false val flagsWithoutPublic = access and Opcodes.ACC_PUBLIC.inv() @@ -236,7 +243,7 @@ class RewriteAnnotations { // read the content of the entry from the input stream, and write it into the archive. if (name.endsWith(DOT_CLASS) && - name.startsWith("androidx/annotation/") && + (name.startsWith("android/annotation/") || name.startsWith("androidx/annotation/")) && name.indexOf("$") == -1 && !entry.isDirectory ) { diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java index 00f3eb3126cb9c21107824d7d5c82ca1a67614a9..7a28c4ea47332b31438e1e2a890778a4d9bd7735 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java +++ b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java @@ -33,7 +33,6 @@ 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.base.Charsets; import com.google.common.io.Files; import kotlin.Pair; import kotlin.text.StringsKt; @@ -52,6 +51,7 @@ import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_ANNOTATION; import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_ENUM; import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_STRING; import static com.android.tools.metalava.model.FieldItemKt.javaUnescapeString; +import static kotlin.text.Charsets.UTF_8; // // Copied from doclava1, but adapted to metalava's code model (plus tweaks to handle @@ -65,7 +65,7 @@ public class ApiFile { public static TextCodebase parseApi(File file, Boolean kotlinStyleNulls) throws ApiParseException { try { - String apiText = Files.asCharSource(file, Charsets.UTF_8).read(); + String apiText = Files.asCharSource(file, UTF_8).read(); return parseApi(file.getPath(), apiText, kotlinStyleNulls); } catch (IOException ex) { throw new ApiParseException("Error reading API file", ex); diff --git a/src/main/java/com/android/tools/metalava/doclava1/Errors.java b/src/main/java/com/android/tools/metalava/doclava1/Errors.java index 9587ae95bbad18c5cbb7d6c38201e6635812efff..b467735481c3b4dd567fee7e049f81926abeaf28 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/Errors.java +++ b/src/main/java/com/android/tools/metalava/doclava1/Errors.java @@ -206,7 +206,7 @@ public class Errors { public static final Error UNAVAILABLE_SYMBOL = new Error(110, WARNING, Category.DOCUMENTATION); public static final Error HIDDEN_SUPERCLASS = new Error(111, WARNING, Category.DOCUMENTATION); public static final Error DEPRECATED = new Error(112, HIDDEN, Category.DOCUMENTATION); - public static final Error DEPRECATION_MISMATCH = new Error(113, WARNING, Category.DOCUMENTATION); + public static final Error DEPRECATION_MISMATCH = new Error(113, ERROR, Category.DOCUMENTATION); public static final Error MISSING_COMMENT = new Error(114, LINT, Category.DOCUMENTATION); public static final Error IO_ERROR = new Error(115, ERROR); public static final Error NO_SINCE_DATA = new Error(116, HIDDEN, Category.DOCUMENTATION); 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 0e6921d22c624dec1744526a7a18ec2100aefbce..ae61fdd9cf9c3fb9935adac9c7aa30dd2923d1f9 100644 --- a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt +++ b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt @@ -27,6 +27,8 @@ import com.android.tools.lint.annotations.Extractor.ANDROID_STRING_DEF import com.android.tools.metalava.ANDROIDX_ANNOTATION_PREFIX import com.android.tools.metalava.ANDROIDX_NONNULL import com.android.tools.metalava.ANDROIDX_NULLABLE +import com.android.tools.metalava.ANDROID_NONNULL +import com.android.tools.metalava.ANDROID_NULLABLE import com.android.tools.metalava.ANDROID_SUPPORT_ANNOTATION_PREFIX import com.android.tools.metalava.Compatibility import com.android.tools.metalava.JAVA_LANG_PREFIX @@ -235,14 +237,21 @@ interface AnnotationItem { "android.annotation.Dimension" -> return "androidx.annotation.Dimension" // Null - "android.support.annotation.NonNull", - "android.annotation.NonNull" -> return "androidx.annotation.NonNull" + // We only change recently/newly nullable annotation in stubs + RECENTLY_NULLABLE -> return if (target == AnnotationTarget.SDK_STUBS_FILE) qualifiedName else ANDROIDX_NULLABLE + RECENTLY_NONNULL -> return if (target == AnnotationTarget.SDK_STUBS_FILE) qualifiedName else ANDROIDX_NONNULL + + ANDROIDX_NULLABLE, + ANDROID_NULLABLE, "android.support.annotation.Nullable", - "android.annotation.Nullable" -> return "androidx.annotation.Nullable" - "libcore.util.NonNull" -> return "androidx.annotation.NonNull" - "libcore.util.Nullable" -> return "androidx.annotation.Nullable" - "org.jetbrains.annotations.NotNull" -> return "androidx.annotation.NonNull" - "org.jetbrains.annotations.Nullable" -> return "androidx.annotation.Nullable" + "libcore.util.Nullable", + "org.jetbrains.annotations.Nullable" -> return if (target == AnnotationTarget.SDK_STUBS_FILE) ANDROID_NULLABLE else ANDROIDX_NULLABLE + + ANDROIDX_NONNULL, + ANDROID_NONNULL, + "android.support.annotation.NonNull", + "libcore.util.NonNull", + "org.jetbrains.annotations.NotNull" -> return if (target == AnnotationTarget.SDK_STUBS_FILE) ANDROID_NONNULL else ANDROIDX_NONNULL // Typedefs "android.support.annotation.IntDef", @@ -303,8 +312,6 @@ interface AnnotationItem { "android.annotation.TargetApi", "android.annotation.SuppressLint" -> return qualifiedName - RECENTLY_NULLABLE, RECENTLY_NONNULL -> return qualifiedName - else -> { // Some new annotations added to the platform: assume they are support annotations? return when { @@ -313,8 +320,8 @@ interface AnnotationItem { "kotlin.annotations.jvm.internal${qualifiedName.substring(qualifiedName.lastIndexOf('.'))}" // Other third party nullness annotations? - isNullableAnnotation(qualifiedName) -> "androidx.annotation.Nullable" - isNonNullAnnotation(qualifiedName) -> "androidx.annotation.NonNull" + isNullableAnnotation(qualifiedName) -> ANDROIDX_NULLABLE + isNonNullAnnotation(qualifiedName) -> ANDROIDX_NONNULL // Support library annotations are all included, as is the built-in stuff like @Retention qualifiedName.startsWith(ANDROIDX_ANNOTATION_PREFIX) -> return qualifiedName @@ -402,6 +409,16 @@ interface AnnotationItem { "java.lang.annotation.Target" -> return ANNOTATION_IN_ALL_STUBS } + // @android.annotation.Nullable and NonNullable specially recognized annotations by the Kotlin + // compiler 1.3 and above: they always go in the stubs. + if (qualifiedName == ANDROID_NULLABLE || + qualifiedName == ANDROID_NONNULL || + qualifiedName == ANDROIDX_NULLABLE || + qualifiedName == ANDROIDX_NONNULL + ) { + return ANNOTATION_IN_ALL_STUBS + } + if (qualifiedName.startsWith("android.annotation.")) { // internal annotations not mapped to androidx: things like @SystemApi. Skip from // stubs, external annotations, signature files, etc. @@ -410,10 +427,10 @@ interface AnnotationItem { // @RecentlyNullable and @RecentlyNonNull are specially recognized annotations by the Kotlin // compiler: they always go in the stubs. - if (qualifiedName == "androidx.annotation.RecentlyNullable" || - qualifiedName == "androidx.annotation.RecentlyNonNull" + if (qualifiedName == RECENTLY_NULLABLE || + qualifiedName == RECENTLY_NONNULL ) { - return ANNOTATION_IN_SDK_STUBS + return ANNOTATION_IN_ALL_STUBS } // Determine the retention of the annotation: source retention annotations go 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 7dc221b37a2f718ce5bf7df0c8ea7d37b95b5066..0d072fa6103a9faaf16cf3bc35319c1f7f368eff 100644 --- a/src/main/java/com/android/tools/metalava/model/ClassItem.kt +++ b/src/main/java/com/android/tools/metalava/model/ClassItem.kt @@ -198,6 +198,8 @@ interface ClassItem : Item { /** Gets the type for this class */ fun toType(): TypeItem + override fun type(): TypeItem? = null + /** Returns true if this class has type parameters */ fun hasTypeVariables(): Boolean diff --git a/src/main/java/com/android/tools/metalava/model/FieldItem.kt b/src/main/java/com/android/tools/metalava/model/FieldItem.kt index 92e49ddf7908b50c01f5829b77dda4901652f823..e65bda9ed2b914668fc1e60f8c1409c5b939982e 100644 --- a/src/main/java/com/android/tools/metalava/model/FieldItem.kt +++ b/src/main/java/com/android/tools/metalava/model/FieldItem.kt @@ -23,7 +23,7 @@ import java.io.PrintWriter interface FieldItem : MemberItem { /** The type of this field */ - fun type(): TypeItem + override fun type(): TypeItem /** * The initial/constant value, if any. If [requireConstant] the initial value will 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 9fbc546a6505b93336eaa1fe4d6739f8c01dbbd0..af2042dcec78ecfdd46419fb9adbe9b2d396a61f 100644 --- a/src/main/java/com/android/tools/metalava/model/Item.kt +++ b/src/main/java/com/android/tools/metalava/model/Item.kt @@ -210,6 +210,13 @@ interface Item { */ fun containingClass(strict: Boolean = true): ClassItem? + /** + * Returns the associated type if any. For example, for a field, property or parameter, + * this is the type of the variable; for a method, it's the return type. + * For packages, classes and compilation units, it's null. + */ + fun type(): TypeItem? + companion object { fun describe(item: Item, capitalize: Boolean = false): String { return when (item) { 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 bdb3b899ad7cdcde416caa580c921bf0903201ca..e27b682217cfb5688987db9f477949db2f8cf4b2 100644 --- a/src/main/java/com/android/tools/metalava/model/MethodItem.kt +++ b/src/main/java/com/android/tools/metalava/model/MethodItem.kt @@ -39,6 +39,8 @@ interface MethodItem : MemberItem { /** Returns the super methods that this method is overriding */ fun superMethods(): List<MethodItem> + override fun type(): TypeItem? = returnType() + /** * Like [internalName] but is the desc-portion of the internal signature, * e.g. for the method "void create(int x, int y)" the internal name of diff --git a/src/main/java/com/android/tools/metalava/model/PackageItem.kt b/src/main/java/com/android/tools/metalava/model/PackageItem.kt index 970d2eb7416f4f71cedccc022e146dd047d38214..711c0d9fd9c92e1290bd36e0cc10d15295007507 100644 --- a/src/main/java/com/android/tools/metalava/model/PackageItem.kt +++ b/src/main/java/com/android/tools/metalava/model/PackageItem.kt @@ -33,6 +33,8 @@ interface PackageItem : Item { return topLevelClasses().asSequence().flatMap { it.allClasses() } } + override fun type(): TypeItem? = null + val isDefault get() = qualifiedName().isEmpty() override fun parent(): PackageItem? = if (qualifiedName().isEmpty()) null else containingPackage() diff --git a/src/main/java/com/android/tools/metalava/model/ParameterItem.kt b/src/main/java/com/android/tools/metalava/model/ParameterItem.kt index 3610d51685185d5383f2b21522b8e60e8da907d6..a7194e6317c35689dcc102f2551faa8b1b58b0b6 100644 --- a/src/main/java/com/android/tools/metalava/model/ParameterItem.kt +++ b/src/main/java/com/android/tools/metalava/model/ParameterItem.kt @@ -24,7 +24,7 @@ interface ParameterItem : Item { fun name(): String /** The type of this field */ - fun type(): TypeItem + override fun type(): TypeItem /** The containing method */ fun containingMethod(): MethodItem diff --git a/src/main/java/com/android/tools/metalava/model/PropertyItem.kt b/src/main/java/com/android/tools/metalava/model/PropertyItem.kt index f82708c54c7172a1b0ad100970ac30ac917cf5d8..5f8478a2a6feb7048960ea0e0f13974d85d528db 100644 --- a/src/main/java/com/android/tools/metalava/model/PropertyItem.kt +++ b/src/main/java/com/android/tools/metalava/model/PropertyItem.kt @@ -21,7 +21,7 @@ import com.android.tools.metalava.model.visitors.TypeVisitor interface PropertyItem : MemberItem { /** The type of this property */ - fun type(): TypeItem + override fun type(): TypeItem override fun accept(visitor: ItemVisitor) { if (visitor.skip(this)) { 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 7ca5ff98dd28d9983281cc3480ec1ab953e42de2..c39e072f7f9d935cd46ad4cf95e14e1e1cf99876 100644 --- a/src/main/java/com/android/tools/metalava/model/TypeItem.kt +++ b/src/main/java/com/android/tools/metalava/model/TypeItem.kt @@ -297,5 +297,46 @@ interface TypeItem { return dimension + base } + + /** Compares two strings, ignoring space diffs (spaces, not whitespace in general) */ + fun equalsWithoutSpace(s1: String, s2: String): Boolean { + if (s1 == s2) { + return true + } + val sp1 = s1.indexOf(' ') // first space + val sp2 = s2.indexOf(' ') + if (sp1 == -1 && sp2 == -1) { + // no spaces in strings and aren't equal + return false + } + + val l1 = s1.length + val l2 = s2.length + var i1 = 0 + var i2 = 0 + + while (i1 < l1 && i2 < l2) { + var c1 = s1[i1++] + var c2 = s2[i2++] + + while (c1 == ' ' && i1 < l1) { + c1 = s1[i1++] + } + while (c2 == ' ' && i2 < l2) { + c2 = s2[i2++] + } + if (c1 != c2) { + return false + } + } + // Skip trailing spaces + while (i1 < l1 && s1[i1] == ' ') { + i1++ + } + while (i2 < l2 && s2[i2] == ' ') { + i2++ + } + return i1 == l1 && i2 == l2 + } } } 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 e8c6acb218495dce993a589c699c5ce1f56e57ab..decece4fe4962c4faa5e7d662f457fcc74dfc47f 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 @@ -284,7 +284,7 @@ class PsiAnnotationSingleAttributeValue( override val value: Any? get() { if (psiValue is PsiLiteral) { - return psiValue.value + return psiValue.value ?: psiValue.text.removeSurrounding("\"") } val value = ConstantEvaluator.evaluate(null, psiValue) @@ -292,7 +292,7 @@ class PsiAnnotationSingleAttributeValue( return value } - return psiValue.text + return psiValue.text ?: psiValue.text.removeSurrounding("\"") } override fun value(): Any? = value 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 564e15cbe5afe41f8da6339ce06fae61df1624a4..4a5ba9d34aae13be80225f69e26930fca0e3a72e 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 @@ -420,6 +420,8 @@ open class PsiClassItem( ) } + val isKotlin = isKotlin(psiClass) + val constructors: MutableList<PsiConstructorItem> = ArrayList(5) for (psiMethod in psiMethods) { if (psiMethod.isPrivate() || psiMethod.isPackagePrivate()) { @@ -468,7 +470,7 @@ open class PsiClassItem( item.fields = fields item.properties = emptyList() - if (isKotlin(psiClass)) { + if (isKotlin) { // Try to initialize the Kotlin properties val properties = mutableListOf<PsiPropertyItem>() for (method in psiMethods) { 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 f856c9193d17f9ef6d826628676a3ed7fb3862b9..c337332dc2deaad874e5b85312cb79afa7b338e8 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 @@ -357,9 +357,16 @@ open class PsiMethodItem( // methods with super methods also consider this method non-final.) modifiers.setFinal(false) } - val parameters = psiMethod.parameterList.parameters.mapIndexed { index, parameter -> - PsiParameterItem.create(codebase, parameter, index) - } + 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 returnType = codebase.getType(psiMethod.returnType!!) val method = PsiMethodItem( codebase = codebase, 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 3a112957cc7003ee2398ed9cc220c71c5335c18b..5ac50e57ac5301a42c3dd906a12c47b24cab7422 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 @@ -16,6 +16,9 @@ package com.android.tools.metalava.model.psi +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.model.AnnotationItem import com.android.tools.metalava.model.Codebase import com.android.tools.metalava.model.DefaultModifierList @@ -24,12 +27,18 @@ 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 org.jetbrains.kotlin.asJava.elements.KtLightModifierList +import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.uast.UAnnotated import org.jetbrains.uast.UMethod +import org.jetbrains.uast.UVariable +import org.jetbrains.uast.kotlin.KotlinNullabilityUAnnotation class PsiModifierItem( codebase: Codebase, @@ -38,8 +47,12 @@ class PsiModifierItem( ) : DefaultModifierList(codebase, flags, annotations), ModifierList, MutableModifierList { companion object { fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner, documentation: String?): PsiModifierItem { - val modifiers = create(codebase, element) - + val modifiers = + if (element is UAnnotated) { + create(codebase, element, element) + } else { + create(codebase, element) + } if (documentation?.contains("@deprecated") == true || // Check for @Deprecated annotation ((element as? PsiDocCommentOwner)?.isDeprecated == true) @@ -50,9 +63,7 @@ class PsiModifierItem( return modifiers } - private fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner): PsiModifierItem { - val modifierList = element.modifierList ?: return PsiModifierItem(codebase) - + private fun computeFlag(element: PsiModifierListOwner, modifierList: PsiModifierList): Int { var flags = 0 if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) { flags = flags or PUBLIC @@ -139,6 +150,14 @@ class PsiModifierItem( } } + return flags + } + + private fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner): PsiModifierItem { + val modifierList = element.modifierList ?: return PsiModifierItem(codebase) + + var flags = computeFlag(element, modifierList) + val psiAnnotations = modifierList.annotations return if (psiAnnotations.isEmpty()) { PsiModifierItem(codebase, flags) @@ -146,22 +165,16 @@ class PsiModifierItem( val annotations: MutableList<AnnotationItem> = psiAnnotations.map { val qualifiedName = it.qualifiedName - // TODO: com.android.internal.annotations.VisibleForTesting? - if (qualifiedName == "androidx.annotation.VisibleForTesting" || - qualifiedName == "android.support.annotation.VisibleForTesting") { - val otherwise = it.findAttributeValue("otherwise") + // Consider also supporting com.android.internal.annotations.VisibleForTesting? + if (qualifiedName == ANDROIDX_VISIBLE_FOR_TESTING || + qualifiedName == ANDROID_SUPPORT_VISIBLE_FOR_TESTING) { + val otherwise = it.findAttributeValue(ATTR_OTHERWISE) val ref = when { otherwise is PsiReferenceExpression -> otherwise.referenceName ?: "" otherwise != null -> otherwise.text else -> "" } - if (ref.endsWith("PROTECTED")) { - flags = (flags and PUBLIC.inv() and PRIVATE.inv() and INTERNAL.inv()) or PROTECTED - } else if (ref.endsWith("PACKAGE_PRIVATE")) { - flags = (flags and PUBLIC.inv() and PRIVATE.inv() and INTERNAL.inv() and PROTECTED.inv()) - } else if (ref.endsWith("PRIVATE") || ref.endsWith("NONE")) { - flags = (flags and PUBLIC.inv() and PROTECTED.inv() and INTERNAL.inv()) or PRIVATE - } + flags = getVisibilityFlag(ref, flags) } PsiAnnotationItem.create(codebase, it, qualifiedName) @@ -170,6 +183,74 @@ class PsiModifierItem( } } + private fun create( + codebase: PsiBasedCodebase, + element: PsiModifierListOwner, + annotated: UAnnotated + ): PsiModifierItem { + val modifierList = element.modifierList ?: return PsiModifierItem(codebase) + var flags = computeFlag(element, modifierList) + val uAnnotations = annotated.annotations + + return if (uAnnotations.isEmpty()) { + val psiAnnotations = modifierList.annotations + if (!psiAnnotations.isEmpty()) { + val annotations: MutableList<AnnotationItem> = + psiAnnotations.map { PsiAnnotationItem.create(codebase, it) }.toMutableList() + PsiModifierItem(codebase, flags, annotations) + } else { + PsiModifierItem(codebase, flags) + } + } else { + val isPrimitiveVariable = element is UVariable && element.type is PsiPrimitiveType + + val annotations: MutableList<AnnotationItem> = uAnnotations + // Uast sometimes puts nullability annotations on primitives!? + .filter { !isPrimitiveVariable || it !is KotlinNullabilityUAnnotation } + .map { + + val qualifiedName = it.qualifiedName + if (qualifiedName == ANDROIDX_VISIBLE_FOR_TESTING || + qualifiedName == ANDROID_SUPPORT_VISIBLE_FOR_TESTING) { + val otherwise = it.findAttributeValue(ATTR_OTHERWISE) + val ref = when { + otherwise is PsiReferenceExpression -> otherwise.referenceName ?: "" + otherwise != null -> otherwise.asSourceString() + else -> "" + } + flags = getVisibilityFlag(ref, flags) + } + + UAnnotationItem.create(codebase, it, qualifiedName) + }.toMutableList() + + if (!isPrimitiveVariable) { + val psiAnnotations = modifierList.annotations + if (psiAnnotations.isNotEmpty() && annotations.none { it.isNullnessAnnotation() }) { + val ktNullAnnotation = psiAnnotations.firstOrNull { it is KtLightNullabilityAnnotation } + ktNullAnnotation?.let { + annotations.add(PsiAnnotationItem.create(codebase, it)) + } + } + } + + PsiModifierItem(codebase, flags, annotations) + } + } + + /** Modifies the modifier flags based on the VisibleForTesting otherwise constants */ + private fun getVisibilityFlag(ref: String, flags: Int): Int { + return if (ref.endsWith("PROTECTED")) { + (flags and PUBLIC.inv() and PRIVATE.inv() and INTERNAL.inv()) or PROTECTED + } else if (ref.endsWith("PACKAGE_PRIVATE")) { + (flags and PUBLIC.inv() and PRIVATE.inv() and INTERNAL.inv() and PROTECTED.inv()) + } else if (ref.endsWith("PRIVATE") || ref.endsWith("NONE")) { + (flags and PUBLIC.inv() and PROTECTED.inv() and INTERNAL.inv()) or PRIVATE + } else { + flags + } + } + fun create(codebase: PsiBasedCodebase, original: PsiModifierItem): PsiModifierItem { val originalAnnotations = original.annotations ?: return PsiModifierItem(codebase, original.flags) val copy: MutableList<AnnotationItem> = ArrayList(originalAnnotations.size) 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 ef758ff660a88b602d1b8e738f61ad15f30591c5..507731483f1b5c997a3185b01b54593792aaef5d 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 @@ -56,7 +56,7 @@ import com.intellij.psi.util.TypeConversionUtil import org.jetbrains.kotlin.asJava.elements.KtLightTypeParameter /** Represents a type backed by PSI */ -class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, private val psiType: PsiType) : TypeItem { +class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, val psiType: PsiType) : TypeItem { private var toString: String? = null private var toAnnotatedString: String? = null private var toInnerAnnotatedString: String? = null @@ -149,7 +149,7 @@ class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, pr if (this === other) return true return when (other) { - is TypeItem -> toTypeString().replace(" ", "") == other.toTypeString().replace(" ", "") + is TypeItem -> TypeItem.equalsWithoutSpace(toTypeString(), other.toTypeString()) else -> false } } 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 new file mode 100644 index 0000000000000000000000000000000000000000..0d0266e6db7d36b9c8f4278cb6e35322502bd11a --- /dev/null +++ b/src/main/java/com/android/tools/metalava/model/psi/UAnnotationItem.kt @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.metalava.model.psi + +import com.android.SdkConstants +import com.android.tools.lint.detector.api.ConstantEvaluator +import com.android.tools.metalava.model.AnnotationArrayAttributeValue +import com.android.tools.metalava.model.AnnotationAttribute +import com.android.tools.metalava.model.AnnotationAttributeValue +import com.android.tools.metalava.model.AnnotationItem +import com.android.tools.metalava.model.AnnotationSingleAttributeValue +import com.android.tools.metalava.model.AnnotationTarget +import com.android.tools.metalava.model.ClassItem +import com.android.tools.metalava.model.DefaultAnnotationItem +import com.android.tools.metalava.model.Item +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiExpression +import com.intellij.psi.PsiField +import com.intellij.psi.PsiLiteral +import com.intellij.psi.PsiMethod +import com.intellij.psi.impl.JavaConstantExpressionEvaluator +import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation +import org.jetbrains.uast.UAnnotation +import org.jetbrains.uast.UBinaryExpression +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UExpression +import org.jetbrains.uast.ULiteralExpression +import org.jetbrains.uast.UReferenceExpression +import org.jetbrains.uast.util.isArrayInitializer + +class UAnnotationItem private constructor( + override val codebase: PsiBasedCodebase, + val uAnnotation: UAnnotation, + private val originalName: String? +) : DefaultAnnotationItem(codebase) { + private val qualifiedName = AnnotationItem.mapName(codebase, originalName) + + private var attributes: List<AnnotationAttribute>? = null + + override fun originalName(): String? = originalName + + override fun toString(): String = toSource() + + override fun toSource(target: AnnotationTarget): String { + val sb = StringBuilder(60) + appendAnnotation(codebase, sb, uAnnotation, originalName, target) + return sb.toString() + } + + override fun resolve(): ClassItem? { + return codebase.findClass(originalName ?: return null) + } + + override fun isNonNull(): Boolean { + if (uAnnotation.javaPsi is KtLightNullabilityAnnotation && + originalName == "" + ) { + // Hack/workaround: some UAST annotation nodes do not provide qualified name :=( + return true + } + return super.isNonNull() + } + + override fun qualifiedName() = qualifiedName + + override fun attributes(): List<AnnotationAttribute> { + if (attributes == null) { + val uAttributes = uAnnotation.attributeValues + attributes = if (uAttributes.isEmpty()) { + emptyList() + } else { + val list = mutableListOf<AnnotationAttribute>() + for (parameter in uAttributes) { + list.add( + UAnnotationAttribute( + codebase, + parameter.name ?: SdkConstants.ATTR_VALUE, parameter.expression + ) + ) + } + list + } + } + + return attributes!! + } + + companion object { + fun create(codebase: PsiBasedCodebase, uAnnotation: UAnnotation, qualifiedName: String? = uAnnotation.qualifiedName): UAnnotationItem { + return UAnnotationItem(codebase, uAnnotation, qualifiedName) + } + + fun create(codebase: PsiBasedCodebase, original: UAnnotationItem): UAnnotationItem { + return UAnnotationItem(codebase, original.uAnnotation, original.originalName) + } + + private fun appendAnnotation( + codebase: PsiBasedCodebase, + sb: StringBuilder, + uAnnotation: UAnnotation, + originalName: String?, + target: AnnotationTarget + ) { + val qualifiedName = AnnotationItem.mapName(codebase, originalName, null, target) ?: return + + val attributes = uAnnotation.attributeValues + if (attributes.isEmpty()) { + sb.append("@$qualifiedName") + return + } + + sb.append("@") + sb.append(qualifiedName) + sb.append("(") + if (attributes.size == 1 && (attributes[0].name == null || attributes[0].name == SdkConstants.ATTR_VALUE)) { + // Special case: omit "value" if it's the only attribute + appendValue(codebase, sb, attributes[0].expression, target) + } else { + var first = true + for (attribute in attributes) { + if (first) { + first = false + } else { + sb.append(", ") + } + sb.append(attribute.name ?: SdkConstants.ATTR_VALUE) + sb.append('=') + appendValue(codebase, sb, attribute.expression, target) + } + } + sb.append(")") + } + + private fun appendValue( + codebase: PsiBasedCodebase, + sb: StringBuilder, + value: UExpression?, + target: AnnotationTarget + ) { + // Compute annotation string -- we don't just use value.text here + // because that may not use fully qualified names, e.g. the source may say + // @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION) + // and we want to compute + // @android.support.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + when (value) { + null -> sb.append("null") + is ULiteralExpression -> sb.append(CodePrinter.constantToSource(value.value)) + is UReferenceExpression -> { + val resolved = value.resolve() + when (resolved) { + is PsiField -> { + val containing = resolved.containingClass + if (containing != null) { + // If it's a field reference, see if it looks like the field is hidden; if + // so, inline the value + val cls = codebase.findOrCreateClass(containing) + val initializer = resolved.initializer + if (initializer != null) { + val fieldItem = cls.findField(resolved.name) + if (fieldItem == null || fieldItem.isHiddenOrRemoved()) { + // Use the literal value instead + val source = getConstantSource(initializer) + if (source != null) { + sb.append(source) + return + } + } + } + containing.qualifiedName?.let { + sb.append(it).append('.') + } + } + + sb.append(resolved.name) + } + is PsiClass -> resolved.qualifiedName?.let { sb.append(it) } + else -> { + sb.append(value.sourcePsi?.text ?: value.asSourceString()) + } + } + } + is UBinaryExpression -> { + appendValue(codebase, sb, value.leftOperand, target) + sb.append(' ') + sb.append(value.operator.text) + sb.append(' ') + appendValue(codebase, sb, value.rightOperand, target) + } + is UCallExpression -> { + if (value.isArrayInitializer()) { + sb.append('{') + var first = true + for (initializer in value.valueArguments) { + if (first) { + first = false + } else { + sb.append(", ") + } + appendValue(codebase, sb, initializer, target) + } + sb.append('}') + } else { + println("todo") + } + } + is UAnnotation -> { + appendAnnotation(codebase, sb, value, value.qualifiedName, target) + } + else -> { + val source = getConstantSource(value) + if (source != null) { + sb.append(source) + return + } + sb.append(value.sourcePsi?.text ?: value.asSourceString()) + } + } + } + + private fun getConstantSource(value: UExpression): String? { + val constant = value.evaluate() + return CodePrinter.constantToExpression(constant) + } + + private fun getConstantSource(value: PsiExpression): String? { + val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false) + return CodePrinter.constantToExpression(constant) + } + } +} + +class UAnnotationAttribute( + codebase: PsiBasedCodebase, + override val name: String, + psiValue: UExpression +) : AnnotationAttribute { + override val value: AnnotationAttributeValue = UAnnotationValue.create( + codebase, psiValue + ) +} + +abstract class UAnnotationValue : AnnotationAttributeValue { + companion object { + fun create(codebase: PsiBasedCodebase, value: UExpression): UAnnotationValue { + return if (value.isArrayInitializer()) { + UAnnotationArrayAttributeValue(codebase, value as UCallExpression) + } else { + UAnnotationSingleAttributeValue(codebase, value) + } + } + } + + override fun toString(): String = toSource() +} + +class UAnnotationSingleAttributeValue( + private val codebase: PsiBasedCodebase, + private val psiValue: UExpression +) : UAnnotationValue(), AnnotationSingleAttributeValue { + override val valueSource: String = getText(psiValue) + override val value: Any? + get() { + if (psiValue is ULiteralExpression) { + val value = psiValue.value + if (value != null) { + return value + } else if (psiValue.isNull) { + return null + } + } + if (psiValue is PsiLiteral) { + return psiValue.value ?: getText(psiValue).removeSurrounding("\"") + } + + val value = ConstantEvaluator.evaluate(null, psiValue) + if (value != null) { + return value + } + + return getText(psiValue).removeSurrounding("\"") + } + + override fun value(): Any? = value + + override fun toSource(): String = getText(psiValue) + + override fun resolve(): Item? { + if (psiValue is UReferenceExpression) { + val resolved = psiValue.resolve() + when (resolved) { + is PsiField -> return codebase.findField(resolved) + is PsiClass -> return codebase.findOrCreateClass(resolved) + is PsiMethod -> return codebase.findMethod(resolved) + } + } + return null + } +} + +class UAnnotationArrayAttributeValue(codebase: PsiBasedCodebase, private val value: UCallExpression) : + UAnnotationValue(), AnnotationArrayAttributeValue { + override val values = value.valueArguments.map { + create(codebase, it) + }.toList() + + override fun toSource(): String = getText(value) +} + +private fun getText(element: UElement): String { + return element.sourcePsi?.text ?: element.asSourceString() +} \ No newline at end of file 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 d74415b4461c5605d010933bbc5b839ed2d80bcb..9dae73fcc89188c829ca3c532ae09441b9c7ab52 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 @@ -73,20 +73,15 @@ class TextTypeItem( if (this === other) return true return when (other) { - is TextTypeItem -> toString() == other.toString() + // Note: when we support type-use annotations, this is not safe: there could be a string + // literal inside which is significant + is TextTypeItem -> TypeItem.equalsWithoutSpace(toString(), other.toString()) is TypeItem -> { val thisString = toTypeString() val otherString = other.toTypeString() - if (thisString == otherString) { + if (TypeItem.equalsWithoutSpace(thisString, otherString)) { return true } - if (thisString[0] == otherString[0]) { - val thisCondensed = thisString.replace(" ", "") - val otherCondensed = otherString.replace(" ", "") - if (thisCondensed == otherCondensed) { - return true - } - } if (thisString.startsWith(JAVA_LANG_PREFIX) && thisString.endsWith(otherString) && thisString.length == otherString.length + JAVA_LANG_PREFIX.length ) { diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties index 233e17ea97e1c8f6cf1cc9d0fa73c97a81797f0f..82254daf6b0abda134a44b1124916b63ede1603e 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.2.5 +metalavaVersion=1.2.7 diff --git a/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt index 9e3999841c5184e169bad33eab5c5bdfcaa6af4a..382174480735cd91367e8d0c3a02788039a11acc 100644 --- a/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt +++ b/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt @@ -22,6 +22,7 @@ import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import kotlin.text.Charsets.UTF_8 class AnnotationsDifferTest { @get:Rule @@ -60,7 +61,7 @@ class AnnotationsDifferTest { options = Options(emptyArray()) AnnotationsDiffer(codebase, codebase2).writeDiffSignature(apiFile) assertTrue(apiFile.exists()) - val actual = apiFile.readText(Charsets.UTF_8) + val actual = apiFile.readText(UTF_8) assertEquals( """ package test.pkg { diff --git a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt index cc7eb68c3c8845c98f32e019078176867b691b9d..f1d6a12b9ae8bbc1251567ad8d921c18c9986c15 100644 --- a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt +++ b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt @@ -211,24 +211,70 @@ class AnnotationsMergerTest : DriverTest() { } @Test - fun `Merge inclusion annotations from Java stub files`() { + fun `Merge type use qualifier annotations from Java stub files`() { + // See b/123223339 check( - warnings = "src/test/pkg/Example.java:6: error: @test.annotation.Show APIs must also be marked @hide: method test.pkg.Example.cShown() [UnhiddenSystemApi]", sourceFiles = *arrayOf( java( """ package test.pkg; - public interface Example { - void aNotAnnotated(); - void bHidden(); - void cShown(); + public class Test { + private Test() { } + public void foo(Object... args) { } } + """ + ), + libcoreNonNullSource, + libcoreNullableSource + ), + compatibilityMode = false, + outputKotlinStyleNulls = false, + omitCommonPackages = false, + mergeJavaStubAnnotations = """ + package test.pkg; - public interface HiddenExample { - void method(); + public class Test { + public void foo(java.lang.@libcore.util.Nullable Object @libcore.util.NonNull ... args) { throw new RuntimeException("Stub!"); } } - """ + """, + api = """ + package test.pkg { + public class Test { + method public void foo(@androidx.annotation.NonNull java.lang.Object...); + } + } + """, + extraArguments = arrayOf(ARG_HIDE_PACKAGE, "libcore.util") + ) + } + + @Test + fun `Merge inclusion annotations from Java stub files`() { + check( + warnings = "src/test/pkg/Example.annotated.java:6: error: @test.annotation.Show APIs must also be marked @hide: method test.pkg.Example.cShown() [UnhiddenSystemApi]", + sourceFiles = *arrayOf( + java( + "src/test/pkg/Example.annotated.java", + """ + package test.pkg; + + public interface Example { + void aNotAnnotated(); + void bHidden(); + void cShown(); + } + """ + ), + java( + "src/test/pkg/HiddenExample.annotated.java", + """ + package test.pkg; + + public interface HiddenExample { + void method(); + } + """ ) ), compatibilityMode = false, @@ -267,14 +313,15 @@ class AnnotationsMergerTest : DriverTest() { check( sourceFiles = *arrayOf( java( + "src/test/pkg/Example.annotated.java", """ - package test.pkg; + package test.pkg; - public interface Example { - void aNotAnnotated(); - void bShown(); - } - """ + public interface Example { + void aNotAnnotated(); + void bShown(); + } + """ ) ), compatibilityMode = false, diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt index 962f9c6147d277003c36d1497da9ec5ebd586d57..9c19b5b170b2d4899e6b7c6f9cb558d573f8d05c 100644 --- a/src/test/java/com/android/tools/metalava/ApiFileTest.kt +++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt @@ -285,7 +285,7 @@ class ApiFileTest : DriverTest() { package androidx.core.util { public final class TestKt { ctor public 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 = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (java.lang.Object)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> }); } } """, @@ -457,10 +457,10 @@ class ApiFileTest : DriverTest() { package test.pkg { public final class TestKt { ctor public TestKt(); - method public static inline <T> void a(T t); - method public static inline <reified T> void b(T t); - method public static inline <reified T> void e(T t); - method public static inline <reified T> void f(T, T t); + method public static inline <T> void a(@Nullable T t); + method public static inline <reified T> void b(@Nullable T t); + method public static inline <reified T> void e(@Nullable T t); + method public static inline <reified T> void f(@Nullable T, @Nullable T t); } } """, @@ -484,7 +484,7 @@ class ApiFileTest : DriverTest() { package test.pkg { public final class TestKt { ctor public TestKt(); - method public static suspend inline Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p); + method public static suspend inline Object hello(@NonNull kotlin.coroutines.Continuation<? super kotlin.Unit> p); } } """, @@ -613,6 +613,7 @@ class ApiFileTest : DriverTest() { androidxNullableSource ), api = """ + // Signature format: 3.0 package androidx.util { public class NonNullableJavaPair<F, S> { ctor public NonNullableJavaPair(F, S); @@ -712,6 +713,7 @@ class ApiFileTest : DriverTest() { androidxNullableSource ), api = """ + // Signature format: 3.0 package test { public class MyClass { ctor public MyClass(); @@ -1419,12 +1421,12 @@ class ApiFileTest : DriverTest() { ), warnings = """ - src/test/pkg/Foo.java:7: warning: Method test.pkg.Foo.method1(): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch] - src/test/pkg/Foo.java:8: warning: Method test.pkg.Foo.method2(): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch] - src/test/pkg/Foo.java:9: warning: Class test.pkg.Foo.Inner1: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch] - src/test/pkg/Foo.java:10: warning: Class test.pkg.Foo.Inner2: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch] - src/test/pkg/Foo.java:11: warning: Class test.pkg.Foo.Inner3: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch] - """, + src/test/pkg/Foo.java:7: error: Method test.pkg.Foo.method1(): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch] + src/test/pkg/Foo.java:8: error: Method test.pkg.Foo.method2(): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch] + src/test/pkg/Foo.java:9: error: Class test.pkg.Foo.Inner1: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch] + src/test/pkg/Foo.java:10: error: Class test.pkg.Foo.Inner2: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch] + src/test/pkg/Foo.java:11: error: Class test.pkg.Foo.Inner3: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch] + """, api = """ package test.pkg { @@ -3212,8 +3214,8 @@ class ApiFileTest : DriverTest() { import androidx.annotation.VisibleForTesting; @SuppressWarnings({"ClassNameDiffersFromFileName", "WeakerAccess"}) - public class ProductionCode { - private ProductionCode() { } + public class ProductionCodeJava { + private ProductionCodeJava() { } @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) public void shouldBeProtected() { @@ -3242,7 +3244,7 @@ class ApiFileTest : DriverTest() { package test.pkg import androidx.annotation.VisibleForTesting - open class ProductionCode2 private constructor() { + open class ProductionCodeKotlin private constructor() { @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) fun shouldBeProtected() { @@ -3270,10 +3272,10 @@ class ApiFileTest : DriverTest() { ), api = """ package test.pkg { - public class ProductionCode { + public class ProductionCodeJava { method protected void shouldBeProtected(); } - public class ProductionCode2 { + public class ProductionCodeKotlin { method protected final void shouldBeProtected(); } } @@ -3331,4 +3333,4 @@ class ApiFileTest : DriverTest() { ) ) } -} \ No newline at end of file +} diff --git a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt index bbc1439e528c29ed8cc7d5cf4d216b48173fdfe9..abbf5f1e3c1649260f99a5177528827b9823747e 100644 --- a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt +++ b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt @@ -653,6 +653,7 @@ class ApiFromTextTest : DriverTest() { @Test fun `Deprecated enum constant`() { val source = """ + // Signature format: 3.0 package androidx.annotation { @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface RestrictTo { method public abstract androidx.annotation.RestrictTo.Scope[] value(); @@ -677,8 +678,9 @@ class ApiFromTextTest : DriverTest() { } @Test - fun `Type parameters in v2 format`() { + fun `Type parameters in v3 format`() { val source = """ + // Signature format: 3.0 package androidx.collection { public class Constants { field public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef"; diff --git a/src/test/java/com/android/tools/metalava/ApiLintTest.kt b/src/test/java/com/android/tools/metalava/ApiLintTest.kt index dbbc41a8447596abc888d5f0a9212fabe0a362f9..bcb59a86a46f4a0494325f089c1f6e4f5b878295 100644 --- a/src/test/java/com/android/tools/metalava/ApiLintTest.kt +++ b/src/test/java/com/android/tools/metalava/ApiLintTest.kt @@ -110,7 +110,10 @@ class ApiLintTest : DriverTest() { """ ) ), - expectedOutput = "9 new API lint issues were found. See tools/metalava/API-LINT.md for how to handle these." + expectedOutput = """ + 9 new API lint issues were found. + See tools/metalava/API-LINT.md for how to handle these. + """ ) } @@ -1453,7 +1456,7 @@ class ApiLintTest : DriverTest() { import java.io.InputStream; public class CheckFiles { - public MyClass(Context context, File file) { + public CheckFiles(Context context, File file) { } public void ok(int i, File file) { } public void ok(int i, InputStream stream) { } @@ -1484,7 +1487,7 @@ class ApiLintTest : DriverTest() { import java.io.InputStream; public class CheckFiles { - public MyClass(Context context, File file) { + public CheckFiles(Context context, File file) { } public void ok(int i, File file) { } public void ok(int i, InputStream stream) { } diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt index 6c01e32849a29e733c05e0fc4d821d9d86937be1..e7b30ebb8bbdd90e3801bd744a7e6005c74a6138 100644 --- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt +++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt @@ -19,6 +19,7 @@ package com.android.tools.metalava import org.junit.Ignore import org.junit.Test import java.io.File +import kotlin.text.Charsets.UTF_8 class CompatibilityCheckTest : DriverTest() { @@ -148,6 +149,11 @@ CompatibilityCheckTest : DriverTest() { method @NonNull public Double convert4(@NonNull Float); method @Nullable public Double convert5(@Nullable Float); method @NonNull public Double convert6(@NonNull Float); + // booleans cannot reasonably be annotated with @Nullable/@NonNull but + // the compiler accepts it and we had a few of these accidentally annotated + // that way in API 28, such as Boolean.getBoolean. Make sure we don't flag + // these as incompatible changes when they're dropped. + method public void convert7(@NonNull boolean); } } """, @@ -161,6 +167,7 @@ CompatibilityCheckTest : DriverTest() { method public Double convert4(Float); method @NonNull public Double convert5(@NonNull Float); method @Nullable public Double convert6(@Nullable Float); + method public void convert7(boolean); } } """ @@ -283,6 +290,32 @@ CompatibilityCheckTest : DriverTest() { ) } + @Test + fun `Kotlin Coroutines`() { + check( + warnings = "", + compatibilityMode = false, + inputKotlinStyleNulls = true, + outputKotlinStyleNulls = true, + checkCompatibilityApi = """ + package test.pkg { + public final class TestKt { + ctor public TestKt(); + method public static suspend inline java.lang.Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit>); + } + } + """, + signatureSource = """ + package test.pkg { + public final class TestKt { + ctor public TestKt(); + method public static suspend inline Object hello(@NonNull kotlin.coroutines.Continuation<? super kotlin.Unit> p); + } + } + """ + ) + } + @Test fun `Add flag new methods but not overrides from platform`() { check( @@ -2248,6 +2281,30 @@ CompatibilityCheckTest : DriverTest() { ) } + @Test + fun `Insignificant type formatting differences`() { + check( + checkCompatibilityApi = """ + package test.pkg { + public final class UsageStatsManager { + method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets(); + method public void setAppStandbyBuckets(java.util.Map<java.lang.String, java.lang.Integer>); + field public java.util.Map<java.lang.String, java.lang.Integer> map; + } + } + """, + signatureSource = """ + package test.pkg { + public final class UsageStatsManager { + method public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets(); + method public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>); + field public java.util.Map<java.lang.String,java.lang.Integer> map; + } + } + """ + ) + } + @Test fun `Adding and removing reified`() { check( @@ -2421,7 +2478,7 @@ CompatibilityCheckTest : DriverTest() { println("Couldn't find $signatureFile: Check that pwd for test is correct. Skipping this test.") return } - val previousSignatureApi = signatureFile.readText(Charsets.UTF_8) + val previousSignatureApi = signatureFile.readText(UTF_8) check( checkDoclava1 = false, diff --git a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt index bc85a784ddef4862dbe33b8210f43aeb5de34a58..d309a11500f08fb46e1add323f9d776cc9bc74c5 100644 --- a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt +++ b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt @@ -7,6 +7,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test import java.io.File +import kotlin.text.Charsets.UTF_8 /** Tests for the [DocAnalyzer] which enhances the docs */ class DocAnalyzerTest : DriverTest() { @@ -1332,7 +1333,7 @@ class DocAnalyzerTest : DriverTest() { * @deprecated Blah blah blah 1 */ @Deprecated - @androidx.annotation.NonNull + @android.annotation.NonNull public java.lang.String toString() { throw new RuntimeException("Stub!"); } /** * My description @@ -1552,7 +1553,7 @@ class DocAnalyzerTest : DriverTest() { ) ) - val doc = File(html, "test/pkg/LocationManager.html").readText(Charsets.UTF_8) + val doc = File(html, "test/pkg/LocationManager.html").readText(UTF_8) assertTrue( "Did not find matching javadoc fragment in LocationManager.html: actual content is\n$doc", doc.contains( diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt index f862f3494177a1bf9ac964a1116617ac84b32c6a..e54df8105f49f68edb3208ffcce5903ae69be7c4 100644 --- a/src/test/java/com/android/tools/metalava/DriverTest.kt +++ b/src/test/java/com/android/tools/metalava/DriverTest.kt @@ -35,10 +35,10 @@ import com.android.tools.metalava.model.parseDocument import com.android.utils.FileUtils import com.android.utils.SdkUtils import com.android.utils.StdLogger -import com.google.common.base.Charsets import com.google.common.io.ByteStreams import com.google.common.io.Closeables import com.google.common.io.Files +import com.intellij.openapi.util.Disposer import org.intellij.lang.annotations.Language import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -95,6 +95,9 @@ abstract class DriverTest { val sw = StringWriter() val writer = PrintWriter(sw) + + Disposer.setDebugMode(true) + if (!com.android.tools.metalava.run(arrayOf(*args), writer, writer)) { val actualFail = cleanupString(sw.toString(), null) if (cleanupString(expectedFail, null).replace(".", "").trim() != @@ -125,6 +128,8 @@ abstract class DriverTest { fail("Printed newlines with nothing else") } + Disposer.assertIsEmpty(true) + return printedOutput } finally { System.setOut(previousOut) @@ -132,6 +137,9 @@ abstract class DriverTest { } } + // This is here to make sure we don't have any unexpected random println's + // in the source that are left behind after debugging and ends up polluting + // the production output class OutputForbiddenWriter(private val stream: String) : ByteArrayOutputStream() { override fun write(b: ByteArray?, off: Int, len: Int) { fail("Unexpected write directly to $stream") @@ -761,12 +769,12 @@ abstract class DriverTest { val signature = convert.fromApi val base = convert.baseApi val convertSig = temporaryFolder.newFile("convert-signatures$index.txt") - convertSig.writeText(signature.trimIndent(), Charsets.UTF_8) + convertSig.writeText(signature.trimIndent(), UTF_8) val extension = convert.format.preferredExtension() val output = temporaryFolder.newFile("convert-output$index$extension") val baseFile = if (base != null) { val baseFile = temporaryFolder.newFile("convert-signatures$index-base.txt") - baseFile.writeText(base.trimIndent(), Charsets.UTF_8) + baseFile.writeText(base.trimIndent(), UTF_8) baseFile } else { null @@ -1039,7 +1047,7 @@ abstract class DriverTest { val actualText = readFile(apiXmlFile, stripBlankLines, trim) assertEquals(stripComments(apiXml, stripLineComments = false).trimIndent(), actualText) // Make sure we can read back the files we write - parseDocument(apiXmlFile.readText(Charsets.UTF_8), false) + parseDocument(apiXmlFile.readText(UTF_8), false) } if (baseline != null && baselineFile != null) { @@ -1205,7 +1213,7 @@ abstract class DriverTest { validateNullabilityTxt.isFile ) val actualReport = - Files.asCharSource(validateNullabilityTxt, Charsets.UTF_8).readLines().map(String::trim).toSet() + Files.asCharSource(validateNullabilityTxt, UTF_8).readLines().map(String::trim).toSet() assertEquals(validateNullability, actualReport) } @@ -1320,7 +1328,7 @@ abstract class DriverTest { val signatureFile: File = apiFile ?: if (signatureSource != null) { val temp = temporaryFolder.newFile("jdiff-doclava-api.txt") - temp.writeText(signatureSource.trimIndent(), Charsets.UTF_8) + temp.writeText(signatureSource.trimIndent(), UTF_8) temp } else { fail("When verifying XML files with doclava you must either specify signatureSource or api") @@ -1349,11 +1357,11 @@ abstract class DriverTest { val base = convert.baseApi val strip = convert.strip val convertSig = temporaryFolder.newFile("doclava-jdiff-signatures$index.txt") - convertSig.writeText(signature.trimIndent(), Charsets.UTF_8) + convertSig.writeText(signature.trimIndent(), UTF_8) val output = temporaryFolder.newFile("doclava-jdiff-output$index.xml") val baseFile = if (base != null) { val baseFile = temporaryFolder.newFile("doclava-jdiff-signatures$index-base.txt") - baseFile.writeText(base.trimIndent(), Charsets.UTF_8) + baseFile.writeText(base.trimIndent(), UTF_8) baseFile } else { null @@ -1570,7 +1578,7 @@ abstract class DriverTest { try { val bytes = ByteStreams.toByteArray(stream) assertNotNull(bytes) - val xml = String(bytes, Charsets.UTF_8).replace("\r\n", "\n") + val xml = String(bytes, UTF_8).replace("\r\n", "\n") assertEquals(expected.trimIndent().trim(), xml.trimIndent().trim()) } finally { Closeables.closeQuietly(stream) @@ -1894,6 +1902,10 @@ abstract class DriverTest { } } + fun java(to: String, @Language("JAVA") source: String): LintDetectorTest.TestFile { + return TestFiles.java(to, source.trimIndent()) + } + fun java(@Language("JAVA") source: String): LintDetectorTest.TestFile { return TestFiles.java(source.trimIndent()) } @@ -1907,7 +1919,7 @@ abstract class DriverTest { } private fun readFile(file: File, stripBlankLines: Boolean = false, trim: Boolean = false): String { - var apiLines: List<String> = Files.asCharSource(file, Charsets.UTF_8).readLines() + var apiLines: List<String> = Files.asCharSource(file, UTF_8).readLines() if (stripBlankLines) { apiLines = apiLines.asSequence().filter { it.isNotBlank() }.toList() } diff --git a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt index bad1733519bae0fd7cbbe3a1dcf3961ee35ae9a7..2d9269567e19aedc5a3a8c28d1bb2be07db43fbc 100644 --- a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt +++ b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt @@ -157,9 +157,6 @@ class ExtractAnnotationsTest : DriverTest() { "test.pkg" to """ <?xml version="1.0" encoding="UTF-8"?> <root> - <item name="test.pkg.LongDefTest void setFlags(java.lang.Object, int) 0"> - <annotation name="androidx.annotation.NonNull"/> - </item> <item name="test.pkg.LongDefTest void setFlags(java.lang.Object, int) 1"> <annotation name="androidx.annotation.LongDef"> <val name="flag" val="true" /> @@ -171,24 +168,12 @@ class ExtractAnnotationsTest : DriverTest() { <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT}" /> </annotation> </item> - <item name="test.pkg.LongDefTest.Inner boolean isNull(java.lang.String) 0"> - <annotation name="androidx.annotation.Nullable"/> - </item> <item name="test.pkg.LongDefTest.Inner void setInner(int) 0"> <annotation name="androidx.annotation.LongDef"> <val name="flag" val="true" /> <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4L}" /> </annotation> </item> - <item name="test.pkg.LongDefTestKt TYPE_1"> - <annotation name="androidx.annotation.NonNull"/> - </item> - <item name="test.pkg.LongDefTestKt TYPE_2"> - <annotation name="androidx.annotation.NonNull"/> - </item> - <item name="test.pkg.LongDefTestKt UNRELATED_TYPE"> - <annotation name="androidx.annotation.NonNull"/> - </item> </root> """ ) @@ -420,6 +405,20 @@ class ExtractAnnotationsTest : DriverTest() { intRangeAnnotationSource, recentlyNullableSource ), + stubs = arrayOf( + """ + package test.pkg; + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Test { + public Test() { throw new RuntimeException("Stub!"); } + /** + * @param value Value is 10 or greater + */ + @androidx.annotation.RecentlyNullable + public static java.lang.String sayHello(int value) { throw new RuntimeException("Stub!"); } + } + """ + ), extractAnnotations = mapOf( "test.pkg" to """ <?xml version="1.0" encoding="UTF-8"?> diff --git a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt index f3efebca9faa75b147450c21d21cc6ffb2fde66e..037100f1d1383ce8d2791a571c9a7fa8208ab99d 100644 --- a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt +++ b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt @@ -91,6 +91,7 @@ class KotlinInteropChecksTest : DriverTest() { extraArguments = arrayOf(ARG_CHECK_KOTLIN_INTEROP), warnings = """ src/test/pkg/Foo.kt:7: warning: Companion object constants like INTEGER_ONE should be marked @JvmField for Java interoperability; see https://android.github.io/kotlin-guides/interop.html#companion-constants [MissingJvmstatic] + src/test/pkg/Foo.kt:10: warning: Companion object constants like WRONG2 should be using @JvmField, not @JvmStatic; see https://android.github.io/kotlin-guides/interop.html#companion-constants [MissingJvmstatic] src/test/pkg/Foo.kt:13: warning: Companion object methods like missing should be marked @JvmStatic for Java interoperability; see https://android.github.io/kotlin-guides/interop.html#companion-functions [MissingJvmstatic] """, sourceFiles = *arrayOf( @@ -105,7 +106,7 @@ class KotlinInteropChecksTest : DriverTest() { const val INTEGER_ONE = 1 var BIG_INTEGER_ONE = BigInteger.ONE @JvmStatic val WRONG = 2 // not yet flagged - @JvmStatic @JvmField val WRONG2 = 2 // not yet flagged + @JvmStatic @JvmField val WRONG2 = 2 @JvmField val ok3 = 3 fun missing() { } diff --git a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt index 90332ec64124717eb0f022c5f4e79b38c92950d9..a3ddfaf87021e7709380f4888138f80d4d7e3cd3 100644 --- a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt +++ b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt @@ -210,12 +210,12 @@ class NullnessMigrationTest : DriverTest() { public java.lang.Double convert0(java.lang.Float f) { throw new RuntimeException("Stub!"); } @androidx.annotation.RecentlyNullable public java.lang.Double convert1(@androidx.annotation.RecentlyNonNull java.lang.Float f) { throw new RuntimeException("Stub!"); } - @androidx.annotation.Nullable - public java.lang.Double convert2(@androidx.annotation.NonNull java.lang.Float f) { throw new RuntimeException("Stub!"); } - @androidx.annotation.Nullable - public java.lang.Double convert3(@androidx.annotation.NonNull java.lang.Float f) { throw new RuntimeException("Stub!"); } - @androidx.annotation.Nullable - public java.lang.Double convert4(@androidx.annotation.NonNull java.lang.Float f) { throw new RuntimeException("Stub!"); } + @android.annotation.Nullable + public java.lang.Double convert2(@android.annotation.NonNull java.lang.Float f) { throw new RuntimeException("Stub!"); } + @android.annotation.Nullable + public java.lang.Double convert3(@android.annotation.NonNull java.lang.Float f) { throw new RuntimeException("Stub!"); } + @android.annotation.Nullable + public java.lang.Double convert4(@android.annotation.NonNull java.lang.Float f) { throw new RuntimeException("Stub!"); } } """ ), @@ -259,6 +259,7 @@ class NullnessMigrationTest : DriverTest() { } """, api = """ + // Signature format: 3.0 package test.pkg { public class MyTest { ctor public MyTest(); @@ -471,9 +472,9 @@ class NullnessMigrationTest : DriverTest() { public Foo() { throw new RuntimeException("Stub!"); } public static char @androidx.annotation.RecentlyNonNull [] toChars(int codePoint) { throw new RuntimeException("Stub!"); } public static int codePointAt(char @androidx.annotation.RecentlyNonNull [] a, int index) { throw new RuntimeException("Stub!"); } - public <T> T @androidx.annotation.RecentlyNonNull [] toArray(T @androidx.annotation.RecentlyNonNull [] a) { throw new RuntimeException("Stub!"); } + public <T> T @android.annotation.RecentlyNonNull [] toArray(T @androidx.annotation.RecentlyNonNull [] a) { throw new RuntimeException("Stub!"); } @androidx.annotation.NonNull - public static java.lang.String newMethod(@androidx.annotation.Nullable java.lang.String argument) { throw new RuntimeException("Stub!"); } + public static java.lang.String newMethod(@android.annotation.Nullable java.lang.String argument) { throw new RuntimeException("Stub!"); } } """ ) @@ -487,8 +488,8 @@ class NullnessMigrationTest : DriverTest() { public static char[] toChars(int codePoint) { throw new RuntimeException("Stub!"); } public static int codePointAt(char[] a, int index) { throw new RuntimeException("Stub!"); } public <T> T[] toArray(T[] a) { throw new RuntimeException("Stub!"); } - @androidx.annotation.NonNull - public static java.lang.String newMethod(@androidx.annotation.Nullable java.lang.String argument) { throw new RuntimeException("Stub!"); } + @android.annotation.NonNull + public static java.lang.String newMethod(@android.annotation.Nullable java.lang.String argument) { throw new RuntimeException("Stub!"); } } """ ) diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt index 7c61c9eac59ae290eb06e303ba54052f58d7753f..36d02bf03f2735ac1277cb4e3293ab13f91f6a2f 100644 --- a/src/test/java/com/android/tools/metalava/OptionsTest.kt +++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt @@ -241,6 +241,13 @@ Diffs and Checks: on the same source tree with different flags at different times, such as occasionally with --api-lint. +--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 Whether to delete baseline files if they are + updated and there is nothing to include. JDiff: --api-xml <file> Like --api, but emits the API in the JDiff XML diff --git a/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt index b8c2a416bee3f24671e251c7224095f4340b52df..a95fa2e3a5b5dd19323085510d1eee00be27e982 100644 --- a/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt +++ b/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt @@ -26,6 +26,7 @@ import org.junit.Test import java.io.File import java.lang.reflect.Modifier import java.net.URLClassLoader +import kotlin.text.Charsets.UTF_8 class RewriteAnnotationsTest : DriverTest() { @Test @@ -82,7 +83,7 @@ class RewriteAnnotationsTest : DriverTest() { @Retention(CLASS) @Target({METHOD, PARAMETER, FIELD}) @interface RecentlyNullable {} - """.trimIndent().trim(), recentlyNull.readText(Charsets.UTF_8).trim().replace("\r\n", "\n") + """.trimIndent().trim(), recentlyNull.readText(UTF_8).trim().replace("\r\n", "\n") ) } diff --git a/src/test/java/com/android/tools/metalava/StubsTest.kt b/src/test/java/com/android/tools/metalava/StubsTest.kt index 807bd9193c3fa65cbbfa4a2cf1c0294990fd9cac..da65e5c80cee2a87718896ce951c4873e2cecad9 100644 --- a/src/test/java/com/android/tools/metalava/StubsTest.kt +++ b/src/test/java/com/android/tools/metalava/StubsTest.kt @@ -1352,17 +1352,17 @@ class StubsTest : DriverTest() { /** My class doc */ @SuppressWarnings({"unchecked", "deprecation", "all"}) public final class Kotlin extends test.pkg.Parent { - public Kotlin(@androidx.annotation.NonNull java.lang.String property1, int arg2) { throw new RuntimeException("Stub!"); } - @androidx.annotation.NonNull + public Kotlin(@android.annotation.NonNull java.lang.String property1, int arg2) { throw new RuntimeException("Stub!"); } + @android.annotation.NonNull public java.lang.String method() { throw new RuntimeException("Stub!"); } /** My method doc */ public void otherMethod(boolean ok, int times) { throw new RuntimeException("Stub!"); } /** property doc */ - @androidx.annotation.Nullable + @android.annotation.Nullable public java.lang.String getProperty2() { throw new RuntimeException("Stub!"); } /** property doc */ - public void setProperty2(@androidx.annotation.Nullable java.lang.String p) { throw new RuntimeException("Stub!"); } - @androidx.annotation.NonNull + public void setProperty2(@android.annotation.Nullable java.lang.String p) { throw new RuntimeException("Stub!"); } + @android.annotation.NonNull public java.lang.String getProperty1() { throw new RuntimeException("Stub!"); } public int someField2; } @@ -3246,9 +3246,9 @@ class StubsTest : DriverTest() { ctor public Test(); } } - """, // WRONG: I should include package annotations! + """, // WRONG: I should include package annotations in the signature file! source = """ - @androidx.annotation.Nullable + @android.annotation.Nullable package test.pkg; """, extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation") @@ -3607,6 +3607,70 @@ class StubsTest : DriverTest() { ) } + @Test + fun `Annotation nested rewriting`() { + checkStubs( + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + + import android.view.Gravity; + + public class ActionBar { + @ViewDebug.ExportedProperty(category = "layout", mapping = { + @ViewDebug.IntToString(from = -1, to = "NONE"), + @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"), + @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"), + @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"), + }) + public int gravity = Gravity.NO_GRAVITY; + } + """ + ), + java( + """ + package test.pkg; + + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + + public class ViewDebug { + @Target({ElementType.FIELD, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + public @interface ExportedProperty { + boolean resolveId() default false; + IntToString[] mapping() default {}; + IntToString[] indexMapping() default {}; + boolean deepExport() default false; + String prefix() default ""; + String category() default ""; + boolean formatToHexString() default false; + boolean hasAdjacentMapping() default false; + } + @Target({ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface IntToString { + int from(); + String to(); + } + } + """ + ) + ), + source = """ + package test.pkg; + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class ActionBar { + public ActionBar() { throw new RuntimeException("Stub!"); } + @test.pkg.ViewDebug.ExportedProperty(category="layout", mapping={@test.pkg.ViewDebug.IntToString(from=0xffffffff, to="NONE"), @test.pkg.ViewDebug.IntToString(from=android.view.Gravity.NO_GRAVITY, to="NONE"), @test.pkg.ViewDebug.IntToString(from=android.view.Gravity.TOP, to="TOP"), @test.pkg.ViewDebug.IntToString(from=android.view.Gravity.BOTTOM, to="BOTTOM")}) public int gravity = 0; // 0x0 + } + """ + ) + } + @Test(expected = FileNotFoundException::class) fun `Test update-api should not generate stubs`() { check( diff --git a/src/test/java/com/android/tools/metalava/SymlinkTest.kt b/src/test/java/com/android/tools/metalava/SymlinkTest.kt index a82194615e446244fb05eca64b5996e0a09acdbe..83916ab16b45ecf1d4e87f0d9cd2f34c6ce53d56 100644 --- a/src/test/java/com/android/tools/metalava/SymlinkTest.kt +++ b/src/test/java/com/android/tools/metalava/SymlinkTest.kt @@ -102,22 +102,22 @@ class SymlinkTest : DriverTest() { * @param factor2 This value must never be {@code null}. * @return This value may be {@code null}. */ - @androidx.annotation.Nullable - public java.lang.Double method1(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } + @android.annotation.Nullable + public java.lang.Double method1(@android.annotation.NonNull java.lang.Double factor1, @android.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } /** * These are the docs for method2. It can sometimes return null. * @param factor1 This value must never be {@code null}. * @param factor2 This value must never be {@code null}. */ - @androidx.annotation.Nullable - public java.lang.Double method2(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } + @android.annotation.Nullable + public java.lang.Double method2(@android.annotation.NonNull java.lang.Double factor1, @android.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } /** * @param factor1 This value must never be {@code null}. * @param factor2 This value must never be {@code null}. * @return This value may be {@code null}. */ - @androidx.annotation.Nullable - public java.lang.Double method3(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } + @android.annotation.Nullable + public java.lang.Double method3(@android.annotation.NonNull java.lang.Double factor1, @android.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } } """ ) diff --git a/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt b/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt index 112d51304ecd3259c62bf39fb117917882a2475e..908248d16452205d0965bc47e8b1899733487d72 100644 --- a/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt +++ b/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt @@ -33,4 +33,22 @@ class TypeItemTest { "java.util.List<@NonNull java.lang.String>" ) } + + @Test + fun testEqualsWithoutSpace() { + assertThat(TypeItem.equalsWithoutSpace("", "")).isTrue() + assertThat(TypeItem.equalsWithoutSpace(" ", "")).isTrue() + assertThat(TypeItem.equalsWithoutSpace("", " ")).isTrue() + assertThat(TypeItem.equalsWithoutSpace(" ", " ")).isTrue() + assertThat(TypeItem.equalsWithoutSpace("true", "tr ue")).isTrue() + assertThat(TypeItem.equalsWithoutSpace("tr ue", "true")).isTrue() + assertThat(TypeItem.equalsWithoutSpace("true", "true ")).isTrue() + assertThat(TypeItem.equalsWithoutSpace("true ", "true")).isTrue() + assertThat(TypeItem.equalsWithoutSpace("true ", "true")).isTrue() + assertThat(TypeItem.equalsWithoutSpace("true", " true")).isTrue() + + assertThat(TypeItem.equalsWithoutSpace("true", "false")).isFalse() + assertThat(TypeItem.equalsWithoutSpace("true", " true false")).isFalse() + assertThat(TypeItem.equalsWithoutSpace("false ", "falser")).isFalse() + } } \ No newline at end of file diff --git a/stub-annotations/src/main/java/android/annotation/NonNull.java b/stub-annotations/src/main/java/android/annotation/NonNull.java new file mode 100644 index 0000000000000000000000000000000000000000..c826461be7ee6d1209a7470bada9e91d1f532765 --- /dev/null +++ b/stub-annotations/src/main/java/android/annotation/NonNull.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD}) +public @interface NonNull {} diff --git a/stub-annotations/src/main/java/android/annotation/Nullable.java b/stub-annotations/src/main/java/android/annotation/Nullable.java new file mode 100644 index 0000000000000000000000000000000000000000..93b73e3de5ad45606241749e69c1f08cfb08cf3d --- /dev/null +++ b/stub-annotations/src/main/java/android/annotation/Nullable.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD}) +public @interface Nullable {}