Pass reporter to PsiSourceParser and related methods

Removes a dependency onto the global `reporter` variable which would
prevent this being moved into a separate library.

Adds a simple `BasicReporter` that can be used for tests that need to
be run separately from the main commands.

Bug: 294725097
Test: ./gradlew
Change-Id: I82df1103e927686ab8206048d9102ac0c3434226
diff --git a/metalava-reporter/src/main/java/com/android/tools/metalava/reporter/Reporter.kt b/metalava-reporter/src/main/java/com/android/tools/metalava/reporter/Reporter.kt
index 3ab4a19..6472299 100644
--- a/metalava-reporter/src/main/java/com/android/tools/metalava/reporter/Reporter.kt
+++ b/metalava-reporter/src/main/java/com/android/tools/metalava/reporter/Reporter.kt
@@ -19,6 +19,7 @@
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.model.Location
 import java.io.File
+import java.io.PrintWriter
 
 interface Reporter {
 
@@ -93,3 +94,34 @@
      */
     fun showProgressTick()
 }
+
+/**
+ * Basic implementation of a [Reporter] that performs no filtering and simply outputs the message to
+ * the supplied [PrintWriter].
+ */
+class BasicReporter(private val stderr: PrintWriter) : Reporter {
+    override fun report(
+        id: Issues.Issue,
+        item: Item?,
+        message: String,
+        location: Location
+    ): Boolean {
+        stderr.println(
+            buildString {
+                append(item?.location() ?: location)
+                append(": ")
+                append(id.defaultLevel.name.lowercase())
+                append(": ")
+                append(message)
+                append(" [")
+                append(id.name)
+                append("]")
+            }
+        )
+        return true
+    }
+
+    override fun isSuppressed(id: Issues.Issue, item: Item?, message: String?): Boolean = false
+
+    override fun showProgressTick() {}
+}
diff --git a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
index 449d118..d402b7e 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
@@ -117,7 +117,7 @@
             // Set up class path to contain our main sources such that we can
             // resolve types in the stubs
             val roots = mutableListOf<File>()
-            extractRoots(options.sources, roots)
+            extractRoots(reporter, options.sources, roots)
             roots.addAll(options.sourcePath)
             val javaStubsCodebase =
                 psiSourceParser.parseSources(
diff --git a/src/main/java/com/android/tools/metalava/ConvertJarsToSignatureFiles.kt b/src/main/java/com/android/tools/metalava/ConvertJarsToSignatureFiles.kt
index 5925b17..d3d89a9 100644
--- a/src/main/java/com/android/tools/metalava/ConvertJarsToSignatureFiles.kt
+++ b/src/main/java/com/android/tools/metalava/ConvertJarsToSignatureFiles.kt
@@ -72,7 +72,7 @@
             // there: package private super classes etc.
             val jarCodebase =
                 loadFromJarFile(
-                    PsiSourceParser(psiEnvironmentManager),
+                    PsiSourceParser(psiEnvironmentManager, reporter),
                     apiJar,
                     preFiltered = false,
                     DefaultAnnotationManager()
diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt
index 89a4297..3d642ae 100644
--- a/src/main/java/com/android/tools/metalava/Driver.kt
+++ b/src/main/java/com/android/tools/metalava/Driver.kt
@@ -219,6 +219,7 @@
     val psiSourceParser =
         PsiSourceParser(
             psiEnvironmentManager,
+            reporter = reporter,
             javaLanguageLevel = options.javaLanguageLevel,
             kotlinLanguageLevel = options.kotlinLanguageLevel,
             allowImplicitRoot = options.allowImplicitRoot,
@@ -707,7 +708,7 @@
                     "No source files specified: recursively including all sources found in the source path (${options.sourcePath.joinToString()}})"
                 )
             }
-            gatherSources(options.sourcePath)
+            gatherSources(reporter, options.sourcePath)
         }
 
     progress("Reading Codebase: ")
diff --git a/src/main/java/com/android/tools/metalava/PsiSourceParser.kt b/src/main/java/com/android/tools/metalava/PsiSourceParser.kt
index 7f1f715..3f60fd6 100644
--- a/src/main/java/com/android/tools/metalava/PsiSourceParser.kt
+++ b/src/main/java/com/android/tools/metalava/PsiSourceParser.kt
@@ -26,6 +26,7 @@
 import com.android.tools.metalava.model.psi.PsiEnvironmentManager
 import com.android.tools.metalava.model.psi.packageHtmlToJavadoc
 import com.android.tools.metalava.reporter.Issues
+import com.android.tools.metalava.reporter.Reporter
 import com.google.common.collect.Lists
 import com.google.common.io.Files
 import com.intellij.pom.java.LanguageLevel
