From db3791c9f6a183b778c36602c52002dc93b5faff Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 16 Jan 2024 18:42:17 +0100 Subject: [PATCH 1/3] Break dependency between string-template-indent and multiline-expression-wrapping If need be, the string-template-indent rule wraps the opening quotes of a multiline raw string literal to a new line. The `string-template-indent` can now be enabled regardless whether `multiline-expression-wrapping` is enabled. Closes #2504 --- .../rules/StringTemplateIndentRule.kt | 100 +++++++++++------- .../rules/StringTemplateIndentRuleTest.kt | 87 ++++++++------- 2 files changed, 113 insertions(+), 74 deletions(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt index 687d0a920a..1fc9ac2583 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt @@ -2,10 +2,12 @@ package com.pinterest.ktlint.ruleset.standard.rules import com.pinterest.ktlint.rule.engine.core.api.ElementType.CALL_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLOSING_QUOTE +import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT import com.pinterest.ktlint.rule.engine.core.api.ElementType.LITERAL_STRING_TEMPLATE_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.OPEN_QUOTE import com.pinterest.ktlint.rule.engine.core.api.ElementType.REGULAR_STRING_PART +import com.pinterest.ktlint.rule.engine.core.api.ElementType.RETURN_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.STRING_TEMPLATE import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE import com.pinterest.ktlint.rule.engine.core.api.IndentConfig @@ -20,11 +22,14 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.firstChildLeafOrSelf +import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline +import com.pinterest.ktlint.rule.engine.core.api.lastChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.nextCodeSibling import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.nextSibling +import com.pinterest.ktlint.rule.engine.core.api.prevCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf -import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe +import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement @@ -38,7 +43,7 @@ public class StringTemplateIndentRule : visitorModifiers = setOf( // Wrap all multiline string templates to a separate line - VisitorModifier.RunAfterRule(MULTILINE_EXPRESSION_WRAPPING_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), +// VisitorModifier.RunAfterRule(MULTILINE_EXPRESSION_WRAPPING_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), // The IndentationRule first needs to fix the indentation of the opening quotes of the string template. The indentation inside // the string template is relative to the opening quotes. Running this rule before the IndentationRule results in a wrong // indentation whenever the indent level of the root of the string template is changed. @@ -51,12 +56,13 @@ public class StringTemplateIndentRule : ), ), Rule.OfficialCodeStyle { + private lateinit var indentConfig: IndentConfig private lateinit var nextIndent: String private lateinit var wrongIndentChar: String private lateinit var wrongIndentDescription: String override fun beforeFirstNode(editorConfig: EditorConfig) { - val indentConfig = + indentConfig = IndentConfig( indentStyle = editorConfig[INDENT_STYLE_PROPERTY], tabWidth = editorConfig[INDENT_SIZE_PROPERTY], @@ -83,24 +89,64 @@ public class StringTemplateIndentRule : autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, ) { - if (node.elementType == STRING_TEMPLATE) { - val psi = node.psi as KtStringTemplateExpression - if (psi.isMultiLine() && psi.isFollowedByTrimIndent()) { - if (node.containsMixedIndentationCharacters()) { - // It can not be determined with certainty how mixed indentation characters should be interpreted. The trimIndent - // function handles tabs and spaces equally (one tabs equals one space) while the user might expect that the tab size in - // the indentation is more than one space. - emit(node.startOffset, "Indentation of multiline raw string literal should not contain both tab(s) and space(s)", false) - return - } + node + .takeIf { it.elementType == STRING_TEMPLATE } + ?.let { stringTemplate -> + val psi = stringTemplate.psi as KtStringTemplateExpression + if (psi.isMultiLine() && psi.isFollowedByTrimIndent()) { + stringTemplate + .takeUnless { it.isPrecededByWhitespaceWithNewline() } + ?.takeUnless { it.isPrecededByReturnKeyword() } + ?.let { whiteSpace -> + emit(stringTemplate.startOffset, "Expected newline before multiline string template", true) + if (autoCorrect) { + whiteSpace.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(whiteSpace.treeParent)) + } + } + stringTemplate + .getFirstLeafAfterTrimIndent() + ?.takeUnless { it.isWhiteSpaceWithNewline() || it.elementType == COMMA } + ?.let { nextLeaf -> + emit(nextLeaf.startOffset, "Expected newline after multiline string template", true) + if (autoCorrect) { + nextLeaf.upsertWhitespaceBeforeMe(indentConfig.childIndentOf(stringTemplate.treeParent)) + } + } + + if (stringTemplate.containsMixedIndentationCharacters()) { + // It can not be determined with certainty how mixed indentation characters should be interpreted. The trimIndent + // function handles tabs and spaces equally (one tabs equals one space) while the user might expect that the tab size in + // the indentation is more than one space. + emit( + stringTemplate.startOffset, + "Indentation of multiline raw string literal should not contain both tab(s) and space(s)", + false, + ) + return + } - val indent = node.getIndent() -// indentWhiteSpaceBeforeStringTemplate(node, indent, emit, autoCorrect) - indentStringTemplate(node, indent, emit, autoCorrect) + val indent = stringTemplate.getIndent() + indentStringTemplate(node, indent, emit, autoCorrect) + } } - } } + private fun ASTNode.getFirstLeafAfterTrimIndent() = + takeIf { it.elementType == STRING_TEMPLATE } + ?.takeIf { (it.psi as KtStringTemplateExpression).isFollowedByTrimIndent() } + ?.treeParent + ?.lastChildLeafOrSelf() + ?.nextLeaf() + + private fun ASTNode.isPrecededByWhitespaceWithNewline() = prevLeaf().isWhiteSpaceWithNewline() + + private fun ASTNode.isPrecededByReturnKeyword() = + // Allow below as otherwise it results in compilation failure: + // return """ + // some string + // """ + prevCodeLeaf()?.elementType == RETURN_KEYWORD + private fun ASTNode.getIndent(): String { // When executing this rule, the indentation rule may not have run yet. The functionality to determine the correct indentation level // is out of scope of this rule as it is owned by the indentation rule. Therefore, the indentation of the line at which the @@ -149,26 +195,6 @@ public class StringTemplateIndentRule : .filterNot { it.isBlank() } } - private fun indentWhiteSpaceBeforeStringTemplate( - node: ASTNode, - indent: String, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean, - ) { - val prevLeaf = node.prevLeaf()!! - if (!prevLeaf.textContains('\n')) { - emit(prevLeaf.startOffset + 1, """Missing newline before raw string literal""", true) - } else if (prevLeaf.getTextAfterLastNewline() != indent) { - emit(prevLeaf.startOffset + 1, "Unexpected indent before opening quotes of raw string literal", true) - } else { - return - } - - if (autoCorrect) { - prevLeaf.upsertWhitespaceAfterMe("\n" + indent) - } - } - private fun indentStringTemplate( node: ASTNode, newIndent: String, diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt index 0517d95c15..34b5581729 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt @@ -10,6 +10,7 @@ import com.pinterest.ktlint.test.TAB import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +@Suppress("RemoveCurlyBracesFromTemplate") class StringTemplateIndentRuleTest { private val stringTemplateIndentRuleAssertThat = assertThatRuleBuilder { StringTemplateIndentRule() } @@ -36,6 +37,7 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( + LintViolation(3, 12, "Unexpected indent of raw string literal"), LintViolation(3, 12, "Unexpected indent of raw string literal"), LintViolation(4, 12, "Unexpected indent of raw string literal"), ).isFormattedAs(formattedCode) @@ -59,6 +61,7 @@ class StringTemplateIndentRuleTest { stringTemplateIndentRuleAssertThat(code) .withEditorConfigOverride(INDENT_STYLE_PROPERTY to IndentConfig.IndentStyle.TAB) .hasLintViolations( + LintViolation(1, 22, "Expected newline before multiline string template"), LintViolation(2, 5, "Unexpected indent of raw string literal"), LintViolation(3, 5, "Unexpected indent of raw string literal"), ).isFormattedAs(formattedCode) @@ -66,7 +69,6 @@ class StringTemplateIndentRuleTest { @Nested inner class `A multiline raw string literal containing both tabs and spaces in the indentation margin can not be autocorrected` { - @Suppress("RemoveCurlyBracesFromTemplate") @Test fun `Mixed indentation characters on inner lines`() { val code = @@ -76,14 +78,13 @@ class StringTemplateIndentRuleTest { ${TAB} line2 $MULTILINE_STRING_QUOTE.trimIndent() """.trimIndent() - stringTemplateIndentRuleAssertThat(code).hasLintViolationWithoutAutoCorrect( - 1, - 11, - "Indentation of multiline raw string literal should not contain both tab(s) and space(s)", - ) + stringTemplateIndentRuleAssertThat(code) + .hasLintViolations( + LintViolation(1, 11, "Expected newline before multiline string template"), + LintViolation(1, 11, "Indentation of multiline raw string literal should not contain both tab(s) and space(s)", false), + ) } - @Suppress("RemoveCurlyBracesFromTemplate", "RemoveCurlyBracesFromTemplate") @Test fun `Mixed indentation characters including line with opening quotes`() { val code = @@ -92,14 +93,13 @@ class StringTemplateIndentRuleTest { ${TAB} line2 $MULTILINE_STRING_QUOTE.trimIndent() """.trimIndent() - stringTemplateIndentRuleAssertThat(code).hasLintViolationWithoutAutoCorrect( - 1, - 11, - "Indentation of multiline raw string literal should not contain both tab(s) and space(s)", - ) + stringTemplateIndentRuleAssertThat(code) + .hasLintViolations( + LintViolation(1, 11, "Expected newline before multiline string template"), + LintViolation(1, 11, "Indentation of multiline raw string literal should not contain both tab(s) and space(s)", false), + ) } - @Suppress("RemoveCurlyBracesFromTemplate") @Test fun `Mixed indentation characters including line with closing quotes`() { val code = @@ -108,11 +108,11 @@ class StringTemplateIndentRuleTest { ${TAB}line1 ${TAB} line2$MULTILINE_STRING_QUOTE.trimIndent() """.trimIndent() - stringTemplateIndentRuleAssertThat(code).hasLintViolationWithoutAutoCorrect( - 1, - 11, - "Indentation of multiline raw string literal should not contain both tab(s) and space(s)", - ) + stringTemplateIndentRuleAssertThat(code) + .hasLintViolations( + LintViolation(1, 11, "Expected newline before multiline string template"), + LintViolation(1, 11, "Indentation of multiline raw string literal should not contain both tab(s) and space(s)", false), + ) } } @@ -156,6 +156,7 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( + LintViolation(1, 15, "Expected newline before multiline string template"), LintViolation(2, 15, "Unexpected indent of raw string literal"), LintViolation(3, 15, "Unexpected indent of raw string literal"), LintViolation(3, 20, "Missing newline before the closing quotes of the raw string literal"), @@ -179,6 +180,7 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( + LintViolation(1, 15, "Expected newline before multiline string template"), LintViolation(2, 15, "Unexpected indent of raw string literal"), LintViolation(3, 15, "Unexpected indent of raw string literal"), ).isFormattedAs(formattedCode) @@ -206,8 +208,10 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( + LintViolation(2, 13, "Expected newline before multiline string template"), LintViolation(3, 13, "Unexpected indent of raw string literal"), LintViolation(4, 13, "Unexpected indent of raw string literal"), + LintViolation(4, 29, "Expected newline after multiline string template"), ).isFormattedAs(formattedCode) } @@ -238,6 +242,7 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( + LintViolation(2, 13, "Expected newline before multiline string template"), LintViolation(3, 1, "Unexpected indent of raw string literal"), LintViolation(5, 1, "Unexpected indent of raw string literal"), LintViolation(6, 1, "Unexpected indent of raw string literal"), @@ -263,13 +268,13 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( + LintViolation(1, 11, "Expected newline before multiline string template"), LintViolation(2, 1, "Unexpected 'tab' character(s) in margin of multiline string"), LintViolation(3, 1, "Unexpected 'tab' character(s) in margin of multiline string"), LintViolation(4, 1, "Unexpected indent of raw string literal"), ).isFormattedAs(formattedCode) } - @Suppress("RemoveCurlyBracesFromTemplate") @Test fun `Format multiline string without tabs in the indentation margin`() { val code = @@ -291,6 +296,7 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( + LintViolation(1, 11, "Expected newline before multiline string template"), LintViolation(2, 7, "Unexpected indent of raw string literal"), LintViolation(3, 7, "Unexpected indent of raw string literal"), LintViolation(4, 7, "Unexpected indent of raw string literal"), @@ -319,6 +325,7 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( + LintViolation(2, 15, "Expected newline before multiline string template"), LintViolation(3, 15, "Unexpected indent of raw string literal"), LintViolation(4, 15, "Unexpected indent of raw string literal"), ).isFormattedAs(formattedCode) @@ -328,10 +335,11 @@ class StringTemplateIndentRuleTest { fun `lint property delegate is indented properly 4`() { val code = """ - fun lazyString() = lazy { $MULTILINE_STRING_QUOTE - someText - $MULTILINE_STRING_QUOTE.trimIndent() - } + fun lazyString() = + lazy { $MULTILINE_STRING_QUOTE + someText + $MULTILINE_STRING_QUOTE.trimIndent() + } """.trimIndent() val formattedCode = """ @@ -344,8 +352,9 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( - LintViolation(2, 27, "Unexpected indent of raw string literal"), - LintViolation(3, 27, "Unexpected indent of raw string literal"), + LintViolation(2, 12, "Expected newline before multiline string template"), + LintViolation(3, 12, "Unexpected indent of raw string literal"), + LintViolation(4, 12, "Unexpected indent of raw string literal"), ).isFormattedAs(formattedCode) } @@ -372,6 +381,7 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( + LintViolation(2, 26, "Expected newline before multiline string template"), LintViolation(3, 26, "Unexpected indent of raw string literal"), LintViolation(4, 26, "Unexpected indent of raw string literal"), ).isFormattedAs(formattedCode) @@ -381,14 +391,15 @@ class StringTemplateIndentRuleTest { fun `lint parameter with multiline string raw string literal after arrow`() { val code = """ - val result = when { - someBooleanFunction() -> $MULTILINE_STRING_QUOTE - someText - $MULTILINE_STRING_QUOTE.trimIndent() - else -> $MULTILINE_STRING_QUOTE - someOtherText - $MULTILINE_STRING_QUOTE.trimIndent() - } + val result = + when { + someBooleanFunction() -> $MULTILINE_STRING_QUOTE + someText + $MULTILINE_STRING_QUOTE.trimIndent() + else -> $MULTILINE_STRING_QUOTE + someOtherText + $MULTILINE_STRING_QUOTE.trimIndent() + } """.trimIndent() val formattedCode = """ @@ -406,10 +417,12 @@ class StringTemplateIndentRuleTest { """.trimIndent() stringTemplateIndentRuleAssertThat(code) .hasLintViolations( - LintViolation(3, 30, "Unexpected indent of raw string literal"), - LintViolation(4, 30, "Unexpected indent of raw string literal"), - LintViolation(6, 13, "Unexpected indent of raw string literal"), - LintViolation(7, 13, "Unexpected indent of raw string literal"), + LintViolation(3, 34, "Expected newline before multiline string template"), + LintViolation(4, 34, "Unexpected indent of raw string literal"), + LintViolation(5, 34, "Unexpected indent of raw string literal"), + LintViolation(6, 17, "Expected newline before multiline string template"), + LintViolation(7, 17, "Unexpected indent of raw string literal"), + LintViolation(8, 17, "Unexpected indent of raw string literal"), ).isFormattedAs(formattedCode) } } From 3574c16d976bf4313bea769cd8d4697352a2e507 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 16 Jan 2024 18:42:28 +0100 Subject: [PATCH 2/3] Fix typo --- .../com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt index 18dd656fd7..36e84c3886 100644 --- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt +++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/KtlintCommandLine.kt @@ -518,7 +518,7 @@ internal class KtlintCommandLine { } catch (e: Exception) { if (code.isStdIn && e is KtLintParseException) { if (code.script) { - // When reading from stdin, code is only parsed as Kotlint script, if it could not be parsed as pure Kotlin. Now parsing + // When reading from stdin, code is only parsed as Kotlin script, if it could not be parsed as pure Kotlin. Now parsing // of the code has failed for both, the file has to be ignored. logger.error { """ From 4b7e8869fc155cf075f2818edcc766135a0f20b2 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Tue, 16 Jan 2024 19:01:36 +0100 Subject: [PATCH 3/3] Fix complex string template with multiple chains --- .../rules/StringTemplateIndentRule.kt | 5 ++++- .../rules/StringTemplateIndentRuleTest.kt | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt index 1fc9ac2583..f07c4923dd 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRule.kt @@ -4,6 +4,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.CALL_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLOSING_QUOTE import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT_QUALIFIED_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.LITERAL_STRING_TEMPLATE_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.OPEN_QUOTE import com.pinterest.ktlint.rule.engine.core.api.ElementType.REGULAR_STRING_PART @@ -105,7 +106,9 @@ public class StringTemplateIndentRule : } stringTemplate .getFirstLeafAfterTrimIndent() - ?.takeUnless { it.isWhiteSpaceWithNewline() || it.elementType == COMMA } + ?.takeUnless { it.isWhiteSpaceWithNewline() } + ?.takeUnless { it.elementType == COMMA } + ?.takeUnless { it.treeParent.elementType == DOT_QUALIFIED_EXPRESSION } ?.let { nextLeaf -> emit(nextLeaf.startOffset, "Expected newline after multiline string template", true) if (autoCorrect) { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt index 34b5581729..1635e9f061 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt @@ -425,4 +425,24 @@ class StringTemplateIndentRuleTest { LintViolation(8, 17, "Unexpected indent of raw string literal"), ).isFormattedAs(formattedCode) } + + @Test + fun `Given a multiline raw string literal and trimIndent is followed by another dot qualified expression`() { + val code = + """ + val foo = $MULTILINE_STRING_QUOTE + someText + $MULTILINE_STRING_QUOTE.trimIndent().lowercase() + """.trimIndent() + val formattedCode = + """ + val foo = + $MULTILINE_STRING_QUOTE + someText + $MULTILINE_STRING_QUOTE.trimIndent().lowercase() + """.trimIndent() + stringTemplateIndentRuleAssertThat(code) + .hasLintViolation(1, 11, "Expected newline before multiline string template") + .isFormattedAs(formattedCode) + } }