diff --git a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties index 755a6e525233..f510760c89fb 100644 --- a/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties +++ b/plugins/kotlin/base/resources/resources-en/messages/KotlinBundle.properties @@ -1452,6 +1452,7 @@ redundant.getter=Redundant getter remove.redundant.if.text=Remove redundant 'if' statement redundant.if.statement=Redundant 'if' statement redundant.if.statement.analyzing.type=Analyzing condition type... +convert.to.concatenated.string.statement.analyzing.entry.type=Analyzing entry type... delete.fix.family.name=Remove arrow redundant.lambda.arrow=Redundant lambda arrow remove.let.call=Remove 'let' call diff --git a/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/ConvertToConcatenatedStringIntentionBase.kt b/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/ConvertToConcatenatedStringIntentionBase.kt new file mode 100644 index 000000000000..0872b6f65580 --- /dev/null +++ b/plugins/kotlin/code-insight/impl-base/src/org/jetbrains/kotlin/idea/codeinsights/impl/base/ConvertToConcatenatedStringIntentionBase.kt @@ -0,0 +1,128 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.idea.codeinsights.impl.base + +import com.intellij.codeInsight.intention.LowPriorityAction +import com.intellij.openapi.actionSystem.ex.ActionUtil +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.idea.base.resources.KotlinBundle +import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingOffsetIndependentIntention +import org.jetbrains.kotlin.idea.util.application.runWriteActionIfPhysical +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* + +abstract class ConvertToConcatenatedStringIntentionBase : SelfTargetingOffsetIndependentIntention( + KtStringTemplateExpression::class.java, + KotlinBundle.lazyMessage("convert.template.to.concatenated.string") +), LowPriorityAction { + override fun isApplicableTo(element: KtStringTemplateExpression): Boolean { + if (element.lastChild.node.elementType != KtTokens.CLOSING_QUOTE) return false // not available for unclosed literal + return element.entries.any { it is KtStringTemplateEntryWithExpression } + } + + override fun startInWriteAction(): Boolean = false + + override fun getElementToMakeWritable(currentFile: PsiFile) = currentFile + + override fun applyTo(element: KtStringTemplateExpression, editor: Editor?) { + val tripleQuoted = isTripleQuoted(element.text ?: error("Failed to get template expression's text")) + val quote = if (tripleQuoted) "\"\"\"" else "\"" + val entries = element.entries.filterNot { it is KtStringTemplateEntryWithExpression && it.expression == null } + + val convertFirstEntryExplicitly = (entries.firstOrNull() as? KtStringTemplateEntryWithExpression)?.expression?.let { + !checkIfExpressionIsStringFromModalView(it) + } ?: false + + val entryTexts = entries.mapIndexed { index, entry -> + val entryText = entry.toSeparateString(quote, convertExplicitly = (index == 0) && convertFirstEntryExplicitly) + val entryIsString = entryText.startsWith(quote) && entryText.endsWith(quote) && entry.isStringLiteral() + entryText to entryIsString + } + + // merge all consecutive string literals + val targetTexts = entryTexts.foldIndexed(mutableListOf()) { index, texts, (currText, currIsString) -> + val prevIsString = entryTexts.getOrNull(index - 1)?.second ?: false + val nextIsString = entryTexts.getOrNull(index + 1)?.second ?: false + var textToBeMerged = currText + if (currIsString && nextIsString) textToBeMerged = textToBeMerged.removeSuffix(quote) + if (currIsString && prevIsString) { + textToBeMerged = textToBeMerged.removePrefix(quote) + texts[texts.lastIndex] += textToBeMerged + } else { + texts.add(textToBeMerged) + } + texts + } + + val text = targetTexts.joinToString("+") + val replacement = KtPsiFactory(element).createExpression(text).safeDeparenthesizeOperands() + runWriteActionIfPhysical(element) { + element.replace(replacement) + } + } + + private fun KtExpression.safeDeparenthesizeOperands(): KtExpression { + if (this is KtBinaryExpression) { + val deparenthesizedLeft = this.left!!.safeDeparenthesizeOperands() + val deparenthesizedRight = this.right!!.safeDeparenthesizeOperands() + return KtPsiFactory(this.project).createExpressionByPattern( + "$0+$1", + deparenthesizedLeft.text, + deparenthesizedRight.text + ) + } + if (this is KtParenthesizedExpression && KtPsiUtil.areParenthesesUseless(this)) { + return KtPsiUtil.safeDeparenthesize(this, true) + } + return this + } + + private fun KtStringTemplateEntry.isStringLiteral(): Boolean = expression == null || expression is KtStringTemplateExpression + + private fun isTripleQuoted(str: String): Boolean = str.startsWith("\"\"\"") && str.endsWith("\"\"\"") + + private fun KtStringTemplateEntry.toSeparateString(quote: String, convertExplicitly: Boolean): String { + if (this !is KtStringTemplateEntryWithExpression) return text.quote(quote) + + val expression = expression!! // checked before + + val text = if (needsParenthesis(expression)) + "(${expression.text})" + else + expression.text + + return if (convertExplicitly) + "$text.toString()" + else + text + } + + private fun needsParenthesis(expression: KtExpression): Boolean = when (expression) { + is KtPostfixExpression -> false + is KtAnnotatedExpression, + is KtLabeledExpression, + is KtOperationExpression -> true + + is KtIfExpression -> expression.`else` !is KtBlockExpression + else -> false + } + + private fun String.quote(quote: String): String = quote + this + quote + + private fun checkIfExpressionIsStringFromModalView(expression: KtExpression): Boolean { + return ActionUtil.underModalProgress( + expression.project, + KotlinBundle.message("convert.to.concatenated.string.statement.analyzing.entry.type") + ) { + isExpressionOfStringType(expression) + } + } + + /** + * Tells whether given [expression] is of the string type. + * + * Called from cancellable modal progress and under read action, so it's safe to use resolve here. + */ + abstract fun isExpressionOfStringType(expression: KtExpression): Boolean +} diff --git a/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions-k2.xml b/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions-k2.xml index 6d5c21cddf46..db1b28571fba 100644 --- a/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions-k2.xml +++ b/plugins/kotlin/code-insight/intentions-k2/resources/kotlin.code-insight.intentions-k2.xml @@ -83,5 +83,12 @@ messages.KotlinBundle group.names.kotlin + + + kotlin + org.jetbrains.kotlin.idea.k2.codeinsight.intentions.ConvertToConcatenatedStringIntention + messages.KotlinBundle + group.names.kotlin + \ No newline at end of file diff --git a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertToConcatenatedStringIntention.kt b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertToConcatenatedStringIntention.kt new file mode 100644 index 000000000000..bc5d4ffd73a2 --- /dev/null +++ b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/ConvertToConcatenatedStringIntention.kt @@ -0,0 +1,13 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.idea.k2.codeinsight.intentions + +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.idea.codeinsights.impl.base.ConvertToConcatenatedStringIntentionBase +import org.jetbrains.kotlin.psi.KtExpression + +class ConvertToConcatenatedStringIntention : ConvertToConcatenatedStringIntentionBase() { + override fun isExpressionOfStringType(expression: KtExpression): Boolean = analyze(expression) { + expression.getKtType()?.isString == true + } +} \ No newline at end of file diff --git a/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java b/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java index b37accd74ea1..a3c5f419879f 100644 --- a/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java +++ b/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java @@ -1150,6 +1150,199 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest { } } + @RunWith(JUnit3RunnerWithInners.class) + @TestMetadata("../../../idea/tests/testData/intentions/convertToConcatenatedString") + public static class ConvertToConcatenatedString extends AbstractK2IntentionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("embeddedIf.kt") + public void testEmbeddedIf() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/embeddedIf.kt"); + } + + @TestMetadata("embeddedIfBraces.kt") + public void testEmbeddedIfBraces() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/embeddedIfBraces.kt"); + } + + @TestMetadata("embeddedIfSingle.kt") + public void testEmbeddedIfSingle() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/embeddedIfSingle.kt"); + } + + @TestMetadata("emptyBraces.kt") + public void testEmptyBraces() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/emptyBraces.kt"); + } + + @TestMetadata("handlesEscapeString.kt") + public void testHandlesEscapeString() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/handlesEscapeString.kt"); + } + + @TestMetadata("hasEmbeddedConcatenation.kt") + public void testHasEmbeddedConcatenation() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/hasEmbeddedConcatenation.kt"); + } + + @TestMetadata("hasEmbeddedHigherPrecedence.kt") + public void testHasEmbeddedHigherPrecedence() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/hasEmbeddedHigherPrecedence.kt"); + } + + @TestMetadata("hasEmbeddedLowerPrecedence.kt") + public void testHasEmbeddedLowerPrecedence() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/hasEmbeddedLowerPrecedence.kt"); + } + + @TestMetadata("hasEmptyTemplateEntry.kt") + public void testHasEmptyTemplateEntry() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/hasEmptyTemplateEntry.kt"); + } + + @TestMetadata("hasParenthesesInTemplateEntry.kt") + public void testHasParenthesesInTemplateEntry() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/hasParenthesesInTemplateEntry.kt"); + } + + @TestMetadata("hasPlusSign.kt") + public void testHasPlusSign() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/hasPlusSign.kt"); + } + + @TestMetadata("hasPlusSignInRawString.kt") + public void testHasPlusSignInRawString() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/hasPlusSignInRawString.kt"); + } + + @TestMetadata("interpolate2StringConstants.kt") + public void testInterpolate2StringConstants() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/interpolate2StringConstants.kt"); + } + + @TestMetadata("interpolate3.kt") + public void testInterpolate3() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/interpolate3.kt"); + } + + @TestMetadata("interpolateChar.kt") + public void testInterpolateChar() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/interpolateChar.kt"); + } + + @TestMetadata("interpolateConstants.kt") + public void testInterpolateConstants() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/interpolateConstants.kt"); + } + + @TestMetadata("interpolateMapAccess.kt") + public void testInterpolateMapAccess() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/interpolateMapAccess.kt"); + } + + @TestMetadata("interpolateMethodInvoke.kt") + public void testInterpolateMethodInvoke() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/interpolateMethodInvoke.kt"); + } + + @TestMetadata("interpolateSimpleWithBraces.kt") + public void testInterpolateSimpleWithBraces() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/interpolateSimpleWithBraces.kt"); + } + + @TestMetadata("interpolateStringWithFloat.kt") + public void testInterpolateStringWithFloat() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/interpolateStringWithFloat.kt"); + } + + @TestMetadata("interpolateStringWithInt.kt") + public void testInterpolateStringWithInt() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/interpolateStringWithInt.kt"); + } + + @TestMetadata("lastExprIsNamedExpression.kt") + public void testLastExprIsNamedExpression() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/lastExprIsNamedExpression.kt"); + } + + @TestMetadata("multilineString.kt") + public void testMultilineString() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/multilineString.kt"); + } + + @TestMetadata("multilineWhenExpr.kt") + public void testMultilineWhenExpr() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/multilineWhenExpr.kt"); + } + + @TestMetadata("namedExprBetweenConstants.kt") + public void testNamedExprBetweenConstants() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/namedExprBetweenConstants.kt"); + } + + @TestMetadata("notApplicableForErrorElement.kt") + public void testNotApplicableForErrorElement() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/notApplicableForErrorElement.kt"); + } + + @TestMetadata("notApplicableForSimple.kt") + public void testNotApplicableForSimple() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/notApplicableForSimple.kt"); + } + + @TestMetadata("notAvailableForDollarSignLiteral.kt") + public void testNotAvailableForDollarSignLiteral() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/notAvailableForDollarSignLiteral.kt"); + } + + @TestMetadata("singleVar.kt") + public void testSingleVar() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/singleVar.kt"); + } + + @TestMetadata("startsWithAnnotatedExpression.kt") + public void testStartsWithAnnotatedExpression() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/startsWithAnnotatedExpression.kt"); + } + + @TestMetadata("startsWithEmbeddedConcatenation.kt") + public void testStartsWithEmbeddedConcatenation() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/startsWithEmbeddedConcatenation.kt"); + } + + @TestMetadata("startsWithInt.kt") + public void testStartsWithInt() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/startsWithInt.kt"); + } + + @TestMetadata("startsWithPostfixExpression.kt") + public void testStartsWithPostfixExpression() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/startsWithPostfixExpression.kt"); + } + + @TestMetadata("startsWithStringExpression.kt") + public void testStartsWithStringExpression() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/startsWithStringExpression.kt"); + } + + @TestMetadata("stringTemplateInCurryBraces.kt") + public void testStringTemplateInCurryBraces() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/stringTemplateInCurryBraces.kt"); + } + + @TestMetadata("tricky.kt") + public void testTricky() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/tricky.kt"); + } + + @TestMetadata("withAndWithoutBraces.kt") + public void testWithAndWithoutBraces() throws Exception { + runTest("../../../idea/tests/testData/intentions/convertToConcatenatedString/withAndWithoutBraces.kt"); + } + } + @RunWith(JUnit3RunnerWithInners.class) @TestMetadata("testData/intentions") public abstract static class Intentions extends AbstractK2IntentionTest { diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToConcatenatedStringIntention.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToConcatenatedStringIntention.kt index 9a61f5d27cae..513ecc611d8a 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToConcatenatedStringIntention.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToConcatenatedStringIntention.kt @@ -2,101 +2,11 @@ package org.jetbrains.kotlin.idea.intentions -import com.intellij.codeInsight.intention.LowPriorityAction -import com.intellij.openapi.editor.Editor import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.idea.base.resources.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingOffsetIndependentIntention -import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.idea.codeinsights.impl.base.ConvertToConcatenatedStringIntentionBase +import org.jetbrains.kotlin.psi.KtExpression -class ConvertToConcatenatedStringIntention : SelfTargetingOffsetIndependentIntention( - KtStringTemplateExpression::class.java, - KotlinBundle.lazyMessage("convert.template.to.concatenated.string") -), LowPriorityAction { - override fun isApplicableTo(element: KtStringTemplateExpression): Boolean { - if (element.lastChild.node.elementType != KtTokens.CLOSING_QUOTE) return false // not available for unclosed literal - return element.entries.any { it is KtStringTemplateEntryWithExpression } - } - - override fun applyTo(element: KtStringTemplateExpression, editor: Editor?) { - val tripleQuoted = isTripleQuoted(element.text!!) - val quote = if (tripleQuoted) "\"\"\"" else "\"" - val entries = element.entries - - val targetEntries = entries - .filterNot { it is KtStringTemplateEntryWithExpression && it.expression == null } - .mapIndexed { index, entry -> entry to entry.toSeparateString(quote, isFirstEntry = (index == 0)) } - - var numberOfOperands = 1 - val text = buildString { - targetEntries.forEachIndexed { index, (entry, entryText) -> - var toBeAppended = entryText - val prevEntryText = targetEntries.getOrNull(index - 1)?.second - if (entryText.startsWith(quote) && prevEntryText?.endsWith(quote) == true && entry.isStringLiteral()) { - toBeAppended = toBeAppended.removePrefix(quote) - } else if (prevEntryText != null) { - append("+") - numberOfOperands++ - } - val (nextEntry, nextEntryText) = targetEntries.getOrNull(index + 1) ?: (null to null) - if (entryText.endsWith(quote) && nextEntryText?.startsWith(quote) == true && nextEntry?.isStringLiteral() == true) { - toBeAppended = toBeAppended.removeSuffix(quote) - } - append(toBeAppended) - } - } - - val replacement = KtPsiFactory(element).createExpression(text).safeDeparenthesizeOperands(numberOfOperands) - element.replace(replacement) - } - - private fun KtExpression.safeDeparenthesizeOperands(numberOfOperands: Int): KtExpression { - if (numberOfOperands > 1 && this is KtBinaryExpression) { - val deparenthesizedLeft = this.left!!.safeDeparenthesizeOperands(numberOfOperands - 1) - val deparenthesizedRight = this.right!!.safeDeparenthesizeOperands(1) - return KtPsiFactory(this.project).createExpressionByPattern( - "$0+$1", - deparenthesizedLeft.text, - deparenthesizedRight.text - ) - } else { - if (this is KtParenthesizedExpression && KtPsiUtil.areParenthesesUseless(this)) { - return this.expression ?: this - } else { - return this - } - } - } - - private fun KtStringTemplateEntry.isStringLiteral() = expression == null || expression is KtStringTemplateExpression - - private fun isTripleQuoted(str: String) = str.startsWith("\"\"\"") && str.endsWith("\"\"\"") - - private fun KtStringTemplateEntry.toSeparateString(quote: String, isFirstEntry: Boolean): String { - if (this !is KtStringTemplateEntryWithExpression) return text.quote(quote) - - val expression = expression!! // checked before - - val text = if (needsParenthesis(expression)) - "(" + expression.text + ")" - else - expression.text - - return if (isFirstEntry && !expression.isStringExpression()) - "$text.toString()" - else - text - } - - private fun needsParenthesis(expression: KtExpression): Boolean = when (expression) { - is KtOperationExpression -> true - is KtIfExpression -> expression.`else` !is KtBlockExpression - else -> false - } - - private fun String.quote(quote: String) = quote + this + quote - - private fun KtExpression.isStringExpression() = KotlinBuiltIns.isString(analyze().getType(this)) -} +class ConvertToConcatenatedStringIntention : ConvertToConcatenatedStringIntentionBase() { + override fun isExpressionOfStringType(expression: KtExpression): Boolean = KotlinBuiltIns.isString(expression.analyze().getType(expression)) +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index dd6b0f999189..6f5c23c94d05 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -8212,6 +8212,16 @@ public abstract class IntentionTestGenerated extends AbstractIntentionTest { runTest("testData/intentions/convertToConcatenatedString/hasEmbeddedLowerPrecedence.kt"); } + @TestMetadata("hasEmptyTemplateEntry.kt") + public void testHasEmptyTemplateEntry() throws Exception { + runTest("testData/intentions/convertToConcatenatedString/hasEmptyTemplateEntry.kt"); + } + + @TestMetadata("hasParenthesesInTemplateEntry.kt") + public void testHasParenthesesInTemplateEntry() throws Exception { + runTest("testData/intentions/convertToConcatenatedString/hasParenthesesInTemplateEntry.kt"); + } + @TestMetadata("hasPlusSign.kt") public void testHasPlusSign() throws Exception { runTest("testData/intentions/convertToConcatenatedString/hasPlusSign.kt"); @@ -8307,6 +8317,11 @@ public abstract class IntentionTestGenerated extends AbstractIntentionTest { runTest("testData/intentions/convertToConcatenatedString/singleVar.kt"); } + @TestMetadata("startsWithAnnotatedExpression.kt") + public void testStartsWithAnnotatedExpression() throws Exception { + runTest("testData/intentions/convertToConcatenatedString/startsWithAnnotatedExpression.kt"); + } + @TestMetadata("startsWithEmbeddedConcatenation.kt") public void testStartsWithEmbeddedConcatenation() throws Exception { runTest("testData/intentions/convertToConcatenatedString/startsWithEmbeddedConcatenation.kt"); @@ -8317,6 +8332,11 @@ public abstract class IntentionTestGenerated extends AbstractIntentionTest { runTest("testData/intentions/convertToConcatenatedString/startsWithInt.kt"); } + @TestMetadata("startsWithPostfixExpression.kt") + public void testStartsWithPostfixExpression() throws Exception { + runTest("testData/intentions/convertToConcatenatedString/startsWithPostfixExpression.kt"); + } + @TestMetadata("startsWithStringExpression.kt") public void testStartsWithStringExpression() throws Exception { runTest("testData/intentions/convertToConcatenatedString/startsWithStringExpression.kt"); diff --git a/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/.k2Intention b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/.k2Intention new file mode 100644 index 000000000000..8847936bdc98 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/.k2Intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.k2.codeinsight.intentions.ConvertToConcatenatedStringIntention diff --git a/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasEmptyTemplateEntry.kt b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasEmptyTemplateEntry.kt new file mode 100644 index 000000000000..345d5f88e70c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasEmptyTemplateEntry.kt @@ -0,0 +1,3 @@ +fun test(): String { + return "${}${1 + 1}abc" +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasEmptyTemplateEntry.kt.after b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasEmptyTemplateEntry.kt.after new file mode 100644 index 000000000000..c86955acf9a6 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasEmptyTemplateEntry.kt.after @@ -0,0 +1,3 @@ +fun test(): String { + return (1 + 1).toString() + "abc" +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasParenthesesInTemplateEntry.kt b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasParenthesesInTemplateEntry.kt new file mode 100644 index 000000000000..c1bb404f8e47 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasParenthesesInTemplateEntry.kt @@ -0,0 +1,3 @@ +fun test(): String { + return "${("a" + "b") + "c"}" +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasParenthesesInTemplateEntry.kt.after b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasParenthesesInTemplateEntry.kt.after new file mode 100644 index 000000000000..f35ad84b9398 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/hasParenthesesInTemplateEntry.kt.after @@ -0,0 +1,3 @@ +fun test(): String { + return ("a" + "b") + "c" +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithAnnotatedExpression.kt b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithAnnotatedExpression.kt new file mode 100644 index 000000000000..80c1b3a8fdd4 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithAnnotatedExpression.kt @@ -0,0 +1,7 @@ +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.EXPRESSION) +annotation class Foo + +fun test(): String { + return "${@Foo 1}a" +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithAnnotatedExpression.kt.after b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithAnnotatedExpression.kt.after new file mode 100644 index 000000000000..b34844e4fc3c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithAnnotatedExpression.kt.after @@ -0,0 +1,7 @@ +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.EXPRESSION) +annotation class Foo + +fun test(): String { + return (@Foo 1).toString() + "a" +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithPostfixExpression.kt b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithPostfixExpression.kt new file mode 100644 index 000000000000..b21c3dbb4399 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithPostfixExpression.kt @@ -0,0 +1,4 @@ +fun test(): String { + var l = 0 + return "${l++}a$l" +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithPostfixExpression.kt.after b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithPostfixExpression.kt.after new file mode 100644 index 000000000000..7ff638abddd7 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/convertToConcatenatedString/startsWithPostfixExpression.kt.after @@ -0,0 +1,4 @@ +fun test(): String { + var l = 0 + return l++.toString() + "a" + l +} \ No newline at end of file diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt index 2775779893e0..a22249a785fc 100644 --- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt +++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2IntentionTests.kt @@ -21,6 +21,7 @@ internal fun MutableTWorkspace.generateK2IntentionTests() { model("${idea}intentions/importMember", pattern = pattern) model("${idea}intentions/convertToBlockBody", pattern = pattern) model("${idea}intentions/addWhenRemainingBranches", pattern = pattern) + model("${idea}intentions/convertToConcatenatedString", pattern = pattern) model("code-insight/intentions-k2/tests/testData/intentions", pattern = pattern) } }