com.jetbrains.pluginverifier.output.markdown.MarkdownResultPrinter.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of verifier-cli Show documentation
Show all versions of verifier-cli Show documentation
Command-line interface for JetBrains Plugin Verifier with set of high-level tasks for plugin and IDE validation
package com.jetbrains.pluginverifier.output.markdown
import com.jetbrains.plugin.structure.base.problems.isError
import com.jetbrains.plugin.structure.base.utils.create
import com.jetbrains.pluginverifier.PluginVerificationResult
import com.jetbrains.pluginverifier.PluginVerificationTarget
import com.jetbrains.pluginverifier.dependencies.MissingDependency
import com.jetbrains.pluginverifier.dymamic.DynamicPluginStatus
import com.jetbrains.pluginverifier.dymamic.DynamicPlugins.simplifiedReasonsNotToLoadUnloadWithoutRestart
import com.jetbrains.pluginverifier.output.DYNAMIC_PLUGIN_FAIL
import com.jetbrains.pluginverifier.output.OutputOptions
import com.jetbrains.pluginverifier.output.ResultPrinter
import com.jetbrains.pluginverifier.results.problems.CompatibilityProblem
import com.jetbrains.pluginverifier.tasks.InvalidPluginFile
import com.jetbrains.pluginverifier.usages.deprecated.DeprecatedApiUsage
import com.jetbrains.pluginverifier.usages.experimental.ExperimentalApiUsage
import com.jetbrains.pluginverifier.usages.internal.InternalApiUsage
import com.jetbrains.pluginverifier.usages.nonExtendable.NonExtendableApiUsage
import com.jetbrains.pluginverifier.usages.overrideOnly.OverrideOnlyMethodUsage
import com.jetbrains.pluginverifier.warnings.CompatibilityWarning
import com.jetbrains.pluginverifier.warnings.PluginStructureWarning
import java.io.PrintWriter
import java.nio.file.Files
private const val REPORT_FILE_NAME = "report.md"
class MarkdownResultPrinter(private val out: PrintWriter) : ResultPrinter, AutoCloseable {
companion object {
fun create(verificationTarget: PluginVerificationTarget, outputOptions: OutputOptions): MarkdownResultPrinter {
val reportHtmlFile = outputOptions.getTargetReportDirectory(verificationTarget).resolve(REPORT_FILE_NAME)
val writer = PrintWriter(Files.newBufferedWriter(reportHtmlFile.create()))
return MarkdownResultPrinter(writer)
}
}
override fun printResults(results: List) {
markdown(out) {
results.forEach {
printResult(it, this)
}
}
}
fun printInvalidPluginFiles(invalidPluginFiles: List) {
markdown(out) {
when (invalidPluginFiles.size) {
0 -> return@markdown
1 -> {
h1("Invalid plugin")
paragraph("The following file specified for the verification is not a valid plugin.")
}
else -> {
h1("Invalid plugins")
out.println("The following files specified for the verification are not valid plugins.")
}
}
if (invalidPluginFiles.isNotEmpty()) {
for ((pluginFile, pluginProblems) in invalidPluginFiles) {
h2("${pluginFile.fileName}")
paragraph("Full path: `$pluginFile`")
val (pluginErrors, otherPluginProblems) = pluginProblems.partition { it.isError }
if (pluginErrors.isNotEmpty()) {
h3("Plugin Problems")
for (pluginError in pluginErrors) {
unorderedListItem("$pluginError")
}
unorderedListEnd()
}
if (otherPluginProblems.isNotEmpty()) {
val prefix = if (pluginErrors.isEmpty()) "" else "Additional "
h3("${prefix}Plugin Warnings")
for (pluginProblem in otherPluginProblems) {
unorderedListItem("$pluginProblem")
}
unorderedListEnd()
}
}
}
}
}
private fun printResult(pluginVerificationResult: PluginVerificationResult, markdown: Markdown) {
with(pluginVerificationResult) {
markdown.h1("Plugin $plugin against $verificationTarget")
markdown.paragraph(verificationVerdict)
if (this is PluginVerificationResult.Verified) {
markdown + this
}
}
}
override fun close() {
out.close()
}
}
fun markdown(out: PrintWriter, init: Markdown.() -> Unit): Markdown {
val markdown = Markdown(out)
markdown.init()
return markdown
}
class Markdown(private val out: PrintWriter) {
fun h1(header: String) {
out.println("# $header")
out.println()
}
fun h2(header: String): Markdown {
out.println("## $header")
out.println()
return this
}
fun h3(header: String) {
out.println("### $header")
out.println()
}
fun unorderedListItem(content: String) {
out.println("* $content")
}
fun unorderedListEnd() {
out.println()
}
fun paragraph(content: String): Markdown {
out.println(content)
out.appendLine()
return this
}
operator fun String.unaryPlus() {
out.println(this)
}
operator fun plus(value: String) {
out.println(value)
}
operator fun plus(@Suppress("UNUSED_PARAMETER") markdown: Markdown) {
// no-op. Concatenating two Markdown instances is equivalent to concatenating their Writers
}
}
private operator fun Markdown.plus(result: PluginVerificationResult.Verified) {
printVerificationResult(result)
}
private fun Markdown.printVerificationResult(result: PluginVerificationResult.Verified) = with(result) {
printVerificationResult("Plugin structure warnings", pluginStructureWarnings, PluginStructureWarning::describe)
printVerificationResult("Missing dependencies", dependenciesGraph.getDirectMissingDependencies(), MissingDependency::describe)
printVerificationResult("Compatibility warnings", compatibilityWarnings, CompatibilityWarning::describe)
printVerificationResult("Compatibility problems", compatibilityProblems, CompatibilityProblem::describe)
printVerificationResult("Deprecated API usages", deprecatedUsages, DeprecatedApiUsage::describe)
printVerificationResult("Experimental API usages", experimentalApiUsages, ExperimentalApiUsage::describe)
printVerificationResult("Internal API usages", internalApiUsages, InternalApiUsage::describe)
printVerificationResult("Override-only API usages", overrideOnlyMethodUsages, OverrideOnlyMethodUsage::describe)
printVerificationResult("Non-extendable API usages", nonExtendableApiUsages, NonExtendableApiUsage::describe)
val dynaStatus = "Dynamic Plugin Status"
when (val dynamicPluginStatus = dynamicPluginStatus) {
is DynamicPluginStatus.MaybeDynamic -> h2(dynaStatus) + paragraph("Plugin can probably be enabled or disabled without IDE restart")
is DynamicPluginStatus.NotDynamic -> {
h2(dynaStatus) +
paragraph(DYNAMIC_PLUGIN_FAIL)
dynamicPluginStatus.simplifiedReasonsNotToLoadUnloadWithoutRestart().forEach {
unorderedListItem(it)
}
unorderedListEnd()
}
null -> Unit
}
}
private fun Markdown.printVerificationResult(title: String,
items: Set, descriptionPropertyExtractor: (T) -> Pair) {
if (items.isNotEmpty()) {
h2("$title (${items.size})")
val shortToFullDescriptions = items.map(descriptionPropertyExtractor)
.groupBy({ it.first }, { it.second })
appendShortAndFullDescriptions(shortToFullDescriptions)
}
}
private fun Markdown.appendShortAndFullDescriptions(shortToFullDescriptions: Map>) {
shortToFullDescriptions.forEach { (shortDescription, fullDescriptions) ->
if (shortDescription.isNotEmpty()) {
h3(shortDescription)
}
for (fullDescription in fullDescriptions) {
fullDescription.lines().forEach { line ->
unorderedListItem(line)
}
}
unorderedListEnd()
}
}
fun PluginStructureWarning.describe() = "" to message
fun MissingDependency.describe() = "" to "$dependency: $missingReason"
fun CompatibilityWarning.describe() = shortDescription to fullDescription
fun CompatibilityProblem.describe() = shortDescription to fullDescription
fun DeprecatedApiUsage.describe() = shortDescription to fullDescription
fun ExperimentalApiUsage.describe() = shortDescription to fullDescription
fun InternalApiUsage.describe() = shortDescription to fullDescription
fun OverrideOnlyMethodUsage.describe() = shortDescription to fullDescription
fun NonExtendableApiUsage.describe() = shortDescription to fullDescription