diff --git a/detekt-core/src/main/resources/default-detekt-config.yml b/detekt-core/src/main/resources/default-detekt-config.yml index 462058ca590c..1d9d3e07393a 100644 --- a/detekt-core/src/main/resources/default-detekt-config.yml +++ b/detekt-core/src/main/resources/default-detekt-config.yml @@ -597,6 +597,8 @@ style: active: true MultilineLambdaItParameter: active: false + MultilineRawString: + active: false NestedClassesVisibility: active: true NewLineAtEndOfFile: diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawString.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawString.kt new file mode 100644 index 000000000000..27f8aa21a66b --- /dev/null +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawString.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 MultilineRawString(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 followd 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/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 4591807eb81f..c39bbd6396e4 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 @@ -95,6 +95,7 @@ class StyleGuideProvider : DefaultRuleSetProvider { RedundantHigherOrderMapUsage(config), UseIfEmptyOrIfBlank(config), MultilineLambdaItParameter(config), + MultilineRawString(config), UseIsNullOrEmpty(config), UseOrEmpty(config), UseAnyOrNoneInsteadOfFind(config), diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringSpec.kt new file mode 100644 index 000000000000..9e758a296c92 --- /dev/null +++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringSpec.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 MultilineRawStringSpec { + val subject = MultilineRawString(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 = "\"\"\""