[go: nahoru, domu]

blob: 7194786c3537ee7375d64b9d719ec331918734ff [file] [log] [blame]
Alan Viverette15959fb2021-05-18 12:00:23 -04001#!/usr/bin/env kotlin
2
3/*
4 * Copyright 2021 The Android Open Source Project
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
Tiem Song3e36fe32022-06-01 15:09:15 -070019/**
20 * To run .kts files, follow these steps:
21 *
22 * 1. Download and install the Kotlin compiler (kotlinc). There are several ways to do this; see
23 * https://kotlinlang.org/docs/command-line.html
24 * 2. Run the script from the command line:
25 * <path_to>/kotlinc -script <script_file>.kts <arguments>
26 */
27
Alan Viverette15959fb2021-05-18 12:00:23 -040028@file:Repository("https://repo1.maven.org/maven2")
29@file:DependsOn("junit:junit:4.11")
30
31import org.junit.Assert.assertEquals
32import org.junit.Test
33import org.w3c.dom.Document
34import org.w3c.dom.Element
35import org.w3c.dom.Node
36import org.w3c.dom.NodeList
37import org.xml.sax.InputSource
38import java.io.File
39import java.io.StringReader
40import javax.xml.parsers.DocumentBuilderFactory
41import kotlin.system.exitProcess
42
Alan Viverette15959fb2021-05-18 12:00:23 -040043if (args.isEmpty()) {
44 println("Expected space-delimited list of files. Consider parsing all baselines with:")
45 println(" ./<path-to-script> `find . -name lint-baseline.xml`")
46 println("Also, consider updating baselines before running the script using:")
Tiem Song3e36fe32022-06-01 15:09:15 -070047 println(" ./gradlew updateLintBaseline --continue")
Alan Viverette15959fb2021-05-18 12:00:23 -040048 exitProcess(1)
49}
50
Tiem Song3e36fe32022-06-01 15:09:15 -070051if (args[0] == "test") {
52 runTests()
53 println("All tests passed")
54 exitProcess(0)
55}
56
Alan Viverette15959fb2021-05-18 12:00:23 -040057val missingFiles = args.filter { arg -> !File(arg).exists() }
58if (missingFiles.isNotEmpty()) {
59 println("Could not find files:\n ${missingFiles.joinToString("\n ")}")
60 exitProcess(1)
61}
62
63val executionPath = File(".")
64// TODO: Consider adding argument "--output <output-file-path>"
65val csvOutputFile = File("output.csv")
66val csvData = StringBuilder()
67val columnLabels = listOf(
68 "Baseline",
69 "ID",
70 "Message",
71 "Error",
72 "Location",
73 "Line"
74)
75
76// Emit labels into the CSV if it's being created from scratch.
77if (!csvOutputFile.exists()) {
78 csvData.append(columnLabels.joinToString(","))
79 csvData.append("\n")
80}
81
82// For each file, emit one issue per line into the CSV.
83args.forEach { lintBaselinePath ->
84 val lintBaselineFile = File(lintBaselinePath)
85 println("Parsing ${lintBaselineFile.path}...")
86
87 val lintIssuesList = LintBaselineParser.parse(lintBaselineFile)
88 lintIssuesList.forEach { lintIssues ->
89 lintIssues.issues.forEach { lintIssue ->
90 val columns = listOf(
91 lintIssues.file.toRelativeString(executionPath),
92 lintIssue.id,
93 lintIssue.message,
94 lintIssue.errorLines.joinToString("\n"),
95 lintIssue.locations.getOrNull(0)?.file ?: "",
96 lintIssue.locations.getOrNull(0)?.line?.toString() ?: "",
97 )
98 csvData.append(columns.joinToString(",") { data ->
99 // Wrap every item with quotes and escape existing quotes.
100 "\"${data.replace("\"", "\"\"")}\""
101 })
102 csvData.append("\n")
103 }
104 }
105}
106
107csvOutputFile.appendText(csvData.toString())
108
109println("Wrote CSV output to ${csvOutputFile.path} for ${args.size} baselines")
110
111object LintBaselineParser {
112 fun parse(lintBaselineFile: File): List<LintIssues> {
113 val builderFactory = DocumentBuilderFactory.newInstance()!!
114 val docBuilder = builderFactory.newDocumentBuilder()!!
115 val doc: Document = docBuilder.parse(lintBaselineFile)
116 return parseIssuesListFromDocument(doc, lintBaselineFile)
117 }
118
119 fun parse(lintBaselineText: String): List<LintIssues> {
120 val builderFactory = DocumentBuilderFactory.newInstance()!!
121 val docBuilder = builderFactory.newDocumentBuilder()!!
122 val doc: Document = docBuilder.parse(InputSource(StringReader(lintBaselineText)))
123 return parseIssuesListFromDocument(doc, File("."))
124 }
125
126 private fun parseIssuesListFromDocument(doc: Document, file: File): List<LintIssues> =
127 doc.getElementsByTagName("issues").mapElementsNotNull { issues ->
128 LintIssues(
129 file = file,
130 issues = parseIssueListFromIssues(issues),
131 )
132 }
133
134 private fun parseIssueListFromIssues(issues: Element): List<LintIssue> =
135 issues.getElementsByTagName("issue").mapElementsNotNull { issue ->
136 LintIssue(
137 id = issue.getAttribute("id"),
138 message = issue.getAttribute("message"),
139 errorLines = parseErrorLineListFromIssue(issue),
140 locations = parseLocationListFromIssue(issue),
141 )
142 }
143
144 private fun parseLocationListFromIssue(issue: Element): List<LintLocation> =
145 issue.getElementsByTagName("location").mapElementsNotNull { location ->
146 LintLocation(
147 file = location.getAttribute("file"),
148 line = location.getAttribute("line")?.toIntOrNull() ?: 0,
149 column = location.getAttribute("column")?.toIntOrNull() ?: 0,
150 )
151 }
152
153 private fun parseErrorLineListFromIssue(issue: Element): List<String> {
154 val list = mutableListOf<String>()
155 var i = 1
156 while (issue.hasAttribute("errorLine$i")) {
157 issue.getAttribute("errorLine$i")?.let{ list.add(it) }
158 i++
159 }
160 return list.toList()
161 }
162
163 // This MUST be inside the class, otherwise we'll get a compilation error.
164 private fun <T> NodeList.mapElementsNotNull(transform: (element: Element) -> T?): List<T> {
165 val list = mutableListOf<T>()
166 for (i in 0 until length) {
167 val node = item(i)
168 if (node.nodeType == Node.ELEMENT_NODE && node is Element) {
169 transform(node)?.let { list.add(it) }
170 }
171 }
172 return list.toList()
173 }
174}
175
176data class LintIssues(
177 val file: File,
178 val issues: List<LintIssue>,
179)
180
181data class LintIssue(
182 val id: String,
183 val message: String,
184 val errorLines: List<String>,
185 val locations: List<LintLocation>,
186)
187
188data class LintLocation(
189 val file: String,
190 val line: Int,
191 val column: Int,
192)
193
194fun runTests() {
195 `Baseline with one issue parses contents correctly`()
196 `Empty baseline has no issues`()
197}
198
199@Test
200fun `Baseline with one issue parses contents correctly`() {
201 /* ktlint-disable max-line-length */
202 var lintBaselineText = """
203 <?xml version="1.0" encoding="UTF-8"?>
204 <issues format="5" by="lint 4.2.0-beta06" client="gradle" variant="debug" version="4.2.0-beta06">
205
206 <issue
207 id="ClassVerificationFailure"
208 message="This call references a method added in API level 19; however, the containing class androidx.print.PrintHelper is reachable from earlier API levels and will fail run-time class verification."
209 errorLine1=" PrintAttributes attr = new PrintAttributes.Builder()"
210 errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
211 <location
212 file="src/main/java/androidx/print/PrintHelper.java"
213 line="271"
214 column="32"/>
215 </issue>
216 </issues>
217 """.trimIndent()
218 /* ktlint-enable max-line-length */
219
220 var listIssues = LintBaselineParser.parse(lintBaselineText)
221 assertEquals(1, listIssues.size)
222
223 var issues = listIssues[0].issues
224 assertEquals(1, issues.size)
225
226 var issue = issues[0]
227 assertEquals("ClassVerificationFailure", issue.id)
228 assertEquals("This call references a method added in API level 19; however, the containing " +
229 "class androidx.print.PrintHelper is reachable from earlier API levels and will fail " +
230 "run-time class verification.", issue.message)
231 assertEquals(2, issue.errorLines.size)
232 assertEquals(1, issue.locations.size)
233
234 var location = issue.locations[0]
235 assertEquals("src/main/java/androidx/print/PrintHelper.java", location.file)
236 assertEquals(271, location.line)
237 assertEquals(32, location.column)
238}
239
240@Test
241fun `Empty baseline has no issues`() {
242 /* ktlint-disable max-line-length */
243 var lintBaselineText = """
244 <?xml version="1.0" encoding="UTF-8"?>
245 <issues format="5" by="lint 4.2.0-beta06" client="gradle" version="4.2.0-beta06">
246
247 </issues>
248 """.trimIndent()
249 /* ktlint-enable max-line-length */
250
251 var listIssues = LintBaselineParser.parse(lintBaselineText)
252 assertEquals(1, listIssues.size)
253
254 var issues = listIssues[0].issues
255 assertEquals(0, issues.size)
256}