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.PrintWriter
21 
22 interface Reporter {
23 
24     /**
25      * Report an issue with a specific file.
26      *
27      * Delegates to calling `report(id, null, message, Location.forFile(file)`.
28      *
29      * @param id the id of the issue.
30      * @param file the optional source file for which the issue is reported.
31      * @param message the message to report.
32      * @param maximumSeverity the maximum [Severity] that will be reported. An issue that is
33      *   configured to have a higher [Severity] that this will use the [maximumSeverity] instead.
34      * @return true if the issue was reported false it is a known issue in a baseline file.
35      */
reportnull36     fun report(
37         id: Issues.Issue,
38         file: File?,
39         message: String,
40         maximumSeverity: Severity = Severity.UNLIMITED,
41     ): Boolean {
42         val location = FileLocation.forFile(file)
43         return report(id, null, message, location, maximumSeverity)
44     }
45 
46     /**
47      * Report an issue.
48      *
49      * The issue is handled as follows:
50      * 1. If the item is suppressed (see [isSuppressed]) then it will only be reported to a file
51      *    into which suppressed issues are reported and this will return `false`.
52      * 2. If possible the issue will be checked in a relevant baseline file to see if it is a known
53      *    issue and if so it will simply be ignored.
54      * 3. Otherwise, it will be reported at the appropriate severity to the command output and if
55      *    possible it will be recorded in a new baseline file that the developer can copy to silence
56      *    the issue in the future.
57      *
58      * If no [location] or [reportable] is provided then no location is reported in the error
59      * message, and the baseline file is neither checked nor updated.
60      *
61      * If a [location] is provided but no [reportable] then it is used both to report the message
62      * and as the baseline key to check and update the baseline file.
63      *
64      * If an [reportable] is provided but no [location] 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 both an [reportable] and [location] are provided then the [reportable] is used as the
68      * baseline key to check and update the baseline file and the [location] is used to report the
69      * message. The reason for that is the [location] is assumed to be a more accurate indication of
70      * where the problem lies but the [reportable] is assumed to provide a more stable key to use in
71      * the baseline as it will not change simply by adding and removing lines in the containing
72      * file.
73      *
74      * @param id the id of the issue.
75      * @param reportable the optional object for which the issue is reported.
76      * @param message the message to report.
77      * @param location the optional location to specify.
78      * @param maximumSeverity the maximum [Severity] that will be reported. An issue that is
79      *   configured to have a higher [Severity] that this will use the [maximumSeverity] instead.
80      * @return true if the issue was reported false it is a known issue in a baseline file.
81      */
reportnull82     fun report(
83         id: Issues.Issue,
84         reportable: Reportable?,
85         message: String,
86         location: FileLocation = FileLocation.UNKNOWN,
87         maximumSeverity: Severity = Severity.UNLIMITED,
88     ): Boolean
89 
90     /**
91      * Check to see whether the issue is suppressed.
92      * 1. If the [Severity] of the [Issues.Issue] is [Severity.HIDDEN] then this returns `true`.
93      * 2. If the [reportable] is `null` then this returns `false`.
94      * 3. If the item has a suppression annotation that lists the name of the issue then this
95      *    returns `true`.
96      * 4. Otherwise, this returns `false`.
97      */
98     fun isSuppressed(
99         id: Issues.Issue,
100         reportable: Reportable? = null,
101         message: String? = null
102     ): Boolean
103 }
104 
105 /**
106  * Basic implementation of a [Reporter] that performs no filtering and simply outputs the message to
107  * the supplied [PrintWriter].
108  */
109 class BasicReporter(private val stderr: PrintWriter) : Reporter {
110     override fun report(
111         id: Issues.Issue,
112         reportable: Reportable?,
113         message: String,
114         location: FileLocation,
115         maximumSeverity: Severity,
116     ): Boolean {
117         stderr.println(
118             buildString {
119                 val usableLocation = reportable?.fileLocation ?: location
120                 append(usableLocation.path)
121                 if (usableLocation.line > 0) {
122                     append(":")
123                     append(usableLocation.line)
124                 }
125                 append(": ")
126                 val severity = id.defaultLevel
127                 append(severity)
128                 append(": ")
129                 append(message)
130                 append(severity.messageSuffix)
131                 append(" [")
132                 append(id.name)
133                 append("]")
134             }
135         )
136         return true
137     }
138 
139     override fun isSuppressed(
140         id: Issues.Issue,
141         reportable: Reportable?,
142         message: String?
143     ): Boolean = false
144 }
145