Skip to content

Commit

Permalink
Split rule documentation printer to improve testability (#4578)
Browse files Browse the repository at this point in the history
* split rule documentation printer to improve testability

* add tests for missed code paths

Co-authored-by: Markus Schwarz <post@markus-schwarz.net>
  • Loading branch information
marschwar and Markus Schwarz committed Feb 10, 2022
1 parent e0e4ae2 commit d50224e
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 97 deletions.
Expand Up @@ -4,6 +4,7 @@ package io.gitlab.arturbosch.detekt.generator.out

sealed class Markdown(open var content: String = "") {
fun append(value: String) {
if (value.isEmpty()) return
content = if (content.isEmpty()) {
value
} else {
Expand Down
@@ -0,0 +1,43 @@
package io.gitlab.arturbosch.detekt.generator.printer

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>> {

override fun print(item: List<Configuration>): String {
if (item.isEmpty()) return ""
return markdown {
h4 { "Configuration options:" }
list {
item.forEach {
val defaultValues = it.defaultValue.getQuotedIfNecessary()
val defaultAndroidValues = it.defaultAndroidValue?.getQuotedIfNecessary()
val defaultString = if (defaultAndroidValues != null) {
"(default: ${code { defaultValues }}) (android default: ${code { defaultAndroidValues }})"
} else {
"(default: ${code { defaultValues }})"
}
if (it.isDeprecated()) {
item {
crossOut { code { it.name } } + " " + defaultString
}
description { "${bold { "Deprecated" }}: ${it.deprecated}" }
} else {
item {
code { it.name } + " " + defaultString
}
}
description { it.description }
}
}
}
}
}
@@ -0,0 +1,65 @@
package io.gitlab.arturbosch.detekt.generator.printer

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> {

override fun print(item: Rule): String {
return markdown {
h3 { item.name }

if (item.description.isNotEmpty()) {
paragraph { item.description }
} else {
paragraph { "TODO: Specify description" }
}

paragraph {
"${bold { "Active by default" }}: ${if (item.defaultActivationStatus.active) "Yes" else "No"}" +
((item.defaultActivationStatus as? Active)?.let { " - Since v${it.since}" }.orEmpty())
}

if (item.requiresTypeResolution) {
paragraph {
bold { "Requires Type Resolution" }
}
}

if (item.debt.isNotEmpty()) {
paragraph {
"${bold { "Debt" }}: ${item.debt}"
}
}

if (!item.aliases.isNullOrEmpty()) {
paragraph {
"${bold { "Aliases" }}: ${item.aliases}"
}
}

markdown { RuleConfigurationPrinter.print(item.configuration) }

printRuleCodeExamples(item)
}
}

private fun MarkdownContent.printRuleCodeExamples(rule: Rule) {
if (rule.nonCompliantCodeExample.isNotEmpty()) {
h4 { "Noncompliant Code:" }
paragraph { codeBlock { rule.nonCompliantCodeExample } }
}

if (rule.compliantCodeExample.isNotEmpty()) {
h4 { "Compliant Code:" }
paragraph { codeBlock { rule.compliantCodeExample } }
}
}
}
@@ -1,18 +1,6 @@
package io.gitlab.arturbosch.detekt.generator.printer

import io.gitlab.arturbosch.detekt.generator.collection.Active
import io.gitlab.arturbosch.detekt.generator.collection.Rule
import io.gitlab.arturbosch.detekt.generator.collection.RuleSetPage
import io.gitlab.arturbosch.detekt.generator.out.MarkdownContent
import io.gitlab.arturbosch.detekt.generator.out.bold
import io.gitlab.arturbosch.detekt.generator.out.code
import io.gitlab.arturbosch.detekt.generator.out.codeBlock
import io.gitlab.arturbosch.detekt.generator.out.crossOut
import io.gitlab.arturbosch.detekt.generator.out.description
import io.gitlab.arturbosch.detekt.generator.out.h3
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
import io.gitlab.arturbosch.detekt.generator.out.paragraph

Expand All @@ -26,83 +14,8 @@ object RuleSetPagePrinter : DocumentationPrinter<RuleSetPage> {
paragraph { "TODO: Specify description" }
}
item.rules.forEach {
markdown { printRule(it) }
markdown { RulePrinter.print(it) }
}
}
}

private fun printRule(rule: Rule): String {
return markdown {
h3 { rule.name }

if (rule.description.isNotEmpty()) {
paragraph { rule.description }
} else {
paragraph { "TODO: Specify description" }
}

paragraph {
"${bold { "Active by default" }}: ${if (rule.defaultActivationStatus.active) "Yes" else "No"}" +
((rule.defaultActivationStatus as? Active)?.let { " - Since v${it.since}" }.orEmpty())
}

if (rule.requiresTypeResolution) {
paragraph {
bold { "Requires Type Resolution" }
}
}

if (rule.debt.isNotEmpty()) {
paragraph {
"${bold { "Debt" }}: ${rule.debt}"
}
}

if (!rule.aliases.isNullOrEmpty()) {
paragraph {
"${bold { "Aliases" }}: ${rule.aliases}"
}
}

if (rule.configuration.isNotEmpty()) {
h4 { "Configuration options:" }
list {
rule.configuration.forEach {
val defaultValues = it.defaultValue.getQuotedIfNecessary()
val defaultAndroidValues = it.defaultAndroidValue?.getQuotedIfNecessary()
val defaultString = if (defaultAndroidValues != null) {
"(default: ${code { defaultValues }}) (android default: ${code { defaultAndroidValues }})"
} else {
"(default: ${code { defaultValues }})"
}
if (it.isDeprecated()) {
item {
crossOut { code { it.name } } + " " + defaultString
}
description { "${bold { "Deprecated" }}: ${it.deprecated}" }
} else {
item {
code { it.name } + " " + defaultString
}
}
description { it.description }
}
}
}

printRuleCodeExamples(rule)
}
}

private fun MarkdownContent.printRuleCodeExamples(rule: Rule) {
if (rule.nonCompliantCodeExample.isNotEmpty()) {
h4 { "Noncompliant Code:" }
paragraph { codeBlock { rule.nonCompliantCodeExample } }
}

if (rule.compliantCodeExample.isNotEmpty()) {
h4 { "Compliant Code:" }
paragraph { codeBlock { rule.compliantCodeExample } }
}
}
}
@@ -0,0 +1,81 @@
package io.gitlab.arturbosch.detekt.generator.printer

import io.gitlab.arturbosch.detekt.generator.collection.Configuration
import io.gitlab.arturbosch.detekt.generator.collection.DefaultValue
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test

internal class RuleConfigurationPrinterTest {
private val configTemplate = Configuration(
name = "configName",
description = "config description",
defaultValue = DefaultValue.of(true),
defaultAndroidValue = null,
deprecated = null
)

@Nested
inner class DefaultValues {
@Test
fun `boolean default`() {
val config = configTemplate.copy(defaultValue = DefaultValue.of(true))
val actual = RuleConfigurationPrinter.print(listOf(config))
assertThat(actual).contains("""* ``configName`` (default: ``true``)""")
}

@Test
fun `int default`() {
val config = configTemplate.copy(defaultValue = DefaultValue.of(99))
val actual = RuleConfigurationPrinter.print(listOf(config))
assertThat(actual).contains("""* ``configName`` (default: ``99``)""")
}

@Test
fun `int default with groupings`() {
val config = configTemplate.copy(defaultValue = DefaultValue.of(99_999))
val actual = RuleConfigurationPrinter.print(listOf(config))
assertThat(actual).contains("""* ``configName`` (default: ``99999``)""")
}

@Test
fun `string default`() {
val config = configTemplate.copy(defaultValue = DefaultValue.of("abc"))
val actual = RuleConfigurationPrinter.print(listOf(config))
assertThat(actual).contains("""* ``configName`` (default: ``'abc'``)""")
}

@Test
fun `string list default`() {
val config = configTemplate.copy(defaultValue = DefaultValue.of(listOf("a", "b", "c")))
val actual = RuleConfigurationPrinter.print(listOf(config))
assertThat(actual).contains("""* ``configName`` (default: ``['a', 'b', 'c']``)""")
}

@Test
fun `with android default`() {
val config = configTemplate.copy(
defaultValue = DefaultValue.of(99),
defaultAndroidValue = DefaultValue.of(100)
)
val actual = RuleConfigurationPrinter.print(listOf(config))
assertThat(actual).contains("""* ``configName`` (default: ``99``) (android default: ``100``)""")
}
}

@Nested
inner class DeprecatedProperties {
private val config = configTemplate.copy(deprecated = "Use something else instead")
private val actual = RuleConfigurationPrinter.print(listOf(config))

@Test
fun `contain deprecation information`() {
assertThat(actual).contains("""**Deprecated**: Use something else instead""")
}

@Test
fun `have strike through`() {
assertThat(actual).contains("""~~``configName``~~""")
}
}
}

0 comments on commit d50224e

Please sign in to comment.