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 index 322d735d7357..d31514d9cfc6 100644 --- 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 @@ -7,11 +7,20 @@ 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 io.gitlab.arturbosch.detekt.rules.isConstant import io.gitlab.arturbosch.detekt.rules.safeAs +import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtPrimaryConstructor +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPsiUtil import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.psi.psiUtil.containingClass import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType /** * All the Raw strings that have more than one line should be followed by `trimMargin()` or `trimIndent()`. @@ -48,7 +57,7 @@ class TrimMultilineRawString(val config: Config) : Rule(config) { override fun visitStringTemplateExpression(expression: KtStringTemplateExpression) { super.visitStringTemplateExpression(expression) - if (expression.isRawStringWithLineBreak() && !expression.isTrimmed()) { + if (expression.isRawStringWithLineBreak() && !expression.isTrimmed() && !expression.isExpectedAsConstant()) { report( CodeSmell( issue, @@ -76,4 +85,23 @@ fun KtStringTemplateExpression.isTrimmed(): Boolean { return nextCall in trimFunctions } +@Suppress("ReturnCount") +private fun KtStringTemplateExpression.isExpectedAsConstant(): Boolean { + val expression = KtPsiUtil.safeDeparenthesize(this) + + val property = getStrictParentOfType()?.takeIf { it.initializer == expression } + if (property != null && property.isConstant()) return true + + val argument = expression.getStrictParentOfType() + ?.takeIf { it.getArgumentExpression() == expression } + if (argument?.parent?.parent is KtAnnotationEntry) return true + + val parameter = expression.getStrictParentOfType() + ?.takeIf { it.defaultValue == expression } + val primaryConstructor = parameter?.parent?.parent?.safeAs() + if (primaryConstructor?.containingClass()?.isAnnotation() == true) return true + + return false +} + 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 index ff7477153065..f812bedff907 100644 --- 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 @@ -93,6 +93,86 @@ class TrimMultilineRawStringSpec { subject.compileAndLint(code) assertThat(subject.findings).isEmpty() } + + @Test + fun `doesn't raise on constant`() { + val code = """ + const val s = + ${TQ} + Given something + When something + Then something + ${TQ} + """.trimIndent() + subject.compileAndLint(code) + assertThat(subject.findings).isEmpty() + } + + @Test + fun `doesn't raise on annotation entry arguments`() { + val code = """ + annotation class DisplayName(val s: String) + @DisplayName( + ${TQ} + Given something + When something + Then something + ${TQ} + ) + class Foo + """.trimIndent() + subject.compileAndLint(code) + assertThat(subject.findings).isEmpty() + } + + @Test + fun `doesn't raise on annotation constructor parameters`() { + val code = """ + annotation class DisplayName( + val s: String = + ${TQ} + Given something + When something + Then something + ${TQ} + ) + """.trimIndent() + subject.compileAndLint(code) + assertThat(subject.findings).isEmpty() + } + + @Test + fun `raises on function arguments`() { + val code = """ + fun foo(s: String) {} + val bar = foo( + ${TQ} + Given something + When something + Then something + ${TQ} + ) + class Foo + """.trimIndent() + subject.compileAndLint(code) + assertThat(subject.findings).hasSize(1) + } + + @Test + fun `raises on class constructor parameters`() { + val code = """ + class Foo( + val s: String = + ${TQ} + Given something + When something + Then something + ${TQ} + ) + """.trimIndent() + subject.compileAndLint(code) + assertThat(subject.findings).hasSize(1) + } } private const val TQ = "\"\"\""