mirror of
https://github.com/jlengrand/detekt.git
synced 2026-03-10 08:11:23 +00:00
Add Markdown report (#4858)
Co-authored-by: Brais Gabín <braisgabin@gmail.com>
This commit is contained in:
committed by
GitHub
parent
43ea42ca51
commit
5d49409814
@@ -10,6 +10,7 @@ dependencies {
|
|||||||
implementation(projects.detektMetrics)
|
implementation(projects.detektMetrics)
|
||||||
implementation(projects.detektPsiUtils)
|
implementation(projects.detektPsiUtils)
|
||||||
implementation(projects.detektReportHtml)
|
implementation(projects.detektReportHtml)
|
||||||
|
implementation(projects.detektReportMd)
|
||||||
implementation(projects.detektReportTxt)
|
implementation(projects.detektReportTxt)
|
||||||
implementation(projects.detektReportXml)
|
implementation(projects.detektReportXml)
|
||||||
implementation(projects.detektReportSarif)
|
implementation(projects.detektReportSarif)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.gitlab.arturbosch.detekt.core.reporting
|
package io.gitlab.arturbosch.detekt.core.reporting
|
||||||
|
|
||||||
import io.github.detekt.report.html.HtmlOutputReport
|
import io.github.detekt.report.html.HtmlOutputReport
|
||||||
|
import io.github.detekt.report.md.MdOutputReport
|
||||||
import io.github.detekt.report.sarif.SarifOutputReport
|
import io.github.detekt.report.sarif.SarifOutputReport
|
||||||
import io.github.detekt.report.txt.TxtOutputReport
|
import io.github.detekt.report.txt.TxtOutputReport
|
||||||
import io.github.detekt.report.xml.XmlOutputReport
|
import io.github.detekt.report.xml.XmlOutputReport
|
||||||
@@ -18,6 +19,7 @@ internal fun defaultReportMapping(reportId: String) = when (reportId) {
|
|||||||
XmlOutputReport::class.java.simpleName -> "xml"
|
XmlOutputReport::class.java.simpleName -> "xml"
|
||||||
HtmlOutputReport::class.java.simpleName -> "html"
|
HtmlOutputReport::class.java.simpleName -> "html"
|
||||||
SarifOutputReport::class.java.simpleName -> "sarif"
|
SarifOutputReport::class.java.simpleName -> "sarif"
|
||||||
|
MdOutputReport::class.java.simpleName -> "md"
|
||||||
else -> reportId
|
else -> reportId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ output-reports:
|
|||||||
# - 'TxtOutputReport'
|
# - 'TxtOutputReport'
|
||||||
# - 'XmlOutputReport'
|
# - 'XmlOutputReport'
|
||||||
# - 'HtmlOutputReport'
|
# - 'HtmlOutputReport'
|
||||||
|
# - 'MdOutputReport'
|
||||||
|
|
||||||
comments:
|
comments:
|
||||||
active: true
|
active: true
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.gitlab.arturbosch.detekt.core.reporting
|
package io.gitlab.arturbosch.detekt.core.reporting
|
||||||
|
|
||||||
import io.github.detekt.report.html.HtmlOutputReport
|
import io.github.detekt.report.html.HtmlOutputReport
|
||||||
|
import io.github.detekt.report.md.MdOutputReport
|
||||||
import io.github.detekt.report.txt.TxtOutputReport
|
import io.github.detekt.report.txt.TxtOutputReport
|
||||||
import io.github.detekt.report.xml.XmlOutputReport
|
import io.github.detekt.report.xml.XmlOutputReport
|
||||||
import io.github.detekt.test.utils.StringPrintStream
|
import io.github.detekt.test.utils.StringPrintStream
|
||||||
@@ -24,6 +25,7 @@ class OutputFacadeSpec {
|
|||||||
val plainOutputPath = createTempFileForTest("detekt", ".txt")
|
val plainOutputPath = createTempFileForTest("detekt", ".txt")
|
||||||
val htmlOutputPath = createTempFileForTest("detekt", ".html")
|
val htmlOutputPath = createTempFileForTest("detekt", ".html")
|
||||||
val xmlOutputPath = createTempFileForTest("detekt", ".xml")
|
val xmlOutputPath = createTempFileForTest("detekt", ".xml")
|
||||||
|
val mdOutputPath = createTempFileForTest("detekt", ".md")
|
||||||
|
|
||||||
val spec = createNullLoggingSpec {
|
val spec = createNullLoggingSpec {
|
||||||
project {
|
project {
|
||||||
@@ -33,6 +35,7 @@ class OutputFacadeSpec {
|
|||||||
report { "html" to htmlOutputPath }
|
report { "html" to htmlOutputPath }
|
||||||
report { "txt" to plainOutputPath }
|
report { "txt" to plainOutputPath }
|
||||||
report { "xml" to xmlOutputPath }
|
report { "xml" to xmlOutputPath }
|
||||||
|
report { "md" to mdOutputPath }
|
||||||
}
|
}
|
||||||
logging {
|
logging {
|
||||||
outputChannel = printStream
|
outputChannel = printStream
|
||||||
@@ -44,7 +47,8 @@ class OutputFacadeSpec {
|
|||||||
assertThat(printStream.toString()).contains(
|
assertThat(printStream.toString()).contains(
|
||||||
"Successfully generated ${TxtOutputReport().name} at $plainOutputPath",
|
"Successfully generated ${TxtOutputReport().name} at $plainOutputPath",
|
||||||
"Successfully generated ${XmlOutputReport().name} at $xmlOutputPath",
|
"Successfully generated ${XmlOutputReport().name} at $xmlOutputPath",
|
||||||
"Successfully generated ${HtmlOutputReport().name} at $htmlOutputPath"
|
"Successfully generated ${HtmlOutputReport().name} at $htmlOutputPath",
|
||||||
|
"Successfully generated ${MdOutputReport().name} at $mdOutputPath"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.gitlab.arturbosch.detekt.core.reporting
|
package io.gitlab.arturbosch.detekt.core.reporting
|
||||||
|
|
||||||
import io.github.detekt.report.html.HtmlOutputReport
|
import io.github.detekt.report.html.HtmlOutputReport
|
||||||
|
import io.github.detekt.report.md.MdOutputReport
|
||||||
import io.github.detekt.report.txt.TxtOutputReport
|
import io.github.detekt.report.txt.TxtOutputReport
|
||||||
import io.github.detekt.report.xml.XmlOutputReport
|
import io.github.detekt.report.xml.XmlOutputReport
|
||||||
import io.github.detekt.test.utils.resourceAsPath
|
import io.github.detekt.test.utils.resourceAsPath
|
||||||
@@ -28,11 +29,12 @@ class OutputReportsSpec {
|
|||||||
report { "txt" to Paths.get("/tmp/path2") }
|
report { "txt" to Paths.get("/tmp/path2") }
|
||||||
report { reportUnderTest to Paths.get("/tmp/path3") }
|
report { reportUnderTest to Paths.get("/tmp/path3") }
|
||||||
report { "html" to Paths.get("D:_Gradle\\xxx\\xxx\\build\\reports\\detekt\\detekt.html") }
|
report { "html" to Paths.get("D:_Gradle\\xxx\\xxx\\build\\reports\\detekt\\detekt.html") }
|
||||||
|
report { "md" to Paths.get("/tmp/path4") }
|
||||||
}.build().reports.toList()
|
}.build().reports.toList()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should parse multiple report entries`() {
|
fun `should parse multiple report entries`() {
|
||||||
assertThat(reports).hasSize(4)
|
assertThat(reports).hasSize(5)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -66,6 +68,13 @@ class OutputReportsSpec {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should properly parse MD report entry`() {
|
||||||
|
val mdRepot = reports[4]
|
||||||
|
assertThat(mdRepot.type).isEqualTo(defaultReportMapping(MdOutputReport::class.java.simpleName))
|
||||||
|
assertThat(mdRepot.path).isEqualTo(Paths.get("/tmp/path4"))
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
inner class `default report ids` {
|
inner class `default report ids` {
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ dependencies {
|
|||||||
implementation(projects.detektRulesEmpty)
|
implementation(projects.detektRulesEmpty)
|
||||||
implementation(projects.detektFormatting)
|
implementation(projects.detektFormatting)
|
||||||
implementation(projects.detektCli)
|
implementation(projects.detektCli)
|
||||||
|
implementation(projects.detektUtils)
|
||||||
implementation(libs.jcommander)
|
implementation(libs.jcommander)
|
||||||
|
|
||||||
testImplementation(projects.detektCore)
|
testImplementation(projects.detektCore)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package io.gitlab.arturbosch.detekt.generator
|
package io.gitlab.arturbosch.detekt.generator
|
||||||
|
|
||||||
|
import io.github.detekt.utils.yaml
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetPage
|
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetPage
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.MarkdownWriter
|
import io.gitlab.arturbosch.detekt.generator.out.MarkdownWriter
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.PropertiesWriter
|
import io.gitlab.arturbosch.detekt.generator.out.PropertiesWriter
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.YamlWriter
|
import io.gitlab.arturbosch.detekt.generator.out.YamlWriter
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.yaml
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.printer.DeprecatedPrinter
|
import io.gitlab.arturbosch.detekt.generator.printer.DeprecatedPrinter
|
||||||
import io.gitlab.arturbosch.detekt.generator.printer.RuleSetPagePrinter
|
import io.gitlab.arturbosch.detekt.generator.printer.RuleSetPagePrinter
|
||||||
import io.gitlab.arturbosch.detekt.generator.printer.defaultconfig.ConfigPrinter
|
import io.gitlab.arturbosch.detekt.generator.printer.defaultconfig.ConfigPrinter
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package io.gitlab.arturbosch.detekt.generator.collection
|
package io.gitlab.arturbosch.detekt.generator.collection
|
||||||
|
|
||||||
|
import io.github.detekt.utils.YamlNode
|
||||||
|
import io.github.detekt.utils.keyValue
|
||||||
|
import io.github.detekt.utils.list
|
||||||
|
import io.github.detekt.utils.listOfMaps
|
||||||
import io.gitlab.arturbosch.detekt.api.ValuesWithReason
|
import io.gitlab.arturbosch.detekt.api.ValuesWithReason
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.YamlNode
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.keyValue
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.list
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.listOfMaps
|
|
||||||
|
|
||||||
sealed interface DefaultValue {
|
sealed interface DefaultValue {
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package io.gitlab.arturbosch.detekt.generator.printer
|
package io.gitlab.arturbosch.detekt.generator.printer
|
||||||
|
|
||||||
|
import io.github.detekt.utils.bold
|
||||||
|
import io.github.detekt.utils.code
|
||||||
|
import io.github.detekt.utils.crossOut
|
||||||
|
import io.github.detekt.utils.description
|
||||||
|
import io.github.detekt.utils.h4
|
||||||
|
import io.github.detekt.utils.item
|
||||||
|
import io.github.detekt.utils.list
|
||||||
|
import io.github.detekt.utils.markdown
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.Configuration
|
import io.gitlab.arturbosch.detekt.generator.collection.Configuration
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.bold
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.code
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.crossOut
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.description
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.h4
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.item
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.list
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.markdown
|
|
||||||
|
|
||||||
internal object RuleConfigurationPrinter : DocumentationPrinter<List<Configuration>> {
|
internal object RuleConfigurationPrinter : DocumentationPrinter<List<Configuration>> {
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package io.gitlab.arturbosch.detekt.generator.printer
|
package io.gitlab.arturbosch.detekt.generator.printer
|
||||||
|
|
||||||
|
import io.github.detekt.utils.MarkdownContent
|
||||||
|
import io.github.detekt.utils.bold
|
||||||
|
import io.github.detekt.utils.codeBlock
|
||||||
|
import io.github.detekt.utils.h3
|
||||||
|
import io.github.detekt.utils.h4
|
||||||
|
import io.github.detekt.utils.markdown
|
||||||
|
import io.github.detekt.utils.paragraph
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.Active
|
import io.gitlab.arturbosch.detekt.generator.collection.Active
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.Rule
|
import io.gitlab.arturbosch.detekt.generator.collection.Rule
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.MarkdownContent
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.bold
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.codeBlock
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.h3
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.h4
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.markdown
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.paragraph
|
|
||||||
|
|
||||||
internal object RulePrinter : DocumentationPrinter<Rule> {
|
internal object RulePrinter : DocumentationPrinter<Rule> {
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package io.gitlab.arturbosch.detekt.generator.printer
|
package io.gitlab.arturbosch.detekt.generator.printer
|
||||||
|
|
||||||
|
import io.github.detekt.utils.markdown
|
||||||
|
import io.github.detekt.utils.paragraph
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetPage
|
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetPage
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.markdown
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.paragraph
|
|
||||||
|
|
||||||
object RuleSetPagePrinter : DocumentationPrinter<RuleSetPage> {
|
object RuleSetPagePrinter : DocumentationPrinter<RuleSetPage> {
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package io.gitlab.arturbosch.detekt.generator.printer.defaultconfig
|
package io.gitlab.arturbosch.detekt.generator.printer.defaultconfig
|
||||||
|
|
||||||
|
import io.github.detekt.utils.yaml
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetPage
|
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetPage
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.yaml
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.printer.DocumentationPrinter
|
import io.gitlab.arturbosch.detekt.generator.printer.DocumentationPrinter
|
||||||
|
|
||||||
object ConfigPrinter : DocumentationPrinter<List<RuleSetPage>> {
|
object ConfigPrinter : DocumentationPrinter<List<RuleSetPage>> {
|
||||||
@@ -81,5 +81,6 @@ object ConfigPrinter : DocumentationPrinter<List<RuleSetPage>> {
|
|||||||
# - 'TxtOutputReport'
|
# - 'TxtOutputReport'
|
||||||
# - 'XmlOutputReport'
|
# - 'XmlOutputReport'
|
||||||
# - 'HtmlOutputReport'
|
# - 'HtmlOutputReport'
|
||||||
|
# - 'MdOutputReport'
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package io.gitlab.arturbosch.detekt.generator.printer.defaultconfig
|
package io.gitlab.arturbosch.detekt.generator.printer.defaultconfig
|
||||||
|
|
||||||
|
import io.github.detekt.utils.YamlNode
|
||||||
|
import io.github.detekt.utils.keyValue
|
||||||
|
import io.github.detekt.utils.node
|
||||||
import io.gitlab.arturbosch.detekt.api.Config
|
import io.gitlab.arturbosch.detekt.api.Config
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.Configuration
|
import io.gitlab.arturbosch.detekt.generator.collection.Configuration
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.Rule
|
import io.gitlab.arturbosch.detekt.generator.collection.Rule
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetPage
|
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetPage
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetProvider
|
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetProvider
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.YamlNode
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.keyValue
|
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.node
|
|
||||||
|
|
||||||
internal fun YamlNode.printRuleSetPage(ruleSetPage: RuleSetPage) {
|
internal fun YamlNode.printRuleSetPage(ruleSetPage: RuleSetPage) {
|
||||||
printRuleSet(ruleSetPage.ruleSet, ruleSetPage.rules)
|
printRuleSet(ruleSetPage.ruleSet, ruleSetPage.rules)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.gitlab.arturbosch.detekt.generator.printer.defaultconfig
|
package io.gitlab.arturbosch.detekt.generator.printer.defaultconfig
|
||||||
|
|
||||||
|
import io.github.detekt.utils.yaml
|
||||||
import io.gitlab.arturbosch.detekt.api.Config
|
import io.gitlab.arturbosch.detekt.api.Config
|
||||||
import io.gitlab.arturbosch.detekt.api.valuesWithReason
|
import io.gitlab.arturbosch.detekt.api.valuesWithReason
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.Active
|
import io.gitlab.arturbosch.detekt.generator.collection.Active
|
||||||
@@ -8,7 +9,6 @@ import io.gitlab.arturbosch.detekt.generator.collection.DefaultValue
|
|||||||
import io.gitlab.arturbosch.detekt.generator.collection.Inactive
|
import io.gitlab.arturbosch.detekt.generator.collection.Inactive
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.Rule
|
import io.gitlab.arturbosch.detekt.generator.collection.Rule
|
||||||
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetProvider
|
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetProvider
|
||||||
import io.gitlab.arturbosch.detekt.generator.out.yaml
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|||||||
13
detekt-report-md/build.gradle.kts
Normal file
13
detekt-report-md/build.gradle.kts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
plugins {
|
||||||
|
id("module")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.detektMetrics)
|
||||||
|
implementation(projects.detektApi)
|
||||||
|
implementation(projects.detektUtils)
|
||||||
|
|
||||||
|
testImplementation(testFixtures(projects.detektApi))
|
||||||
|
testImplementation(libs.mockk)
|
||||||
|
testImplementation(libs.assertj)
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package io.github.detekt.report.md
|
||||||
|
|
||||||
|
import io.github.detekt.metrics.ComplexityReportGenerator
|
||||||
|
import io.github.detekt.psi.toUnifiedString
|
||||||
|
import io.github.detekt.utils.MarkdownContent
|
||||||
|
import io.github.detekt.utils.codeBlock
|
||||||
|
import io.github.detekt.utils.emptyLine
|
||||||
|
import io.github.detekt.utils.h1
|
||||||
|
import io.github.detekt.utils.h2
|
||||||
|
import io.github.detekt.utils.h3
|
||||||
|
import io.github.detekt.utils.item
|
||||||
|
import io.github.detekt.utils.list
|
||||||
|
import io.github.detekt.utils.markdown
|
||||||
|
import io.github.detekt.utils.paragraph
|
||||||
|
import io.gitlab.arturbosch.detekt.api.Detektion
|
||||||
|
import io.gitlab.arturbosch.detekt.api.Finding
|
||||||
|
import io.gitlab.arturbosch.detekt.api.OutputReport
|
||||||
|
import io.gitlab.arturbosch.detekt.api.ProjectMetric
|
||||||
|
import io.gitlab.arturbosch.detekt.api.SourceLocation
|
||||||
|
import io.gitlab.arturbosch.detekt.api.internal.whichDetekt
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
private const val DETEKT_WEBSITE_BASE_URL = "https://detekt.dev"
|
||||||
|
|
||||||
|
private const val EXTRA_LINES_IN_SNIPPET = 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains rule violations in Markdown format report.
|
||||||
|
* [See](https://detekt.dev/docs/introduction/configurations/#output-reports)
|
||||||
|
*/
|
||||||
|
class MdOutputReport : OutputReport() {
|
||||||
|
override val ending: String = "md"
|
||||||
|
|
||||||
|
override val name = "Markdown report"
|
||||||
|
|
||||||
|
override fun render(detektion: Detektion) = markdown {
|
||||||
|
h1 { "detekt" }
|
||||||
|
|
||||||
|
h2 { "Metrics" }
|
||||||
|
renderMetrics(detektion.metrics)
|
||||||
|
|
||||||
|
h2 { "Complexity Report" }
|
||||||
|
renderComplexity(getComplexityMetrics(detektion))
|
||||||
|
|
||||||
|
renderFindings(detektion.findings)
|
||||||
|
emptyLine()
|
||||||
|
|
||||||
|
paragraph {
|
||||||
|
val detektLink = link("detekt version ${renderVersion()}", "$DETEKT_WEBSITE_BASE_URL/")
|
||||||
|
"generated with $detektLink on ${renderDate()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderVersion(): String = whichDetekt() ?: "unknown"
|
||||||
|
|
||||||
|
private fun renderDate(): String {
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||||
|
return "${OffsetDateTime.now(ZoneOffset.UTC).format(formatter)} UTC"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getComplexityMetrics(detektion: Detektion): List<String> {
|
||||||
|
return ComplexityReportGenerator.create(detektion).generate().orEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MarkdownContent.renderMetrics(metrics: Collection<ProjectMetric>) {
|
||||||
|
list {
|
||||||
|
metrics.forEach { item { "%,d ${it.type}".format(Locale.US, it.value) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MarkdownContent.renderComplexity(complexityReport: List<String>) {
|
||||||
|
list {
|
||||||
|
complexityReport.forEach { item { it.trim() } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MarkdownContent.renderGroup(group: String, findings: List<Finding>) {
|
||||||
|
findings
|
||||||
|
.groupBy { it.id }
|
||||||
|
.toList()
|
||||||
|
.sortedBy { (rule, _) -> rule }
|
||||||
|
.forEach { (rule, ruleFindings) ->
|
||||||
|
renderRule(rule, group, ruleFindings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MarkdownContent.renderRule(rule: String, group: String, findings: List<Finding>) {
|
||||||
|
h3 { "$group, $rule (%,d)".format(Locale.US, findings.size) }
|
||||||
|
paragraph { (findings.first().issue.description) }
|
||||||
|
|
||||||
|
paragraph {
|
||||||
|
link(
|
||||||
|
"Documentation",
|
||||||
|
"$DETEKT_WEBSITE_BASE_URL/docs/rules/${group.toLowerCase(Locale.US)}#${rule.toLowerCase(Locale.US)}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
list {
|
||||||
|
findings
|
||||||
|
.sortedWith(compareBy({ it.file }, { it.location.source.line }, { it.location.source.column }))
|
||||||
|
.forEach {
|
||||||
|
item { renderFinding(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MarkdownContent.renderFindings(findings: Map<String, List<Finding>>) {
|
||||||
|
val total = findings.values
|
||||||
|
.asSequence()
|
||||||
|
.map { it.size }
|
||||||
|
.fold(0) { a, b -> a + b }
|
||||||
|
|
||||||
|
h2 { "Findings (%,d)".format(Locale.US, total) }
|
||||||
|
|
||||||
|
findings
|
||||||
|
.filter { it.value.isNotEmpty() }
|
||||||
|
.toList()
|
||||||
|
.sortedBy { (group, _) -> group }
|
||||||
|
.forEach { (group, groupFindings) ->
|
||||||
|
renderGroup(group, groupFindings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MarkdownContent.renderFinding(finding: Finding): String {
|
||||||
|
val filePath = finding.location.filePath.relativePath ?: finding.location.filePath.absolutePath
|
||||||
|
val location = "${filePath.toUnifiedString()}:${finding.location.source.line}:${finding.location.source.column}"
|
||||||
|
|
||||||
|
val message = if (finding.message.isNotEmpty()) {
|
||||||
|
codeBlock("") { finding.message }
|
||||||
|
} else { "" }
|
||||||
|
|
||||||
|
val psiFile = finding.entity.ktElement?.containingFile
|
||||||
|
val snippet = if (psiFile != null) {
|
||||||
|
val lineSequence = psiFile.text.splitToSequence('\n')
|
||||||
|
snippetCode(lineSequence, finding.startPosition)
|
||||||
|
} else { "" }
|
||||||
|
|
||||||
|
return "$location\n$message\n$snippet"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MarkdownContent.snippetCode(lines: Sequence<String>, location: SourceLocation): String {
|
||||||
|
val dropLineCount = max(location.line - 1 - EXTRA_LINES_IN_SNIPPET, 0)
|
||||||
|
val takeLineCount = EXTRA_LINES_IN_SNIPPET + 1 + min(location.line - 1, EXTRA_LINES_IN_SNIPPET)
|
||||||
|
var currentLineNumber = dropLineCount + 1
|
||||||
|
var text = ""
|
||||||
|
|
||||||
|
val lineNoSpace = (currentLineNumber + takeLineCount).toString().length
|
||||||
|
|
||||||
|
lines
|
||||||
|
.drop(dropLineCount)
|
||||||
|
.take(takeLineCount)
|
||||||
|
.forEach { line ->
|
||||||
|
val lineNo = ("$currentLineNumber ").take(lineNoSpace)
|
||||||
|
text += "$lineNo $line\n"
|
||||||
|
|
||||||
|
if (currentLineNumber == location.line) {
|
||||||
|
val positions = currentLineNumber.toString().length
|
||||||
|
val lineErr = "!".repeat(positions) + " ".repeat(location.column + lineNoSpace - positions)
|
||||||
|
text += "$lineErr^ error\n"
|
||||||
|
}
|
||||||
|
currentLineNumber++
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeBlock("kotlin") { text }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun MarkdownContent.link(text: String, url: String) = "[$text]($url)"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
io.github.detekt.report.md.MdOutputReport
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package io.github.detekt.report.md
|
||||||
|
|
||||||
|
import io.github.detekt.metrics.CognitiveComplexity
|
||||||
|
import io.github.detekt.metrics.processors.commentLinesKey
|
||||||
|
import io.github.detekt.metrics.processors.complexityKey
|
||||||
|
import io.github.detekt.metrics.processors.linesKey
|
||||||
|
import io.github.detekt.metrics.processors.logicalLinesKey
|
||||||
|
import io.github.detekt.metrics.processors.sourceLinesKey
|
||||||
|
import io.gitlab.arturbosch.detekt.api.Detektion
|
||||||
|
import io.gitlab.arturbosch.detekt.api.Finding
|
||||||
|
import io.gitlab.arturbosch.detekt.api.ProjectMetric
|
||||||
|
import io.gitlab.arturbosch.detekt.api.internal.whichDetekt
|
||||||
|
import io.gitlab.arturbosch.detekt.test.TestDetektion
|
||||||
|
import io.gitlab.arturbosch.detekt.test.createEntity
|
||||||
|
import io.gitlab.arturbosch.detekt.test.createFinding
|
||||||
|
import io.gitlab.arturbosch.detekt.test.createIssue
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class MdOutputReportSpec {
|
||||||
|
private val mdReport = MdOutputReport()
|
||||||
|
private val detektion = createTestDetektionWithMultipleSmells()
|
||||||
|
private val result = mdReport.render(detektion)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `renders Markdown structure correctly`() {
|
||||||
|
assertThat(result).contains("Metrics")
|
||||||
|
assertThat(result).contains("Complexity Report")
|
||||||
|
assertThat(result).contains("Findings")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `contains zero findings`() {
|
||||||
|
val result = mdReport.render(TestDetektion())
|
||||||
|
|
||||||
|
assertThat(result).contains("Findings (0)")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `contains the total number of findings`() {
|
||||||
|
assertThat(result).contains("Findings (3)")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `renders the 'generated with' text correctly`() {
|
||||||
|
val header = "generated with [detekt version ${whichDetekt()}](https://detekt.dev/) on "
|
||||||
|
|
||||||
|
assertThat(result).contains(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `renders the right file locations`() {
|
||||||
|
assertThat(result).contains("src/main/com/sample/Sample1.kt:9:17")
|
||||||
|
assertThat(result).contains("src/main/com/sample/Sample2.kt:13:17")
|
||||||
|
assertThat(result).contains("src/main/com/sample/Sample3.kt:14:16")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `renders the right number of issues per rule`() {
|
||||||
|
assertThat(result).contains("id_a (2)")
|
||||||
|
assertThat(result).contains("id_b (1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `renders the right violation messages for the rules`() {
|
||||||
|
assertThat(result).contains("Message finding 1")
|
||||||
|
assertThat(result).contains("Message finding 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `renders the right violation description for the rules`() {
|
||||||
|
assertThat(result).contains("Description id_a")
|
||||||
|
assertThat(result).contains("Description id_b")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `renders the right documentation links for the rules`() {
|
||||||
|
val detektion = object : TestDetektion() {
|
||||||
|
override val findings: Map<String, List<Finding>> = mapOf(
|
||||||
|
"Style" to listOf(
|
||||||
|
createFinding(createIssue("ValCouldBeVar"), createEntity(""))
|
||||||
|
),
|
||||||
|
"empty" to listOf(
|
||||||
|
createFinding(createIssue("EmptyBody"), createEntity("")),
|
||||||
|
createFinding(createIssue("EmptyIf"), createEntity(""))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = mdReport.render(detektion)
|
||||||
|
assertThat(result).contains("[Documentation](https://detekt.dev/docs/rules/style#valcouldbevar)")
|
||||||
|
assertThat(result).contains("[Documentation](https://detekt.dev/docs/rules/empty#emptybody)")
|
||||||
|
assertThat(result).contains("[Documentation](https://detekt.dev/docs/rules/empty#emptyif)")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `asserts that the generated HTML is the same even if we change the order of the findings`() {
|
||||||
|
val findings = findings()
|
||||||
|
val reversedFindings = findings
|
||||||
|
.reversedArray()
|
||||||
|
.map { (section, findings) -> section to findings.asReversed() }
|
||||||
|
.toTypedArray()
|
||||||
|
|
||||||
|
val firstDetektion = createMdDetektion(*findings)
|
||||||
|
val secondDetektion = createMdDetektion(*reversedFindings)
|
||||||
|
|
||||||
|
val firstReport = mdReport.render(firstDetektion)
|
||||||
|
val secondReport = mdReport.render(secondDetektion)
|
||||||
|
|
||||||
|
assertThat(firstReport).isEqualTo(secondReport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mockKtElement(): KtElement {
|
||||||
|
val ktElementMock = mockk<KtElement>()
|
||||||
|
val psiFileMock = mockk<PsiFile>()
|
||||||
|
val code = """
|
||||||
|
package com.example.test
|
||||||
|
|
||||||
|
import io.github.*
|
||||||
|
|
||||||
|
class Test() {
|
||||||
|
val greeting: String = "Hello, World!"
|
||||||
|
|
||||||
|
init {
|
||||||
|
println(greetings)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
println(greetings)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
every { psiFileMock.text } returns code
|
||||||
|
every { ktElementMock.containingFile } returns psiFileMock
|
||||||
|
return ktElementMock
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTestDetektionWithMultipleSmells(): Detektion {
|
||||||
|
val entity1 = createEntity(
|
||||||
|
path = "src/main/com/sample/Sample1.kt",
|
||||||
|
position = 9 to 17,
|
||||||
|
text = 17..20,
|
||||||
|
ktElement = mockKtElement(),
|
||||||
|
basePath = "/Users/tester/detekt/"
|
||||||
|
)
|
||||||
|
val entity2 = createEntity(
|
||||||
|
path = "src/main/com/sample/Sample2.kt",
|
||||||
|
ktElement = mockKtElement(),
|
||||||
|
position = 13 to 17,
|
||||||
|
basePath = "/Users/tester/detekt/"
|
||||||
|
)
|
||||||
|
val entity3 = createEntity(
|
||||||
|
path = "src/main/com/sample/Sample3.kt",
|
||||||
|
position = 14 to 16,
|
||||||
|
ktElement = mockKtElement(),
|
||||||
|
basePath = "/Users/tester/detekt/"
|
||||||
|
)
|
||||||
|
|
||||||
|
val issueA = createIssue("id_a")
|
||||||
|
val issueB = createIssue("id_b")
|
||||||
|
|
||||||
|
return createMdDetektion(
|
||||||
|
"Section-1" to listOf(
|
||||||
|
createFinding(issueA, entity1, "Message finding 1"),
|
||||||
|
createFinding(issueA, entity2, "Message finding 2")
|
||||||
|
),
|
||||||
|
"Section-2" to listOf(
|
||||||
|
createFinding(issueB, entity3, "")
|
||||||
|
)
|
||||||
|
).also {
|
||||||
|
it.addData(complexityKey, 10)
|
||||||
|
it.addData(CognitiveComplexity.KEY, 10)
|
||||||
|
it.addData(sourceLinesKey, 20)
|
||||||
|
it.addData(logicalLinesKey, 10)
|
||||||
|
it.addData(commentLinesKey, 2)
|
||||||
|
it.addData(linesKey, 2222)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMdDetektion(vararg findingPairs: Pair<String, List<Finding>>): Detektion {
|
||||||
|
return object : TestDetektion() {
|
||||||
|
override val findings: Map<String, List<Finding>> = findingPairs.toMap()
|
||||||
|
|
||||||
|
override val metrics: Collection<ProjectMetric> = listOf(
|
||||||
|
ProjectMetric("M1", 10_000),
|
||||||
|
ProjectMetric("M2", 2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findings(): Array<Pair<String, List<Finding>>> {
|
||||||
|
val issueA = createIssue("id_a")
|
||||||
|
val issueB = createIssue("id_b")
|
||||||
|
val issueC = createIssue("id_c")
|
||||||
|
|
||||||
|
val entity1 = createEntity("src/main/com/sample/Sample1.kt", 11 to 5)
|
||||||
|
val entity2 = createEntity("src/main/com/sample/Sample1.kt", 22 to 2)
|
||||||
|
val entity3 = createEntity("src/main/com/sample/Sample1.kt", 11 to 2)
|
||||||
|
val entity4 = createEntity("src/main/com/sample/Sample2.kt", 1 to 1)
|
||||||
|
|
||||||
|
return arrayOf(
|
||||||
|
"Section 1" to listOf(
|
||||||
|
createFinding(issueA, entity1),
|
||||||
|
createFinding(issueA, entity2),
|
||||||
|
createFinding(issueA, entity3),
|
||||||
|
createFinding(issueA, entity4),
|
||||||
|
createFinding(issueB, entity2),
|
||||||
|
createFinding(issueB, entity1),
|
||||||
|
createFinding(issueB, entity4)
|
||||||
|
),
|
||||||
|
"Section 2" to listOf(
|
||||||
|
createFinding(issueB, entity3),
|
||||||
|
createFinding(issueC, entity1),
|
||||||
|
createFinding(issueC, entity2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("module")
|
id("module")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation(libs.assertj)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@file:Suppress("detekt.TooManyFunctions")
|
@file:Suppress("detekt.TooManyFunctions")
|
||||||
|
|
||||||
package io.gitlab.arturbosch.detekt.generator.out
|
package io.github.detekt.utils
|
||||||
|
|
||||||
sealed class Markdown(open var content: String = "") {
|
sealed class Markdown(open var content: String = "") {
|
||||||
fun append(value: String) {
|
fun append(value: String) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package io.gitlab.arturbosch.detekt.generator.out
|
package io.github.detekt.utils
|
||||||
|
|
||||||
sealed class YML(open val indent: Int = 0, open var content: String = "") {
|
sealed class YML(open val indent: Int = 0, open var content: String = "") {
|
||||||
fun append(value: String) {
|
fun append(value: String) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package io.gitlab.arturbosch.detekt.generator.out
|
package io.github.detekt.utils
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
@@ -17,6 +17,7 @@ include("detekt-metrics")
|
|||||||
include("detekt-parser")
|
include("detekt-parser")
|
||||||
include("detekt-psi-utils")
|
include("detekt-psi-utils")
|
||||||
include("detekt-report-html")
|
include("detekt-report-html")
|
||||||
|
include("detekt-report-md")
|
||||||
include("detekt-report-sarif")
|
include("detekt-report-sarif")
|
||||||
include("detekt-report-txt")
|
include("detekt-report-txt")
|
||||||
include("detekt-report-xml")
|
include("detekt-report-xml")
|
||||||
|
|||||||
Reference in New Issue
Block a user