mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
K2 intention: Handle convert-to-concatenated-string-intention
^KTIJ-22940 fixed GitOrigin-RevId: 1644ca792280ad47ccba1674511cd1bc2ed18460
This commit is contained in:
committed by
intellij-monorepo-bot
parent
be665e781e
commit
63e8b16ace
@@ -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
|
||||
|
||||
+128
@@ -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>(
|
||||
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<String>()) { 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
|
||||
}
|
||||
+7
@@ -83,5 +83,12 @@
|
||||
<bundleName>messages.KotlinBundle</bundleName>
|
||||
<categoryKey>group.names.kotlin</categoryKey>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<language>kotlin</language>
|
||||
<className>org.jetbrains.kotlin.idea.k2.codeinsight.intentions.ConvertToConcatenatedStringIntention</className>
|
||||
<bundleName>messages.KotlinBundle</bundleName>
|
||||
<categoryKey>group.names.kotlin</categoryKey>
|
||||
</intentionAction>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
+13
@@ -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
|
||||
}
|
||||
}
|
||||
+193
@@ -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 {
|
||||
|
||||
+5
-95
@@ -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>(
|
||||
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))
|
||||
}
|
||||
+20
@@ -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");
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.k2.codeinsight.intentions.ConvertToConcatenatedStringIntention
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(): String {
|
||||
return <caret>"${}${1 + 1}abc"
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(): String {
|
||||
return (1 + 1).toString() + "abc"
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(): String {
|
||||
return <caret>"${("a" + "b") + "c"}"
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun test(): String {
|
||||
return ("a" + "b") + "c"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.EXPRESSION)
|
||||
annotation class Foo
|
||||
|
||||
fun test(): String {
|
||||
return "<caret>${@Foo 1}a"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.EXPRESSION)
|
||||
annotation class Foo
|
||||
|
||||
fun test(): String {
|
||||
return (@Foo 1).toString() + "a"
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun test(): String {
|
||||
var l = 0
|
||||
return "<caret>${l++}a$l"
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun test(): String {
|
||||
var l = 0
|
||||
return l++.toString() + "a" + l
|
||||
}
|
||||
+1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user