To ordinary string literal: remove 'trimIndent()' if string is single line

#KT-32616 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-08-22 19:33:08 +09:00
committed by Ilya Kirillov
parent fbe66c3496
commit d4e4a4c3e7
28 changed files with 197 additions and 15 deletions
@@ -20,16 +20,21 @@ import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.idea.inspections.collections.isCalling
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver
import org.jetbrains.kotlin.psi.psiUtil.startOffset
class ToOrdinaryStringLiteralIntention : SelfTargetingOffsetIndependentIntention<KtStringTemplateExpression>(
KtStringTemplateExpression::class.java,
"To ordinary string literal"
), LowPriorityAction {
companion object {
private val TRIM_INDENT_FUNCTIONS = listOf(FqName("kotlin.text.trimIndent"), FqName("kotlin.text.trimMargin"))
}
override fun isApplicableTo(element: KtStringTemplateExpression): Boolean {
return element.text.startsWith("\"\"\"")
}
@@ -39,24 +44,20 @@ class ToOrdinaryStringLiteralIntention : SelfTargetingOffsetIndependentIntention
val endOffset = element.endOffset
val currentOffset = editor?.caretModel?.currentCaret?.offset ?: startOffset
val entries = element.entries
val trimIndentCall = getTrimIndentCall(element, entries)
val text = buildString {
append("\"")
for (entry in element.entries) {
if (entry is KtLiteralStringTemplateEntry) {
var text = entry.text
text = text.replace("\\", "\\\\")
text = text.replace("\"", "\\\"")
text = StringUtil.convertLineSeparators(text, "\\n")
append(text)
} else {
append(entry.text)
if (trimIndentCall != null) {
append(trimIndentCall.stringTemplateText)
} else {
entries.joinTo(buffer = this, separator = "") {
if (it is KtLiteralStringTemplateEntry) it.text.escape() else it.text
}
}
append("\"")
}
val replaced = element.replaced(KtPsiFactory(element).createExpression(text))
val replaced = (trimIndentCall?.qualifiedExpression ?: element).replaced(KtPsiFactory(element).createExpression(text))
val offset = when {
currentOffset - startOffset < 2 -> startOffset
@@ -65,4 +66,42 @@ class ToOrdinaryStringLiteralIntention : SelfTargetingOffsetIndependentIntention
}
editor?.caretModel?.moveToOffset(offset)
}
private fun String.escape(): String {
var text = this
text = text.replace("\\", "\\\\")
text = text.replace("\"", "\\\"")
return StringUtil.convertLineSeparators(text, "\\n")
}
private fun getTrimIndentCall(
element: KtStringTemplateExpression,
entries: Array<KtStringTemplateEntry>
): TrimIndentCall? {
val qualifiedExpression = element.getQualifiedExpressionForReceiver()?.takeIf {
it.callExpression?.isCalling(TRIM_INDENT_FUNCTIONS) == true
} ?: return null
val marginPrefix = if (qualifiedExpression.calleeName == "trimMargin") {
when (val arg = qualifiedExpression.callExpression?.valueArguments?.singleOrNull()?.getArgumentExpression()) {
null -> "|"
is KtStringTemplateExpression -> arg.entries.singleOrNull()?.takeIf { it is KtLiteralStringTemplateEntry }?.text
else -> null
} ?: return null
} else {
null
}
val stringTemplateText = entries
.joinToString(separator = "") { it.text }
.let { if (marginPrefix != null) it.trimMargin(marginPrefix) else it.trimIndent() }
.escape()
return TrimIndentCall(qualifiedExpression, stringTemplateText)
}
private data class TrimIndentCall(
val qualifiedExpression: KtQualifiedExpression,
val stringTemplateText: String
)
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val s = <caret>"""
foo
""".trimIndent()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"foo"
@@ -0,0 +1,3 @@
// WITH_RUNTIME
val s = <caret>"""foo
""".trimIndent()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"foo"
@@ -0,0 +1,3 @@
// WITH_RUNTIME
val s = <caret>"""
foo""".trimIndent()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"foo"
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"""foo""".trimIndent()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"foo"
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"""""".trimIndent()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>""
@@ -0,0 +1,3 @@
// WITH_RUNTIME
val s = <caret>"""
""".trimIndent()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>""
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val s = """
<caret>
""".trimIndent()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = " "<caret>
@@ -0,0 +1,5 @@
// WITH_RUNTIME
val s = <caret>"""
foo
bar
""".trimIndent()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"foo\nbar"
@@ -0,0 +1,5 @@
// WITH_RUNTIME
val s = <caret>"""
\foo
\\bar
""".trimIndent()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"\\foo\n \\\\bar"
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val s = <caret>"""
|foo
""".trimMargin()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"foo"
@@ -0,0 +1,5 @@
// WITH_RUNTIME
val s = <caret>"""
_foo
_bar
""".trimMargin("_")
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"foo\nbar"
@@ -0,0 +1,5 @@
// WITH_RUNTIME
val s = <caret>"""
|\foo
| \\bar
""".trimMargin()
@@ -0,0 +1,2 @@
// WITH_RUNTIME
val s = <caret>"\\foo\n \\\\bar"
@@ -0,0 +1,6 @@
// WITH_RUNTIME
val prefix = "|"
val s = <caret>"""
|foo
|bar
""".trimMargin(prefix)
@@ -0,0 +1,3 @@
// WITH_RUNTIME
val prefix = "|"
val s = <caret>"\n |foo\n |bar\n".trimMargin(prefix)
@@ -15761,6 +15761,71 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
public void testSimple() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/simple.kt");
}
@TestMetadata("trimIndent1.kt")
public void testTrimIndent1() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimIndent1.kt");
}
@TestMetadata("trimIndent2.kt")
public void testTrimIndent2() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimIndent2.kt");
}
@TestMetadata("trimIndent3.kt")
public void testTrimIndent3() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimIndent3.kt");
}
@TestMetadata("trimIndent4.kt")
public void testTrimIndent4() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimIndent4.kt");
}
@TestMetadata("trimIndent5.kt")
public void testTrimIndent5() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimIndent5.kt");
}
@TestMetadata("trimIndent6.kt")
public void testTrimIndent6() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimIndent6.kt");
}
@TestMetadata("trimIndent7.kt")
public void testTrimIndent7() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimIndent7.kt");
}
@TestMetadata("trimIndent8.kt")
public void testTrimIndent8() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimIndent8.kt");
}
@TestMetadata("trimIndent9.kt")
public void testTrimIndent9() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimIndent9.kt");
}
@TestMetadata("trimMargin1.kt")
public void testTrimMargin1() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimMargin1.kt");
}
@TestMetadata("trimMargin2.kt")
public void testTrimMargin2() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimMargin2.kt");
}
@TestMetadata("trimMargin3.kt")
public void testTrimMargin3() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimMargin3.kt");
}
@TestMetadata("trimMargin4.kt")
public void testTrimMargin4() throws Exception {
runTest("idea/testData/intentions/toOrdinaryStringLiteral/trimMargin4.kt");
}
}
@TestMetadata("idea/testData/intentions/toRawStringLiteral")