From 5b71c3c513f408702904b79018bbaa6e8e9d2bba Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Thu, 20 Oct 2022 09:07:53 +0900 Subject: [PATCH] Fix false positive MultilineRawStringIndentation with tab indentation --- .../style/MultilineRawStringIndentation.kt | 27 +++++++++++---- .../rules/style/TrimMultilineRawString.kt | 2 +- .../MultilineRawStringIndentationSpec.kt | 34 +++++++++++++++++++ 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringIndentation.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringIndentation.kt index e51f41e07399..ca3152fa6f14 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringIndentation.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringIndentation.kt @@ -14,8 +14,11 @@ import io.gitlab.arturbosch.detekt.api.SourceLocation import io.gitlab.arturbosch.detekt.api.TextLocation import io.gitlab.arturbosch.detekt.api.config import io.gitlab.arturbosch.detekt.api.internal.Configuration +import io.gitlab.arturbosch.detekt.rules.safeAs import org.jetbrains.kotlin.com.intellij.psi.PsiFile import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry +import org.jetbrains.kotlin.psi.KtStringTemplateEntry import org.jetbrains.kotlin.psi.KtStringTemplateExpression /** @@ -63,11 +66,9 @@ class MultilineRawStringIndentation(config: Config) : Rule(config) { override fun visitStringTemplateExpression(expression: KtStringTemplateExpression) { super.visitStringTemplateExpression(expression) - val text = expression.text - val lineCount = text.lines().count() - if (lineCount <= 1) return - if (!expression.isTrimmed()) return - if (!text.matches(rawStringRegex)) { + if (!expression.isRawStringWithLineBreak() || !expression.isTrimmed()) return + + if (!expression.isSurroundedByLineBreaks()) { report( CodeSmell( issue, @@ -79,6 +80,7 @@ class MultilineRawStringIndentation(config: Config) : Rule(config) { } val lineAndColumn = getLineAndColumnInPsiFile(expression.containingFile, expression.textRange) ?: return + val lineCount = expression.text.lines().count() expression.checkIndentation( baseIndent = lineAndColumn.lineContent?.countIndent() ?: return, @@ -167,9 +169,20 @@ private fun message(desiredIntent: Int, currentIndent: Int): String { return "The indentation should be $desiredIntent but it is $currentIndent." } -private val rawStringRegex = "\"{3}\n(.*\n)? *\"{3}".toRegex(RegexOption.DOT_MATCHES_ALL) +private fun KtStringTemplateExpression.isSurroundedByLineBreaks(): Boolean { + val entries = this.entries + return entries.takeWhile { it.isBlankOrLineBreak() }.any { it.text == "\n" } && + entries.takeLastWhile { it.isBlankOrLineBreak() }.any { it.text == "\n" } +} + +private fun KtStringTemplateEntry.isBlankOrLineBreak(): Boolean { + val text = safeAs()?.text ?: return false + return text.all { it.isTabChar() } || text == "\n" +} + +private fun Char.isTabChar() = this == ' ' || this == '\t' -private fun String.countIndent() = this.takeWhile { it == ' ' }.count() +private fun String.countIndent() = this.takeWhile { it.isTabChar() }.count() private fun PsiFile.getLine(line: Int): String { return text.lineSequence().drop(line - 1).first() 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 91e984038fc3..322d735d7357 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 @@ -61,7 +61,7 @@ class TrimMultilineRawString(val config: Config) : Rule(config) { } fun KtStringTemplateExpression.isRawStringWithLineBreak(): Boolean = - text.startsWith("\"\"\"") && entries.any { + text.startsWith("\"\"\"") && text.endsWith("\"\"\"") && entries.any { val literalText = it.safeAs()?.text literalText != null && "\n" in literalText } diff --git a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringIndentationSpec.kt b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringIndentationSpec.kt index dde5e4109065..075a478fe2df 100644 --- a/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringIndentationSpec.kt +++ b/detekt-rules-style/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/MultilineRawStringIndentationSpec.kt @@ -1,6 +1,7 @@ package io.gitlab.arturbosch.detekt.rules.style import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.test.TestConfig import io.gitlab.arturbosch.detekt.test.assertThat import io.gitlab.arturbosch.detekt.test.compileAndLint import org.junit.jupiter.api.Nested @@ -310,6 +311,38 @@ class MultilineRawStringIndentationSpec { assertThat(subject.findings) .isEmpty() } + + @Test + fun `don't raise if it has no line breaks`() { + val code = """ + val a = ${TQ}Hello world!$TQ.trimIndent() + """.trimIndent() + subject.compileAndLint(code) + assertThat(subject.findings) + .isEmpty() + } + + @Test + fun `don't raise multiline raw strings if all have the correct indentation - tabs`() { + val code = """ + fun redirect(accessToken: String, refreshToken: String) { + ${TAB}respond( + $TAB${TAB}content = $TQ + $TAB$TAB$TAB{ + $TAB$TAB$TAB "access_token": "${'$'}{accessToken}", + $TAB$TAB$TAB "token_type": "fake_token_type", + $TAB$TAB$TAB "expires_in": 3600, + $TAB$TAB$TAB "refresh_token": "${'$'}{refreshToken}" + $TAB$TAB$TAB} + $TAB$TAB$TQ.trimIndent(), + $TAB${TAB}status = HttpStatusCode.FOUND + $TAB) + } + """.trimIndent() + val subject = MultilineRawStringIndentation(TestConfig(mapOf("indentSize" to 1))) + subject.compileAndLint(code) + assertThat(subject.findings).isEmpty() + } } @Nested @@ -366,3 +399,4 @@ class MultilineRawStringIndentationSpec { } private const val TQ = "\"\"\"" +private const val TAB = "\t"