What is the best approach to extract a kotlin multiline string with trimIndent as a string expression

I’m trying to grab the content of multiline string that use trim functions as a simple KtExpression (with properly indented / trimmed string). As far as I got, there’s no easy way even with UAST.

The best I could use is reusing the ToOrdinaryStringLiteralIntention code just to extract the expression. However this has some issues with the latest 2025.1 because K2 is changing things.

E.g. in tests I have

KotlinCacheService should not be used for the K2 mode. See https://kotl.in/analysis-api/ for the migration.
java.lang.IllegalStateException: KotlinCacheService should not be used for the K2 mode. See https://kotl.in/analysis-api/ for the migration.
	at org.jetbrains.kotlin.caches.resolve.KotlinCacheService$Companion.getInstance(KotlinCacheService.kt:24)
	at org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils.getResolutionFacade(ResolutionUtils.kt:31)
	at org.jetbrains.kotlin.idea.caches.resolve.ExtendedResolutionApiKt.safeAnalyzeNonSourceRootCode(ExtendedResolutionApi.kt:158)
	at org.jetbrains.kotlin.idea.inspections.collections.FunctionUtilsKt.isCalling(FunctionUtils.kt:81)
	at org.jetbrains.kotlin.idea.inspections.collections.FunctionUtilsKt.isCalling$default(FunctionUtils.kt:77)
	at xxx.ToOrdinaryStringLiteral.getTrimIndentCall(KotlinQuirks.kt:112)
	at xxx.ToOrdinaryStringLiteral.applyTo(KotlinQuirks.kt:78)
	at xxx.ToOrdinaryStringLiteral.convertToOrdinaryStringLiteral(KotlinQuirks.kt:73)

Most the code is the same as here : intellij-community/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/intentions/ToOrdinaryStringLiteralIntention.kt at 468d9bd86c6065a31d78bfbcc9bc08c0af80d07c · JetBrains/intellij-community · GitHub

Thanks in advance for any hints.

Right now I have replaced isCalling by this check which seems better

        val qualifiedExpression = element.getQualifiedExpressionForReceiver()?.takeIf {
            // Original IJP code calls: it.callExpression?.isCalling(TRIM_INDENT_FUNCTIONS) == true
            it.callExpression?.toUElementOfType<UCallExpression>()?.let { uce ->
                kotlinTrimMethods.uCallMatches(uce)
            } == true
        } ?: return null
    private val kotlinTrimMethods: CallMatcher =
        CallMatcher.anyOf(
            CallMatcher.staticCall("kotlin.text.StringsKt__IndentKt", "trimIndent", "trimMargin"),
            CallMatcher.staticCall("kotlin.text.StringsKt", "trimIndent", "trimMargin")
        )

Our UInjectionHost.getStringRoomExpression() might help a bit.

You can also UExpression.accept and walk children by a visitor.

// to get UInjectionHost from string literal
UastContextKt.toUElement(psi, UInjectionHost.class) 

getStringRoomExpression() is interesting, but my issue really is how to get the “rendered string” not how it appears in the source code.

With Java multi-line strings are correctly with this simplified code (expression as ULiteralExpression).evaluateString(), e.g.

        method("""
            This is text
            spanning multiple lines
        """);

yields " This is text\n spanning multiple lines\n".

But Kotlin makes it more tricky because the language don’t understand that indents are eliminated, instead it relies on trimMargin / trimIndent methods to indent the string properly. And when traversing the Psi tree with UAST this makes it more difficult to get the string as a simple binary expression with indents removed.

E.g. the indents are not removed

That’s why I was using ToOrdinaryStringLiteralIntention code in the first place, but having this as UAST utility (and kotlin utility too) makes sense in my opinion.