Add Markdown report (#4858)

Co-authored-by: Brais Gabín <braisgabin@gmail.com>
This commit is contained in:
Vitaly V. Pinchuk
2022-06-14 08:06:05 +03:00
committed by GitHub
parent 43ea42ca51
commit 5d49409814
23 changed files with 467 additions and 32 deletions

View File

@@ -10,6 +10,7 @@ dependencies {
implementation(projects.detektMetrics)
implementation(projects.detektPsiUtils)
implementation(projects.detektReportHtml)
implementation(projects.detektReportMd)
implementation(projects.detektReportTxt)
implementation(projects.detektReportXml)
implementation(projects.detektReportSarif)

View File

@@ -1,6 +1,7 @@
package io.gitlab.arturbosch.detekt.core.reporting
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.txt.TxtOutputReport
import io.github.detekt.report.xml.XmlOutputReport
@@ -18,6 +19,7 @@ internal fun defaultReportMapping(reportId: String) = when (reportId) {
XmlOutputReport::class.java.simpleName -> "xml"
HtmlOutputReport::class.java.simpleName -> "html"
SarifOutputReport::class.java.simpleName -> "sarif"
MdOutputReport::class.java.simpleName -> "md"
else -> reportId
}

View File

@@ -46,6 +46,7 @@ output-reports:
# - 'TxtOutputReport'
# - 'XmlOutputReport'
# - 'HtmlOutputReport'
# - 'MdOutputReport'
comments:
active: true

View File

@@ -1,6 +1,7 @@
package io.gitlab.arturbosch.detekt.core.reporting
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.xml.XmlOutputReport
import io.github.detekt.test.utils.StringPrintStream
@@ -24,6 +25,7 @@ class OutputFacadeSpec {
val plainOutputPath = createTempFileForTest("detekt", ".txt")
val htmlOutputPath = createTempFileForTest("detekt", ".html")
val xmlOutputPath = createTempFileForTest("detekt", ".xml")
val mdOutputPath = createTempFileForTest("detekt", ".md")
val spec = createNullLoggingSpec {
project {
@@ -33,6 +35,7 @@ class OutputFacadeSpec {
report { "html" to htmlOutputPath }
report { "txt" to plainOutputPath }
report { "xml" to xmlOutputPath }
report { "md" to mdOutputPath }
}
logging {
outputChannel = printStream
@@ -44,7 +47,8 @@ class OutputFacadeSpec {
assertThat(printStream.toString()).contains(
"Successfully generated ${TxtOutputReport().name} at $plainOutputPath",
"Successfully generated ${XmlOutputReport().name} at $xmlOutputPath",
"Successfully generated ${HtmlOutputReport().name} at $htmlOutputPath"
"Successfully generated ${HtmlOutputReport().name} at $htmlOutputPath",
"Successfully generated ${MdOutputReport().name} at $mdOutputPath"
)
}
}

View File

@@ -1,6 +1,7 @@
package io.gitlab.arturbosch.detekt.core.reporting
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.xml.XmlOutputReport
import io.github.detekt.test.utils.resourceAsPath
@@ -28,11 +29,12 @@ class OutputReportsSpec {
report { "txt" to Paths.get("/tmp/path2") }
report { reportUnderTest to Paths.get("/tmp/path3") }
report { "html" to Paths.get("D:_Gradle\\xxx\\xxx\\build\\reports\\detekt\\detekt.html") }
report { "md" to Paths.get("/tmp/path4") }
}.build().reports.toList()
@Test
fun `should parse multiple report entries`() {
assertThat(reports).hasSize(4)
assertThat(reports).hasSize(5)
}
@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
inner class `default report ids` {

View File

@@ -10,6 +10,7 @@ dependencies {
implementation(projects.detektRulesEmpty)
implementation(projects.detektFormatting)
implementation(projects.detektCli)
implementation(projects.detektUtils)
implementation(libs.jcommander)
testImplementation(projects.detektCore)

View File

@@ -1,10 +1,10 @@
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.out.MarkdownWriter
import io.gitlab.arturbosch.detekt.generator.out.PropertiesWriter
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.RuleSetPagePrinter
import io.gitlab.arturbosch.detekt.generator.printer.defaultconfig.ConfigPrinter

View File

@@ -1,10 +1,10 @@
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.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 {

View File

@@ -1,14 +1,14 @@
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.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>> {

View File

@@ -1,14 +1,14 @@
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.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> {

View File

@@ -1,8 +1,8 @@
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.out.markdown
import io.gitlab.arturbosch.detekt.generator.out.paragraph
object RuleSetPagePrinter : DocumentationPrinter<RuleSetPage> {

View File

@@ -1,7 +1,7 @@
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.out.yaml
import io.gitlab.arturbosch.detekt.generator.printer.DocumentationPrinter
object ConfigPrinter : DocumentationPrinter<List<RuleSetPage>> {
@@ -81,5 +81,6 @@ object ConfigPrinter : DocumentationPrinter<List<RuleSetPage>> {
# - 'TxtOutputReport'
# - 'XmlOutputReport'
# - 'HtmlOutputReport'
# - 'MdOutputReport'
""".trimIndent()
}

View File

@@ -1,13 +1,13 @@
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.generator.collection.Configuration
import io.gitlab.arturbosch.detekt.generator.collection.Rule
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetPage
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) {
printRuleSet(ruleSetPage.ruleSet, ruleSetPage.rules)

View File

@@ -1,5 +1,6 @@
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.valuesWithReason
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.Rule
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.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test

View 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)
}

View File

@@ -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)"

View File

@@ -0,0 +1 @@
io.github.detekt.report.md.MdOutputReport

View File

@@ -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)
)
)
}

View File

@@ -1,3 +1,7 @@
plugins {
id("module")
}
dependencies {
testImplementation(libs.assertj)
}

View File

@@ -1,6 +1,6 @@
@file:Suppress("detekt.TooManyFunctions")
package io.gitlab.arturbosch.detekt.generator.out
package io.github.detekt.utils
sealed class Markdown(open var content: String = "") {
fun append(value: String) {

View File

@@ -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 = "") {
fun append(value: String) {

View File

@@ -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.junit.jupiter.api.Nested

View File

@@ -17,6 +17,7 @@ include("detekt-metrics")
include("detekt-parser")
include("detekt-psi-utils")
include("detekt-report-html")
include("detekt-report-md")
include("detekt-report-sarif")
include("detekt-report-txt")
include("detekt-report-xml")