diff --git a/detekt-core/src/main/resources/default-detekt-config.yml b/detekt-core/src/main/resources/default-detekt-config.yml index 462058ca590..e1bff89c1fc 100644 --- a/detekt-core/src/main/resources/default-detekt-config.yml +++ b/detekt-core/src/main/resources/default-detekt-config.yml @@ -642,6 +642,8 @@ style: excludeGuardClauses: false TrailingWhitespace: active: false + TrimMultilineRawString: + active: false UnderscoresInNumericLiterals: active: false acceptableLength: 4 diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/StyleGuideProvider.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/StyleGuideProvider.kt index 4591807eb81..834e95b2f7d 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/StyleGuideProvider.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/StyleGuideProvider.kt @@ -30,6 +30,7 @@ class StyleGuideProvider : DefaultRuleSetProvider { DestructuringDeclarationWithTooManyEntries(config), ReturnCount(config), ThrowsCount(config), + TrimMultilineRawString(config), NewLineAtEndOfFile(config), WildcardImport(config), FileParsingRule(config), diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/TrimMultilineRawString.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/TrimMultilineRawString.kt new file mode 100644 index 00000000000..d2d215ee919 --- /dev/null +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/TrimMultilineRawString.kt @@ -0,0 +1,74 @@ +package io.gitlab.arturbosch.detekt.rules.style + +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.Debt +import io.gitlab.arturbosch.detekt.api.Entity +import io.gitlab.arturbosch.detekt.api.Issue +import io.gitlab.arturbosch.detekt.api.Rule +import io.gitlab.arturbosch.detekt.api.Severity +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis + +/** + * All the Raw strings that have more than one line should be followed by `trimMargin()` or `trimIndent()`. + * + * + * """ + * Hello World! + * How are you? + * """ + * + * + * + * """ + * | Hello World! + * | How are you? + * """.trimMargin() + * + * """ + * Hello World! + * How are you? + * """.trimIndent() + * + * """Hello World! How are you?""" + * + */ +class TrimMultilineRawString(val config: Config) : Rule(config) { + override val issue = Issue( + javaClass.simpleName, + Severity.Style, + "Multiline raw strings should be followed by `trimMargin()` or `trimIndent()`.", + Debt.FIVE_MINS + ) + + override fun visitStringTemplateExpression(expression: KtStringTemplateExpression) { + super.visitStringTemplateExpression(expression) + + if (expression.text.lines().count() <= 1) return + + val nextCall = expression.getQualifiedExpressionForSelectorOrThis() + .getQualifiedExpressionForReceiver() + ?.selectorExpression + ?.asKtCallExpression() + ?.calleeExpression + ?.text + + if (nextCall !in trimFunctions) { + report( + CodeSmell( + issue, + Entity.from(expression), + "Multiline raw strings should be followed by `trimMargin()` or `trimIndent()`", + ) + ) + } + } +} + +private fun KtExpression.asKtCallExpression(): KtCallExpression? = this as? KtCallExpression + +private val trimFunctions = listOf("trimIndent", "trimMargin") diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/TrimMultilineRawStringSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/TrimMultilineRawStringSpec.kt new file mode 100644 index 00000000000..d09235756ac --- /dev/null +++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/TrimMultilineRawStringSpec.kt @@ -0,0 +1,87 @@ +@file:Suppress("StringTemplate") + +package io.gitlab.arturbosch.detekt.rules.style + +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.test.compileAndLint +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class TrimMultilineRawStringSpec { + val subject = TrimMultilineRawString(Config.empty) + + @Test + fun `raises multiline raw strings without trim`() { + val code = """ + val a = ${TQ} + Hello world! + ${TQ} + """ + subject.compileAndLint(code) + assertThat(subject.findings).hasSize(1) + } + + @Test + fun `raises multiline raw strings with lenght`() { + val code = """ + val a = ${TQ} + Hello world! + ${TQ}.length + """ + subject.compileAndLint(code) + assertThat(subject.findings).hasSize(1) + } + + @Test + fun `doesn't raise multiline raw strings without trimIndent`() { + val code = """ + val a = ${TQ} + Hello world! + ${TQ}.trimIndent() + """ + subject.compileAndLint(code) + assertThat(subject.findings).isEmpty() + } + + @Test + fun `doesn't raise multiline raw strings without trimMargin`() { + val code = """ + val a = ${TQ} + |Hello world! + ${TQ}.trimMargin() + """ + subject.compileAndLint(code) + assertThat(subject.findings).isEmpty() + } + + @Test + fun `doesn't raise multiline raw strings without trimMargin with parameter`() { + val code = """ + val a = ${TQ} + >Hello world! + ${TQ}.trimMargin(">") + """ + subject.compileAndLint(code) + assertThat(subject.findings).isEmpty() + } + + @Test + fun `don't raise one line raw strings`() { + val code = """ + val a = ${TQ}Hello world!${TQ} + """ + subject.compileAndLint(code) + assertThat(subject.findings).isEmpty() + } + + @Test + fun `doesn't raise if it is not a raw string`() { + val code = """ + val a = "Hello world!" + """ + subject.compileAndLint(code) + assertThat(subject.findings).isEmpty() + } +} + +private const val TQ = "\"\"\""