@@ -60,6 +61,7 @@
  */
 class PsiSourceParser(
     private val psiEnvironmentManager: PsiEnvironmentManager,
+    private val reporter: Reporter,
     private val javaLanguageLevel: LanguageLevel = defaultJavaLanguageLevel,
     private val kotlinLanguageLevel: LanguageVersionSettings = defaultKotlinLanguageLevel,
     private val allowImplicitRoot: Boolean = true,
@@ -84,7 +86,7 @@
             sourcePath.filter { it.path.isNotBlank() }.map { it.absoluteFile }.toMutableList()
         // Add in source roots implied by the source files
         if (allowImplicitRoot) {
-            extractRoots(absoluteSources, absoluteSourceRoots)
+            extractRoots(reporter, absoluteSources, absoluteSourceRoots)
         }
 
         val absoluteClasspath = classpath.map { it.absoluteFile }
@@ -220,7 +222,7 @@
 private fun skippableDirectory(file: File): Boolean =
     file.path.endsWith(".git") && file.name == ".git"
 
-private fun addSourceFiles(list: MutableList<File>, file: File) {
+private fun addSourceFiles(reporter: Reporter, list: MutableList<File>, file: File) {
     if (file.isDirectory) {
         if (skippableDirectory(file)) {
             return
@@ -236,7 +238,7 @@
         val files = file.listFiles()
         if (files != null) {
             for (child in files) {
-                addSourceFiles(list, child)
+                addSourceFiles(reporter, list, child)
             }
         }
     } else if (file.isFile) {
@@ -249,19 +251,20 @@
     }
 }
 
-fun gatherSources(sourcePath: List<File>): List<File> {
+fun gatherSources(reporter: Reporter, sourcePath: List<File>): List<File> {
     val sources = Lists.newArrayList<File>()
     for (file in sourcePath) {
         if (file.path.isBlank()) {
             // --source-path "" means don't search source path; use "." for pwd
             continue
         }
-        addSourceFiles(sources, file.absoluteFile)
+        addSourceFiles(reporter, sources, file.absoluteFile)
     }
     return sources.sortedWith(compareBy { it.name })
 }
 
 fun extractRoots(
+    reporter: Reporter,
     sources: List<File>,
     sourceRoots: MutableList<File> = mutableListOf()
 ): List<File> {
@@ -275,7 +278,7 @@
             continue
         }
 
-        val root = findRoot(file) ?: continue
+        val root = findRoot(reporter, file) ?: continue
         dirToRootCache[parent.path] = root
 
         if (!sourceRoots.contains(root)) {
@@ -290,7 +293,7 @@
  * If given a full path to a Java or Kotlin source file, produces the path to the source root if
  * possible.
  */
-private fun findRoot(file: File): File? {
+private fun findRoot(reporter: Reporter, file: File): File? {
     val path = file.path
     if (path.endsWith(SdkConstants.DOT_JAVA) || path.endsWith(SdkConstants.DOT_KT)) {
         val pkg = findPackage(file) ?: return null
@@ -303,7 +306,7 @@
             reporter.report(
                 Issues.IO_ERROR,
                 file,
-                "$PROGRAM_NAME was unable to determine the package name. " +
+                "Unable to determine the package name. " +
                     "This usually means that a source file was where the directory does not seem to match the package " +
                     "declaration; we expected the path $path to end with /${pkg.replace('.', '/') + '/' + file.name}"
             )
diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt
index 487b90a..45fd112 100644
--- a/src/test/java/com/android/tools/metalava/DriverTest.kt
+++ b/src/test/java/com/android/tools/metalava/DriverTest.kt
@@ -1355,7 +1355,11 @@
 
         if (checkCompilation && stubsDir != null) {
             val generated =
-                gatherSources(listOf(stubsDir)).asSequence().map { it.path }.toList().toTypedArray()
+                gatherSources(reporter, listOf(stubsDir))
+                    .asSequence()
+                    .map { it.path }
+                    .toList()
+                    .toTypedArray()
 
             // Also need to include on the compile path annotation classes referenced in the stubs
             val extraAnnotationsDir = File("stub-annotations/src/main/java")
@@ -1368,7 +1372,7 @@
                 )
             }
             val extraAnnotations =
-                gatherSources(listOf(extraAnnotationsDir))
+                gatherSources(reporter, listOf(extraAnnotationsDir))
                     .asSequence()
                     .map { it.path }
                     .toList()
diff --git a/src/test/java/com/android/tools/metalava/model/psi/PsiTestUtils.kt b/src/test/java/com/android/tools/metalava/model/psi/PsiTestUtils.kt
index bb6bd31..61f2619 100644
--- a/src/test/java/com/android/tools/metalava/model/psi/PsiTestUtils.kt
+++ b/src/test/java/com/android/tools/metalava/model/psi/PsiTestUtils.kt
@@ -21,12 +21,14 @@
 import com.android.tools.lint.checks.infrastructure.TestFile
 import com.android.tools.metalava.ENV_VAR_METALAVA_TESTS_RUNNING
 import com.android.tools.metalava.PsiSourceParser
+import com.android.tools.metalava.reporter.BasicReporter
 import com.android.tools.metalava.testing.findKotlinStdlibPaths
 import com.android.tools.metalava.testing.getAndroidJar
 import com.android.tools.metalava.testing.tempDirectory
 import com.android.tools.metalava.updateGlobalOptionsForTest
 import com.intellij.openapi.util.Disposer
 import java.io.File
+import java.io.PrintWriter
 import kotlin.test.assertNotNull
 
 inline fun testCodebase(vararg sources: TestFile, action: (PsiBasedCodebase) -> Unit) {
@@ -54,7 +56,8 @@
     val kotlinStdlibPaths = findKotlinStdlibPaths(sourcePaths)
     updateGlobalOptionsForTest(emptyArray())
 
-    return PsiSourceParser(psiEnvironmentManager)
+    val reporter = BasicReporter(PrintWriter(System.err))
+    return PsiSourceParser(psiEnvironmentManager, reporter)
         .parseSources(
             sources = sources.map { it.createFile(directory) },
             description = "Test Codebase",
diff --git a/src/test/java/com/android/tools/metalava/model/psi/PsiTypePrinterTest.kt b/src/test/java/com/android/tools/metalava/model/psi/PsiTypePrinterTest.kt
index 6255bb9..3c6c4c0 100644
--- a/src/test/java/com/android/tools/metalava/model/psi/PsiTypePrinterTest.kt
+++ b/src/test/java/com/android/tools/metalava/model/psi/PsiTypePrinterTest.kt
@@ -24,6 +24,7 @@
 import com.android.tools.metalava.model.Item
 import com.android.tools.metalava.nonNullSource
 import com.android.tools.metalava.nullableSource
+import com.android.tools.metalava.reporter.BasicReporter
 import com.android.tools.metalava.testing.TemporaryFolderOwner
 import com.android.tools.metalava.testing.getAndroidJar
 import com.android.tools.metalava.testing.java
@@ -878,9 +879,10 @@
         classPath.add(getAndroidJar())
 
         updateGlobalOptionsForTest(emptyArray())
+        val reporter = BasicReporter(PrintWriter(System.err))
         // TestDriver#check normally sets this for all the other tests
         val codebase =
-            PsiSourceParser(psiEnvironmentManagerRule.manager)
+            PsiSourceParser(psiEnvironmentManagerRule.manager, reporter)
                 .parseSources(
                     sourceFiles,
                     "test project",
diff --git a/src/test/java/com/android/tools/metalava/stub/StubsTest.kt b/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
index 0bcbf19..490b102 100644
--- a/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
+++ b/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
@@ -22,6 +22,7 @@
 import com.android.tools.metalava.extractRoots
 import com.android.tools.metalava.gatherSources
 import com.android.tools.metalava.model.FileFormat
+import com.android.tools.metalava.reporter
 import com.android.tools.metalava.supportParameterName
 import com.android.tools.metalava.systemApiSource
 import com.android.tools.metalava.testApiSource
@@ -1183,8 +1184,8 @@
         check(
             expectedIssues =
                 """
-            src/test/Something2.java: error: metalava was unable to determine the package name. This usually means that a source file was where the directory does not seem to match the package declaration; we expected the path TESTROOT/src/test/Something2.java to end with /test/wrong/Something2.java [IoError]
-            src/test/Something2.java: error: metalava was unable to determine the package name. This usually means that a source file was where the directory does not seem to match the package declaration; we expected the path TESTROOT/src/test/Something2.java to end with /test/wrong/Something2.java [IoError]
+            src/test/Something2.java: error: Unable to determine the package name. This usually means that a source file was where the directory does not seem to match the package declaration; we expected the path TESTROOT/src/test/Something2.java to end with /test/wrong/Something2.java [IoError]
+            src/test/Something2.java: error: Unable to determine the package name. This usually means that a source file was where the directory does not seem to match the package declaration; we expected the path TESTROOT/src/test/Something2.java to end with /test/wrong/Something2.java [IoError]
             """,
             sourceFiles =
                 arrayOf(
@@ -1227,8 +1228,8 @@
             projectSetup = { dir ->
                 // Make sure we handle blank/doc-only java doc files in root extraction
                 val src = listOf(File(dir, "src"))
-                val files = gatherSources(src)
-                val roots = extractRoots(files)
+                val files = gatherSources(reporter, src)
+                val roots = extractRoots(reporter, files)
                 assertEquals(1, roots.size)
                 assertEquals(src[0].path, roots[0].path)
             }