All Downloads are FREE. Search and download functionalities are using the official Maven repository.

name.remal.gradle_plugins.dsl.utils.code_quality.FindBugsReport-writeHtml.kt Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
@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)) } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy