• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.metalava.reporter
18 
19 import java.io.File
20 import java.io.OutputStream
21 import java.io.PrintWriter
22 import java.io.StringWriter
23 import java.io.Writer
24 
25 interface Reporter {
26 
27     /**
28      * Report an issue with a specific file.
29      *
30      * Delegates to calling `report(id, null, message, Location.forFile(file)`.
31      *
32      * @param id the id of the issue.
33      * @param file the optional source file for which the issue is reported.
34      * @param message the message to report.
35      * @param maximumSeverity the maximum [Severity] that will be reported. An issue that is
36      *   configured to have a higher [Severity] that this will use the [maximumSeverity] instead.
37      * @return true if the issue was reported false it is a known issue in a baseline file.
38      */
reportnull39     fun report(
40         id: Issues.Issue,
41         file: File?,
42         message: String,
43         maximumSeverity: Severity = Severity.UNLIMITED,
44     ): Boolean {
45         val location = FileLocation.forFile(file)
46         return report(id, null, message, location, maximumSeverity)
47     }
48 
49     /**
50      * Report an issue.
51      *
52      * The issue is handled as follows:
53      * 1. If the item is suppressed (see [isSuppressed]) then it will only be reported to a file
54      *    into which suppressed issues are reported and this will return `false`.
55      * 2. If possible the issue will be checked in a relevant baseline file to see if it is a known
56      *    issue and if so it will simply be ignored.
57      * 3. Otherwise, it will be reported at the appropriate severity to the command output and if
58      *    possible it will be recorded in a new baseline file that the developer can copy to silence
59      *    the issue in the future.
60      *
61      * If no [location] or [reportable] is provided then no location is reported in the error
62      * message, and the baseline file is neither checked nor updated.
63      *
64      * If a [location] is provided but no [reportable] then it is used both to report the message
65      * and as the baseline key to check and update the baseline file.
66      *
67      * If an [reportable] is provided but no [location] then it is used both to report the message
68      * and as the baseline key to check and update the baseline file.
69      *
70      * If both an [reportable] and [location] are provided then the [reportable] is used as the
71      * baseline key to check and update the baseline file and the [location] is used to report the
72      * message. The reason for that is the [location] is assumed to be a more accurate indication of
73      * where the problem lies but the [reportable] is assumed to provide a more stable key to use in
74      * the baseline as it will not change simply by adding and removing lines in the containing
75      * file.
76      *
77      * @param id the id of the issue.
78      * @param reportable the optional object for which the issue is reported.
79      * @param message the message to report.
80      * @param location the optional location to specify.
81      * @param maximumSeverity the maximum [Severity] that will be reported. An issue that is
82      *   configured to have a higher [Severity] that this will use the [maximumSeverity] instead.
83      * @return true if the issue was reported false it is a known issue in a baseline file.
84      */
reportnull85     fun report(
86         id: Issues.Issue,
87         reportable: Reportable?,
88         message: String,
89         location: FileLocation = FileLocation.UNKNOWN,
90         maximumSeverity: Severity = Severity.UNLIMITED,
91     ): Boolean
92 
93     /**
94      * Check to see whether the issue is suppressed.
95      * 1. If the [Severity] of the [Issues.Issue] is [Severity.HIDDEN] then this returns `true`.
96      * 2. If the [reportable] is `null` then this returns `false`.
97      * 3. If the item has a suppression annotation that lists the name of the issue then this
98      *    returns `true`.
99      * 4. Otherwise, this returns `false`.
100      */
101     fun isSuppressed(
102         id: Issues.Issue,
103         reportable: Reportable? = null,
104         message: String? = null
105     ): Boolean
106 }
107 
108 /**
109  * Abstract implementation of a [Reporter] that performs no filtering and delegates the handling of
110  * a report to [handleFormattedMessage].
111  */
112 abstract class AbstractBasicReporter : Reporter {
113     override fun report(
114         id: Issues.Issue,
115         reportable: Reportable?,
116         message: String,
117         location: FileLocation,
118         maximumSeverity: Severity,
119     ): Boolean {
120         val formattedMessage = buildString {
121             val usableLocation = reportable?.fileLocation ?: location
122             append(usableLocation.path)
123             if (usableLocation.line > 0) {
124                 append(":")
125                 append(usableLocation.line)
126             }
127             append(": ")
128             val severity = id.defaultLevel
129             append(severity)
130             append(": ")
131             append(message)
132             append(severity.messageSuffix)
133             append(" [")
134             append(id.name)
135             append("]")
136         }
137         return handleFormattedMessage(formattedMessage)
138     }
139 
140     abstract fun handleFormattedMessage(formattedMessage: String): Boolean
141 
142     override fun isSuppressed(
143         id: Issues.Issue,
144         reportable: Reportable?,
145         message: String?
146     ): Boolean = false
147 }
148 
149 /**
150  * Basic implementation of a [Reporter] that performs no filtering and simply outputs the message to
151  * the supplied [PrintWriter].
152  */
153 class BasicReporter(private val stderr: PrintWriter) : AbstractBasicReporter() {
154     constructor(writer: Writer) : this(stderr = PrintWriter(writer))
155 
156     constructor(outputStream: OutputStream) : this(stderr = PrintWriter(outputStream))
157 
handleFormattedMessagenull158     override fun handleFormattedMessage(formattedMessage: String): Boolean {
159         stderr.println(formattedMessage)
160         stderr.flush()
161         return true
162     }
163 
isSuppressednull164     override fun isSuppressed(
165         id: Issues.Issue,
166         reportable: Reportable?,
167         message: String?
168     ): Boolean = false
169 }
170 
171 /** A [Reporter] which will record issues in an internal buffer, accessible through [issues]. */
172 class RecordingReporter : AbstractBasicReporter() {
173     private val stringWriter = StringWriter()
174 
175     override fun handleFormattedMessage(formattedMessage: String): Boolean {
176         stringWriter.append(formattedMessage).append("\n")
177         return true
178     }
179 
180     val issues: String
181         get() = stringWriter.toString().trim()
182 
183     /** Remove and return any existing issues. */
184     fun removeIssues(): String {
185         val issues = stringWriter.toString().trim()
186         stringWriter.buffer.setLength(0)
187         return issues
188     }
189 }
190 
191 /**
192  * A [Reporter] which will throw an exception for the first issue, even warnings or hidden, that is
193  * reported.
194  *
195  * Safe to use when no issues are expected as it will prevent any issues from being silently
196  * ignored.
197  */
198 class ThrowingReporter private constructor() : AbstractBasicReporter() {
handleFormattedMessagenull199     override fun handleFormattedMessage(formattedMessage: String): Boolean {
200         error(formattedMessage)
201     }
202 
203     companion object {
204         val INSTANCE = ThrowingReporter()
205     }
206 }
207