Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TrimMultilineRawString: fix false positive when it's expected as constant #5480

Merged
merged 1 commit into from Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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()`.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<KtProperty>()?.takeIf { it.initializer == expression }
if (property != null && property.isConstant()) return true

val argument = expression.getStrictParentOfType<KtValueArgument>()
?.takeIf { it.getArgumentExpression() == expression }
if (argument?.parent?.parent is KtAnnotationEntry) return true

val parameter = expression.getStrictParentOfType<KtParameter>()
?.takeIf { it.defaultValue == expression }
val primaryConstructor = parameter?.parent?.parent?.safeAs<KtPrimaryConstructor>()
if (primaryConstructor?.containingClass()?.isAnnotation() == true) return true

return false
}

private val trimFunctions = listOf("trimIndent", "trimMargin")
Expand Up @@ -93,6 +93,88 @@ class TrimMultilineRawStringSpec {
subject.compileAndLint(code)
assertThat(subject.findings).isEmpty()
}

@Test
fun `doesn't raise on constant`() {
val code = """
object O {
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 = "\"\"\""