name.remal.gradle_plugins.dsl.utils.code_quality.FindBugsReport-writeHtml.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-plugins-kotlin-dsl Show documentation
Show all versions of gradle-plugins-kotlin-dsl Show documentation
Remal Gradle plugins: gradle-plugins-kotlin-dsl
@file:Suppress("MaxLineLength")
package name.remal.gradle_plugins.dsl.utils.code_quality
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
import name.remal.*
import org.jsoup.nodes.Element
import org.jsoup.parser.Parser.parseFragment
import org.jsoup.parser.Tag
import java.io.File
import java.io.OutputStream
import java.io.Writer
import java.nio.charset.StandardCharsets.UTF_8
fun FindBugsReport.writeHtmlTo(outputStream: OutputStream, projectDir: File? = null, toolName: String? = null, rootProjectDir: File? = projectDir) = outputStream.write(asHtmlString(projectDir, toolName, rootProjectDir).toByteArray(UTF_8))
fun FindBugsReport.writeHtmlTo(writer: Writer, projectDir: File? = null, toolName: String? = null, rootProjectDir: File? = projectDir) = writer.write(asHtmlString(projectDir, toolName, rootProjectDir))
fun FindBugsReport.writeHtmlTo(file: File, projectDir: File? = null, toolName: String? = null, rootProjectDir: File? = projectDir) = file.forceDeleteRecursively().createParentDirectories().writeText(asHtmlString(projectDir, toolName, rootProjectDir), UTF_8)
@Suppress("ComplexMethod", "LongMethod")
fun FindBugsReport.asHtmlString(projectDir: File? = null, toolName: String? = null, rootProjectDir: File? = projectDir) = buildHtmlString {
val tool = tool.nullIfEmpty() ?: toolName.nullIfEmpty()
val version = version.nullIfEmpty()
val title = buildString {
if (tool != null) {
append(tool)
append(" analysis report")
} else {
append("Analysis report")
}
}
head {
meta { attributes["http-equiv"] = "Content-Type"; attributes["content"] = "text/html;charset=UTF-8" }
meta { attributes["http-equiv"] = "X-UA-Compatible"; attributes["content"] = "IE=edge, chrome=1" }
meta("SKYPE_TOOLBAR", "SKYPE_TOOLBAR_PARSER_COMPATIBLE")
meta("viewport", "width=device-width, initial-scale=1, shrink-to-fit=no")
title(title)
FindBugsReportWebJars.resources
.filterKeys { it.endsWith(".css") }
.forEach { path, content ->
cssResource(content, path)
}
}
body {
val usedLanguages = mutableSetOf()
div("container") container@{
h1 { +title }
if (tool != null && version != null) {
p {
text(buildString {
append(tool)
append(" version: ")
append(version)
})
}
}
br
val sortedBugs = sortedBugs
if (sortedBugs.isEmpty()) {
p("alert alert-success") { +"No $tool violations were found" }
return@container
}
val srcDirs = projectDir?.let { projectDir ->
project?.srcDirs.default(emptySet()).asSequence()
.mapNotNull { projectDir.resolve(it) }
.mapNotNull(File::getAbsoluteFile)
.distinct()
.toList()
}
val hasCategories = sortedBugs.any { !it.category.isNullOrEmpty() }
val hasTypes = sortedBugs.any { !it.type.isNullOrEmpty() }
table("table table-borderless") {
sortedBugs.forEach { bug ->
tbody {
val className = bug.location?.className.nullIfEmpty()
val sourcePath = bug.location?.sourceFile.nullIfEmpty()
val startLine = bug.location?.startLine
val sourceFile = if (sourcePath != null && srcDirs != null) {
srcDirs.asSequence()
.map { it.resolve(sourcePath) }
.filter(File::isFile)
.mapNotNull(File::getAbsoluteFile)
.firstOrNull()
} else {
null
}
val relativePath = if (sourceFile != null && rootProjectDir != null) {
rootProjectDir.toPath().relativize(sourceFile.toPath())
.nullIf { isAbsolute }
?.toString()
?.replace(File.pathSeparatorChar, '/')
.nullIf { startsWith("../") }
} else {
null
}
tr("table-secondary") {
style = "font-size: 110%"
if (hasCategories) {
td {
style = "white-space: nowrap"
+bug.category.default()
}
}
if (hasTypes) {
td {
style = "white-space: nowrap"
+bug.type.default()
}
}
td {
attributes["width"] = "100%"
+buildString {
if (relativePath != null) {
append(relativePath)
if (startLine != null) append(':').append(startLine)
return@buildString
}
if (sourceFile != null) {
append(sourceFile.path)
if (startLine != null) append(':').append(startLine)
return@buildString
}
if (className != null) {
append(className)
if (startLine != null) append(':').append(startLine)
return@buildString
}
}
}
}
tr {
td {
if (hasCategories && hasTypes) {
colSpan = "3"
} else if (hasCategories || hasTypes) {
colSpan = "2"
}
div {
div {
strong { +bug.message.default() }
}
if (bug.message != bug.type?.let(types::get)?.let(FindBugsType::textDescription)) {
br
div {
unsafe {
val sourceHtml = bug.type?.let(types::get)?.let(FindBugsType::htmlDescription).nullIfEmpty() ?: return@unsafe
parseFragment("$sourceHtml", Element("div"), "").toList().forEach { node ->
if (node is Element) {
for (headerNumber in (1..6)) {
val elements = node.select("h$headerNumber").toList()
elements.forEach { element ->
element.before(Element("br"))
element.after(Element(Tag.valueOf("div"), "", element.attributes()).apply {
html("${element.html()}")
})
element.remove()
}
}
}
raw(node.outerHtml())
}
}
}
}
if (sourceFile != null && startLine != null) {
val endLine = Math.max(bug.location?.endLine ?: startLine, startLine)
val startIndex = startLine - 1
val endIndex = endLine - 1
val allLines = sourceFile.readLines(UTF_8)
val lineMargin = 3
val startMarginIndex = Math.max(0, startIndex - lineMargin)
val endMarginIndex = Math.min(endIndex + lineMargin + 1, allLines.size)
val linesToDisplay = allLines.subList(startMarginIndex, endMarginIndex)
br
pre("container") {
style = "max-height: 25rem"
classes += "line-numbers"
if (startMarginIndex > 0) {
(startMarginIndex + 1).toString().let {
attributes["data-start"] = it
attributes["data-line-offset"] = it
}
}
if (startLine != endLine) {
attributes["data-line"] = "$startLine-$endLine"
} else {
attributes["data-line"] = startLine.toString()
}
val sourceLanguage = sourceFile.sourceLanguage
if (sourceLanguage != null) {
classes += "language-$sourceLanguage"
usedLanguages.add(sourceLanguage)
}
code { +linesToDisplay.joinToString("\n") }
}
}
br
br
}
}
}
}
}
}
}
FindBugsReportWebJars
.resources
.filterKeys { it.endsWith(".js") }
.filterKeys { !it.startsWith("prism/") }
.forEach { path, content ->
jsResource(content, path)
}
FindBugsReportWebJars
.resources
.filterKeys { it.endsWith(".js") }
.filterKeys { path -> usedLanguages.any { path.startsWith("prism/$it.") } }
.forEach { path, content ->
jsResource(content, path)
}
}
}
private val File.sourceLanguage: String?
get() = when (extension) {
"as" -> "actionscript"
"css" -> "css"
"go" -> "go"
"htm" -> "html"
"html" -> "html"
"groovy" -> "groovy"
"ini" -> "ini"
"java" -> "java"
"js" -> "javascript"
"json" -> "json"
"kt" -> "kotlin"
"less" -> "less"
"lua" -> "lua"
"md" -> "markdown"
"php" -> "php"
"properties" -> "properties"
"proto" -> "protobuf"
"protobuf" -> "protobuf"
"py" -> "python"
"sh" -> "bash"
"jsx" -> "jsx"
"tsx" -> "tsx"
"rb" -> "ruby"
"ruby" -> "ruby"
"sass" -> "sass"
"scss" -> "scss"
"scala" -> "scala"
"smarty" -> "smarty"
"sql" -> "sql"
"svg" -> "svg"
"toml" -> "toml"
"twig" -> "twig"
"ts" -> "typescript"
"xml" -> "xml"
"yaml" -> "yaml"
"yml" -> "yaml"
else -> null
}
private fun buildHtmlString(builder: HTML.() -> Unit) = buildString {
append("")
appendHTML().html {
lang = "en"
builder(this)
}
}
private fun FlowOrMetaDataContent.cssResource(content: ByteArray, path: String? = null) {
style {
path.nullIfEmpty()?.let { attributes["data-path"] = it }
unsafe {
raw(String(content, UTF_8))
}
}
}
private fun FlowOrMetaDataContent.jsResource(content: ByteArray, path: String? = null) {
script {
path.nullIfEmpty()?.let { attributes["data-path"] = it }
unsafe {
raw(String(content, UTF_8))
}
}
}