[Kotlin] Add inspection for multi-dollar string conversion

KTIJ-31819


(cherry picked from commit 7908c8ca0d47cf954c8af01d40bc9a323904395a)

KT-CR-18891

GitOrigin-RevId: cd551d652a3784abba99e1e68eb12e0be82212a1
This commit is contained in:
Pavel Kirpichenkov
2024-12-03 20:31:10 +00:00
committed by intellij-monorepo-bot
parent 7b16461e28
commit 6101bf71d5
46 changed files with 512 additions and 56 deletions
@@ -2288,6 +2288,9 @@ inspection.can.be.primary.constructor.property.display.name=Property is explicit
inspection.can.unescape.dollar.literal.inspection.display.name=Escaped dollar characters in string can be simplified
inspection.can.unescape.dollar.literal.inspection.problem.description=Escaped dollar characters in the string can be simplified
replace.with.dollar.literals=Replace escaped dollars with literals
inspection.can.convert.to.multi.dollar.string.display.name=Interpolation prefix can simplify string
inspection.can.convert.to.multi.dollar.string.problem.description=An interpolation prefix can simplify the string
add.interpolation.prefix=Add interpolation prefix
inspection.has.platform.type.display.name=Function or property has platform type
inspection.leaking.this.display.name=Leaking 'this' in constructor
inspection.redundant.if.display.name=Redundant 'if' statement
@@ -0,0 +1,23 @@
<html>
<body>
Reports that a string can benefit from a conversion to a version with an interpolation prefix.
<p>
An interpolation prefix may make a string easier to comprehend, since prefixed strings can use dollar literals without additional escaping.
</p>
<p><b>Example:</b></p>
<pre><code>
val jsonSchema: String = """
{
"${'$'}id": "https://example.com/product.schema.json",
}"""
</code></pre>
The quick fix converts all safely replaceable escaped dollar characters to regular dollar characters:
<pre><code>
val jsonSchema: String = $$"""
{
"$id": "https://example.com/product.schema.json",
}"""
</code></pre>
<!-- tooltip end -->
</body>
</html>
@@ -3,22 +3,13 @@
package org.jetbrains.kotlin.idea.codeinsights.impl.base
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.psi.KtBlockStringTemplateEntry
import org.jetbrains.kotlin.psi.KtEscapeStringTemplateEntry
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtSimpleNameStringTemplateEntry
import org.jetbrains.kotlin.psi.KtStringTemplateEntry
import org.jetbrains.kotlin.psi.KtStringTemplateEntryWithExpression
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isSingleQuoted
import org.jetbrains.kotlin.psi.psiUtil.plainContent
private const val DEFAULT_INTERPOLATION_PREFIX_LENGTH: Int = 2
private const val INTERPOLATION_PREFIX_LENGTH_THRESHOLD: Int = 5
private const val DOLLAR: String = "$"
val dollarLiteralExpressions: Array<String> = arrayOf(
"'$'", "\"$\""
)
@@ -103,11 +94,11 @@ class EntryUpdateDiff(
}
}
internal fun KtStringTemplateEntry.asOneToOneDiff(newText: String): EntryUpdateDiff {
private fun KtStringTemplateEntry.asOneToOneDiff(newText: String): EntryUpdateDiff {
return EntryUpdateDiff(0..<textLength, text, newText)
}
fun KtStringTemplateEntryWithExpression.changePrefixLength(prefixLength: Int): KtStringTemplateEntryWithExpression {
private fun KtStringTemplateEntryWithExpression.changePrefixLength(prefixLength: Int): KtStringTemplateEntryWithExpression {
require(prefixLength > 0) { "Unexpected string template prefix length: $prefixLength" }
val replacement = when (this) {
@@ -119,7 +110,7 @@ fun KtStringTemplateEntryWithExpression.changePrefixLength(prefixLength: Int): K
return replacement
}
fun KtBlockStringTemplateEntry.changePrefixLength(prefixLength: Int): KtStringTemplateEntryWithExpression {
private fun KtBlockStringTemplateEntry.changePrefixLength(prefixLength: Int): KtStringTemplateEntryWithExpression {
require(prefixLength > 0) { "Unexpected string template entry prefix length: $prefixLength" }
val ktPsiFactory = KtPsiFactory(project)
val expression = this.expression
@@ -138,7 +129,7 @@ fun KtBlockStringTemplateEntry.changePrefixLength(prefixLength: Int): KtStringTe
return replacement
}
fun KtSimpleNameStringTemplateEntry.changePrefixLength(prefixLength: Int): KtSimpleNameStringTemplateEntry {
private fun KtSimpleNameStringTemplateEntry.changePrefixLength(prefixLength: Int): KtSimpleNameStringTemplateEntry {
require(prefixLength > 0) { "Unexpected string template entry prefix length: $prefixLength" }
val ktPsiFactory = KtPsiFactory(project)
return ktPsiFactory.createMultiDollarSimpleNameStringTemplateEntry(
@@ -147,7 +138,7 @@ fun KtSimpleNameStringTemplateEntry.changePrefixLength(prefixLength: Int): KtSim
)
}
fun KtLiteralStringTemplateEntry.escapeIfNecessary(
private fun KtLiteralStringTemplateEntry.escapeIfNecessary(
newPrefixLength: Int,
isSourceSingleQuoted: Boolean,
isDestinationSingleQuoted: Boolean,
@@ -183,7 +174,7 @@ private fun KtLiteralStringTemplateEntry.escapeDollarIfNecessary(
)
}
fun KtLiteralStringTemplateEntry.escapeSpecialCharacters(): List<EntryUpdateDiff> {
private fun KtLiteralStringTemplateEntry.escapeSpecialCharacters(): List<EntryUpdateDiff> {
val escaper = StringUtil.escaper(true, "\"")
var from = 0
var to = 0
@@ -216,7 +207,7 @@ fun KtLiteralStringTemplateEntry.escapeSpecialCharacters(): List<EntryUpdateDiff
return diffs
}
fun KtStringTemplateEntry.unescapeIfPossible(newPrefixLength: Int): KtStringTemplateEntry {
private fun KtStringTemplateEntry.unescapeIfPossible(newPrefixLength: Int): KtStringTemplateEntry {
fun previousDollarsCount(): Int {
if (this.prevSibling !is KtLiteralStringTemplateEntry) return 0
return this.prevSibling.text.takeLastWhile { it == '$' }.length
@@ -263,38 +254,47 @@ fun Char.canBeStartOfIdentifierOrBlock(): Boolean {
return isLetter() || this == '_' || this == '{' || this == '`'
}
fun KtStringTemplateEntry.isEscapedDollar(): Boolean {
return when (this) {
is KtEscapeStringTemplateEntry -> {
return unescapedValue == "$"
}
is KtBlockStringTemplateEntry -> {
expression?.text in dollarLiteralExpressions
}
else -> false
}
fun KtStringTemplateEntry.isEscapedDollar(): Boolean = when (this) {
is KtEscapeStringTemplateEntry -> this.isEscapedDollar()
is KtBlockStringTemplateEntry -> this.isInterpolatedDollarLiteralExpression()
else -> false
}
/**
* Convert a string to a multi-dollar string, choosing an appropriate prefix length based on the string's content.
* The function searches for the shortest possible prefix that doesn't exceed [INTERPOLATION_PREFIX_LENGTH_THRESHOLD].
* If no such prefix exists, the [DEFAULT_INTERPOLATION_PREFIX_LENGTH] is used.
* Context for the multi-dollar conversion inspection and intention.
*/
internal fun convertToMultiDollarString(element: KtStringTemplateExpression): KtStringTemplateExpression {
class MultiDollarConversionInfo(
val prefixLength: Int,
)
fun prepareMultiDollarConversionInfo(element: KtStringTemplateExpression, useFallbackPrefix: Boolean): MultiDollarConversionInfo? {
val suitablePrefixLength = findSuitablePrefixLength(element, useFallbackPrefix) ?: return null
return MultiDollarConversionInfo(suitablePrefixLength)
}
/**
* Search for the shortest possible prefix that doesn't exceed [INTERPOLATION_PREFIX_LENGTH_THRESHOLD].
* If no such prefix exists, the [DEFAULT_INTERPOLATION_PREFIX_LENGTH] if [useFallbackPrefix] is `true`, or `null` otherwise.
*/
private fun findSuitablePrefixLength(element: KtStringTemplateExpression, useFallbackPrefix: Boolean): Int? {
val longestUnsafeDollarSequence = longestUnsafeDollarSequenceLength(element, INTERPOLATION_PREFIX_LENGTH_THRESHOLD)
if (longestUnsafeDollarSequence >= INTERPOLATION_PREFIX_LENGTH_THRESHOLD) {
return if (useFallbackPrefix) DEFAULT_INTERPOLATION_PREFIX_LENGTH else null
}
return maxOf(longestUnsafeDollarSequence + 1, DEFAULT_INTERPOLATION_PREFIX_LENGTH)
}
/**
* Convert a plain string to a multi-dollar string with the specified prefix length
*/
fun convertToMultiDollarString(element: KtStringTemplateExpression, contextInfo: MultiDollarConversionInfo): KtStringTemplateExpression {
require(element.interpolationPrefix == null) { "Can't convert the string which already has a prefix to multi-dollar string" }
val longestUnsafeDollarSequence = longestUnsafeDollarSequenceLength(element, threshold = INTERPOLATION_PREFIX_LENGTH_THRESHOLD)
val prefixLength = if (longestUnsafeDollarSequence in DEFAULT_INTERPOLATION_PREFIX_LENGTH..< INTERPOLATION_PREFIX_LENGTH_THRESHOLD)
longestUnsafeDollarSequence + 1 else DEFAULT_INTERPOLATION_PREFIX_LENGTH
replaceExpressionEntries(element, prefixLength)
replaceExpressionEntries(element, contextInfo.prefixLength)
val replaced = element.replace(
KtPsiFactory(element.project).createMultiDollarStringTemplate(
content = element.plainContent,
prefixLength = prefixLength,
prefixLength = contextInfo.prefixLength,
forceMultiQuoted = !element.isSingleQuoted(),
)
) as KtStringTemplateExpression
@@ -306,7 +306,7 @@ internal fun convertToMultiDollarString(element: KtStringTemplateExpression): Kt
* Replace dollar escape sequences in a string template if it's safe, i.e., if replacement won't turn a literal part into interpolation.
* Both `\$` and `${'$'}` sequences are replaced if possible.
*/
internal fun simplifyDollarEntries(element: KtStringTemplateExpression): KtStringTemplateExpression {
fun simplifyDollarEntries(element: KtStringTemplateExpression): KtStringTemplateExpression {
val ktPsiFactory = KtPsiFactory(element.project)
val prefixLength = element.interpolationPrefix?.textLength?.takeIf { it > 1 } ?: return element
@@ -314,12 +314,12 @@ internal fun simplifyDollarEntries(element: KtStringTemplateExpression): KtStrin
when (entry) {
is KtEscapeStringTemplateEntry -> {
if (entry.isEscapedDollar() && entry.isSafeToReplaceWithDollar(prefixLength))
entry.replace(ktPsiFactory.createLiteralStringTemplateEntry(DOLLAR))
entry.replace(ktPsiFactory.createLiteralStringTemplateEntry("$"))
}
is KtBlockStringTemplateEntry -> {
if (entry.expression?.text in dollarLiteralExpressions && entry.isSafeToReplaceWithDollar(prefixLength))
entry.replace(ktPsiFactory.createLiteralStringTemplateEntry(DOLLAR))
entry.replace(ktPsiFactory.createLiteralStringTemplateEntry("$"))
}
}
}
@@ -352,7 +352,7 @@ internal fun longestUnsafeDollarSequenceLength(
is KtBlockStringTemplateEntry -> {
when {
entry.isSimplifiableInterpolatedDollar() -> current++
entry.isInterpolatedDollarLiteralExpression() -> current++
else -> {
current = 0
}
@@ -384,7 +384,7 @@ internal fun longestUnsafeDollarSequenceLength(
return longest
}
private fun KtBlockStringTemplateEntry.isSimplifiableInterpolatedDollar(): Boolean {
private fun KtBlockStringTemplateEntry.isInterpolatedDollarLiteralExpression(): Boolean {
return this.expression?.text in dollarLiteralExpressions
}
@@ -396,7 +396,7 @@ private fun replaceExpressionEntries(stringTemplate: KtStringTemplateExpression,
}
}
private fun KtEscapeStringTemplateEntry.isEscapedDollar(): Boolean = unescapedValue == DOLLAR
private fun KtEscapeStringTemplateEntry.isEscapedDollar(): Boolean = unescapedValue == "$"
/**
* It's unsafe to replace with a `$` if the part before the entry ends with a `$`, and the part after can be considered identifier/block.
@@ -406,6 +406,6 @@ private fun KtStringTemplateEntry.isSafeToReplaceWithDollar(prefixLength: Int):
if (prevSibling !is KtLiteralStringTemplateEntry) return true
val nextSiblingStringLiteral = nextSibling as? KtLiteralStringTemplateEntry ?: return true
if (!nextSiblingStringLiteral.canBeConsideredIdentifierOrBlock()) return true
val trailingDollarsLength = prevSibling.text.takeLastWhile { it.toString() == DOLLAR }.length
val trailingDollarsLength = prevSibling.text.takeLastWhile { it.toString() == "$" }.length
return trailingDollarsLength + 1 < prefixLength
}
@@ -68,6 +68,14 @@
language="kotlin"
key="inspection.can.unescape.dollar.literal.inspection.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.k2.codeinsight.inspections.CanConvertToMultiDollarStringInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.code.migration"
enabledByDefault="true"
level="WARNING"
language="kotlin"
key="inspection.can.convert.to.multi.dollar.string.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.k2.codeinsight.inspections.diagnosticBased.RedundantModalityModifierInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.redundant.constructs"
@@ -0,0 +1,72 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.k2.codeinsight.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.codeInspection.util.InspectionMessage
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinApplicableInspectionBase
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinModCommandQuickFix
import org.jetbrains.kotlin.idea.codeinsights.impl.base.*
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.KtVisitor
import org.jetbrains.kotlin.psi.KtVisitorVoid
class CanConvertToMultiDollarStringInspection :
KotlinApplicableInspectionBase.Simple<KtStringTemplateExpression, MultiDollarConversionInfo>() {
override fun isApplicableByPsi(element: KtStringTemplateExpression): Boolean {
if (!element.languageVersionSettings.supportsFeature(LanguageFeature.MultiDollarInterpolation)) return false
return element.interpolationPrefix == null
}
override fun buildVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean
): KtVisitor<*, *> {
return object : KtVisitorVoid() {
override fun visitStringTemplateExpression(entry: KtStringTemplateExpression) {
visitTargetElement(entry, holder, isOnTheFly)
}
}
}
context(KaSession)
override fun prepareContext(element: KtStringTemplateExpression): MultiDollarConversionInfo? {
if (!element.entries.any { it.isEscapedDollar() }) return null
return prepareMultiDollarConversionInfo(element, useFallbackPrefix = false)
}
override fun getProblemDescription(
element: KtStringTemplateExpression,
context: MultiDollarConversionInfo,
): @InspectionMessage String {
return KotlinBundle.message("inspection.can.convert.to.multi.dollar.string.problem.description")
}
override fun createQuickFix(
element: KtStringTemplateExpression,
context: MultiDollarConversionInfo,
): KotlinModCommandQuickFix<KtStringTemplateExpression> {
return object : KotlinModCommandQuickFix<KtStringTemplateExpression>() {
override fun getFamilyName(): @IntentionFamilyName String {
return KotlinBundle.message("add.interpolation.prefix")
}
override fun applyFix(
project: Project,
element: KtStringTemplateExpression,
updater: ModPsiUpdater
) {
val multiDollarVersion = convertToMultiDollarString(element, context)
simplifyDollarEntries(multiDollarVersion)
}
}
}
}
@@ -14,7 +14,6 @@ import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinApplicableInspectionBase
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.inspections.KotlinModCommandQuickFix
import org.jetbrains.kotlin.idea.codeinsights.impl.base.canBeStartOfIdentifierOrBlock
import org.jetbrains.kotlin.idea.codeinsights.impl.base.dollarLiteralExpressions
import org.jetbrains.kotlin.idea.codeinsights.impl.base.isEscapedDollar
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isSingleQuoted
@@ -8262,6 +8262,140 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString")
public static class CanConvertToMultiDollarString extends AbstractK2LocalInspectionTest {
@java.lang.Override
@org.jetbrains.annotations.NotNull
public final KotlinPluginMode getPluginMode() {
return KotlinPluginMode.K2;
}
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("blockDollarWithChar.kt")
public void testBlockDollarWithChar() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/blockDollarWithChar.kt");
}
@TestMetadata("blockDollarWithString.kt")
public void testBlockDollarWithString() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/blockDollarWithString.kt");
}
@TestMetadata("escapedDollar.kt")
public void testEscapedDollar() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/escapedDollar.kt");
}
@TestMetadata("escapedDollarCaretInsideBlockExpression.kt")
public void testEscapedDollarCaretInsideBlockExpression() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/escapedDollarCaretInsideBlockExpression.kt");
}
@TestMetadata("multipleSimplifiableDollars.kt")
public void testMultipleSimplifiableDollars() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/multipleSimplifiableDollars.kt");
}
@TestMetadata("notReplaceableAlreadyPrefixed.kt")
public void testNotReplaceableAlreadyPrefixed() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableAlreadyPrefixed.kt");
}
@TestMetadata("notReplaceableFirstIsTooLong.kt")
public void testNotReplaceableFirstIsTooLong() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableFirstIsTooLong.kt");
}
@TestMetadata("notReplaceableNothingToChange.kt")
public void testNotReplaceableNothingToChange() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableNothingToChange.kt");
}
@TestMetadata("notReplaceableNothingToChangeTripleQuoted.kt")
public void testNotReplaceableNothingToChangeTripleQuoted() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableNothingToChangeTripleQuoted.kt");
}
@TestMetadata("notReplaceableNothingToChangeTripleQuotedMultiline.kt")
public void testNotReplaceableNothingToChangeTripleQuotedMultiline() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableNothingToChangeTripleQuotedMultiline.kt");
}
@TestMetadata("notReplaceableSecondIsTooLong.kt")
public void testNotReplaceableSecondIsTooLong() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableSecondIsTooLong.kt");
}
@TestMetadata("notReplaceableTooManyDollars.kt")
public void testNotReplaceableTooManyDollars() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableTooManyDollars.kt");
}
@TestMetadata("notReplaceableTooManyDollarsBacktickIdentifier.kt")
public void testNotReplaceableTooManyDollarsBacktickIdentifier() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableTooManyDollarsBacktickIdentifier.kt");
}
@TestMetadata("notReplaceableTooManyDollarsBlock.kt")
public void testNotReplaceableTooManyDollarsBlock() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableTooManyDollarsBlock.kt");
}
@TestMetadata("notReplaceableTooManyDollarsUnderscoreIdentifier.kt")
public void testNotReplaceableTooManyDollarsUnderscoreIdentifier() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableTooManyDollarsUnderscoreIdentifier.kt");
}
@TestMetadata("notReplaceableTripleQuotedMultiLine.kt")
public void testNotReplaceableTripleQuotedMultiLine() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableTripleQuotedMultiLine.kt");
}
@TestMetadata("notReplaceableTripleQuotedPrefixed.kt")
public void testNotReplaceableTripleQuotedPrefixed() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableTripleQuotedPrefixed.kt");
}
@TestMetadata("notReplaceableTripleQuotedSingleLine.kt")
public void testNotReplaceableTripleQuotedSingleLine() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/notReplaceableTripleQuotedSingleLine.kt");
}
@TestMetadata("replaceableManyDollars.kt")
public void testReplaceableManyDollars() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/replaceableManyDollars.kt");
}
@TestMetadata("replaceableManyDollarsTwoSeqences.kt")
public void testReplaceableManyDollarsTwoSeqences() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/replaceableManyDollarsTwoSeqences.kt");
}
@TestMetadata("tripleQuotedReplaceableMultiLine.kt")
public void testTripleQuotedReplaceableMultiLine() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/tripleQuotedReplaceableMultiLine.kt");
}
@TestMetadata("tripleQuotedReplaceableMultiLineLong.kt")
public void testTripleQuotedReplaceableMultiLineLong() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/tripleQuotedReplaceableMultiLineLong.kt");
}
@TestMetadata("tripleQuotedReplaceableSingleLine.kt")
public void testTripleQuotedReplaceableSingleLine() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/tripleQuotedReplaceableSingleLine.kt");
}
@TestMetadata("tripleQuotedReplaceableSingleLineLong.kt")
public void testTripleQuotedReplaceableSingleLineLong() throws Exception {
runTest("../../../idea/tests/testData/inspectionsLocal/canConvertToMultiDollarString/tripleQuotedReplaceableSingleLineLong.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("testData/inspectionsLocal")
public abstract static class InspectionsLocal extends AbstractK2LocalInspectionTest {
@@ -10,13 +10,14 @@ import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.KotlinApplicableModCommandAction
import org.jetbrains.kotlin.idea.k2.codeinsight.intentions.multiDollarStrings.convertToMultiDollarString
import org.jetbrains.kotlin.idea.k2.codeinsight.intentions.multiDollarStrings.simplifyDollarEntries
import org.jetbrains.kotlin.idea.codeinsights.impl.base.MultiDollarConversionInfo
import org.jetbrains.kotlin.idea.codeinsights.impl.base.convertToMultiDollarString
import org.jetbrains.kotlin.idea.codeinsights.impl.base.prepareMultiDollarConversionInfo
import org.jetbrains.kotlin.idea.codeinsights.impl.base.simplifyDollarEntries
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
internal class ConvertToMultiDollarStringIntention : KotlinApplicableModCommandAction<KtStringTemplateExpression, Unit>(
KtStringTemplateExpression::class,
) {
internal class ConvertToMultiDollarStringIntention :
KotlinApplicableModCommandAction<KtStringTemplateExpression, MultiDollarConversionInfo>(KtStringTemplateExpression::class) {
override fun getFamilyName(): @IntentionFamilyName String = KotlinBundle.message("convert.to.multi.dollar.string")
override fun isApplicableByPsi(element: KtStringTemplateExpression): Boolean {
@@ -27,13 +28,15 @@ internal class ConvertToMultiDollarStringIntention : KotlinApplicableModCommandA
override fun invoke(
actionContext: ActionContext,
element: KtStringTemplateExpression,
elementContext: Unit,
elementContext: MultiDollarConversionInfo,
updater: ModPsiUpdater
) {
val replaced = convertToMultiDollarString(element)
val replaced = convertToMultiDollarString(element, elementContext)
simplifyDollarEntries(replaced)
}
context(KaSession)
override fun prepareContext(element: KtStringTemplateExpression): Unit = Unit
override fun prepareContext(element: KtStringTemplateExpression): MultiDollarConversionInfo? {
return prepareMultiDollarConversionInfo(element, useFallbackPrefix = true)
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.k2.codeinsight.inspections.CanConvertToMultiDollarStringInspection
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"${'$'}<caret>"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$"$"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"${"$"}<caret>"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$"$"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"\$<caret>"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$"$"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"\$ ${15 +<caret> 27}"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$"$ $${15 + 27}"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"\$\$\$${'$'}${'$'}${"$"}${"$"}<caret>"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$"$$$$$$$"
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test() {
$$"\$<caret>"
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test() {
"\$\$\$\$\$<caret>Foo \$\$Bar"
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test(some: Int) {
"$10 10$ $$$$$ ${3 + 2} $some<caret>"
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test(some: Int) {
"""$10 10$ <caret>$$$$$ ${3 + 2} $some"""
}
@@ -0,0 +1,9 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test(some: Int) {
"""<caret>
$10 10$ $$$$$ ${3 + 2} $some
$10 10$ $$$$$ ${3 + 2} $some
""".trimIndent()
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test() {
"\$\$<caret>Foo \$\$\$\$\$Bar"
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test() {
"\$\$\$\$\$<caret>Foo"
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test() {
"\$\$\$\$\$<caret>`Foo`"
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test() {
"\$\$\$\$\$<caret>{Foo}"
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test() {
"\$\$\$\$\$<caret>_Foo"
}
@@ -0,0 +1,10 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test() {
"""
${'$'}${'$'}${'$'}${'$'}${'$'}Foo
${'$'}${'$'}${'$'}${'$'}${'$'}Bar
${'$'}${'$'}${'$'}${'$'}${'$'}Baz<caret>
"""
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test() {
$$"""${'$'}${'$'}${'$'}Foo<caret>"""
}
@@ -0,0 +1,6 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
// PROBLEM: none
fun test() {
"""${'$'}${'$'}${'$'}${'$'}${'$'}Foo<caret>"""
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"${'$'}\$\$${"$"}<caret>Foo"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$$$$"$$$$Foo"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"${'$'}\$\$${"$"}<caret>Foo ${'$'}\$\$${"$"}<caret>Bar"
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$$$$"$$$$Foo $$$$Bar"
}
@@ -0,0 +1,9 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"""
\$\$${'$'}${'$'}${'$'}Foo<caret>
\$\$${'$'}${'$'}${'$'}Bar
\$\$${'$'}${'$'}${'$'}Baz
"""
}
@@ -0,0 +1,9 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$$$$"""
\$\$$$$Foo
\$\$$$$Bar
\$\$$$$Baz
"""
}
@@ -0,0 +1,9 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"""
${'$'}${'$'}${'$'}${'$'}${'$'}${'$'}
${'$'}${'$'}${'$'}${'$'}${'$'}${'$'}<caret>
${'$'}${'$'}${'$'}${'$'}${'$'}${'$'}
"""
}
@@ -0,0 +1,9 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$"""
$$$$$$
$$$$$$
$$$$$$
"""
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"""\$\$${'$'}${'$'}${'$'}F<caret>oo"""
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$$$$"""\$\$$$$Foo"""
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
"""${'$'}${'$'}${'$'}${'$'}<caret>${'$'}${'$'}"""
}
@@ -0,0 +1,5 @@
// COMPILER_ARGUMENTS: -Xmulti-dollar-interpolation
fun test() {
$$"""$$$$$$"""
}
@@ -589,6 +589,7 @@ private fun assembleWorkspace(): TWorkspace = workspace(KotlinPluginMode.K1) {
excludedDirectories = listOf(
"unusedVariable", // In FE1.0, this is a quickfix rather than a local inspection
"canSimplifyDollarLiteral", // K2-only
"canConvertToMultiDollarString", // K2-only
)
)
}
@@ -66,6 +66,7 @@ internal fun MutableTWorkspace.generateK2InspectionTests() {
model("${idea}/inspectionsLocal/arrayInDataClass")
model("${idea}/inspectionsLocal/collections/simplifiableCallChain")
model("${idea}/inspectionsLocal/canSimplifyDollarLiteral")
model("${idea}/inspectionsLocal/canConvertToMultiDollarString")
model("code-insight/inspections-k2/tests/testData/inspectionsLocal", pattern = pattern)
}
/**