[PSI] KtPsiFactory: Add companion block creation utilities

Create KtCompanionBlock instances through typed KtPsiFactory overloads
instead of requiring callers to parse wrapper classes and cast PSI.
Validate that input contains one exact block so malformed or compound
text cannot silently produce a partial result.

Cover empty, populated, and invalid inputs in KtPsiFactoryTest.

^KT-89032 Fixed
diff --git a/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt b/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt
index 103031b..08f3f4b 100644
--- a/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt
+++ b/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt
@@ -292,6 +292,34 @@
         return createClass("class A {\n $text\n}").companionObjects.first()
     }
 
+    /** Creates an empty companion block. */
+    @KtExperimentalApi
+    fun createCompanionBlock(): KtCompanionBlock {
+        return createCompanionBlock("companion {\n}")
+    }
+
+    /**
+     * Creates a companion block from the given [text] (for example, `"companion { ... }"`). The text must contain exactly one complete
+     * companion block and no other class-body elements.
+     */
+    @KtExperimentalApi
+    fun createCompanionBlock(@NonNls text: String): KtCompanionBlock {
+        val klass = createClass("class A {\n$text\n}")
+        val companionBlock = klass.companionBlocks.singleOrNull()
+
+        checkWithAttachment(
+            companionBlock != null &&
+                    klass.body?.declarationsAndCompanionBlocks?.singleOrNull() === companionBlock &&
+                    companionBlock.text == text,
+            { "Failed to create a single companion block from text" },
+        ) {
+            it.withAttachment("text.kt", text)
+            it.withPsiAttachment("parsed.kt", klass)
+        }
+
+        return companionBlock
+    }
+
     /** Creates a file-level annotation entry from the given [annotationText] (without the `@file:` prefix). */
     fun createFileAnnotation(@NonNls annotationText: String): KtAnnotationEntry {
         return createFileAnnotationListWithAnnotation(annotationText).annotationEntries.first()
diff --git a/compiler/psi/psi-impl/tests/org/jetbrains/kotlin/psi/KtPsiFactoryTest.kt b/compiler/psi/psi-impl/tests/org/jetbrains/kotlin/psi/KtPsiFactoryTest.kt
index ddf3f45..76809c3 100644
--- a/compiler/psi/psi-impl/tests/org/jetbrains/kotlin/psi/KtPsiFactoryTest.kt
+++ b/compiler/psi/psi-impl/tests/org/jetbrains/kotlin/psi/KtPsiFactoryTest.kt
@@ -3,9 +3,12 @@
  * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
  */
 
+@file:OptIn(KtExperimentalApi::class)
+
 package org.jetbrains.kotlin.psi
 
 import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
 import org.junit.jupiter.api.Assertions
 import org.junit.jupiter.api.Test
 
@@ -53,4 +56,47 @@
         val template = psiFactory.createRawStringTemplate("\$Foo\n\${Bar}")
         Assertions.assertEquals("\"\"\"\$Foo\n\${Bar}\"\"\"", template.text)
     }
+
+    @Test
+    fun testCreateEmptyCompanionBlock() {
+        val companionBlock = KtPsiFactory(project).createCompanionBlock()
+
+        Assertions.assertEquals("companion {\n}", companionBlock.text)
+        Assertions.assertTrue(companionBlock.declarations.isEmpty())
+    }
+
+    @Test
+    fun testCreateCompanionBlockFromText() {
+        val text = """
+            companion {
+                val answer = 42
+                fun getAnswer() = answer
+            }
+        """.trimIndent()
+
+        val companionBlock = KtPsiFactory(project).createCompanionBlock(text)
+
+        Assertions.assertEquals(text, companionBlock.text)
+        Assertions.assertEquals(2, companionBlock.declarations.size)
+        Assertions.assertTrue(companionBlock.declarations[0] is KtProperty)
+        Assertions.assertTrue(companionBlock.declarations[1] is KtNamedFunction)
+    }
+
+    @Test
+    fun testCreateCompanionBlockRejectsInvalidText() {
+        val psiFactory = KtPsiFactory(project)
+        val invalidTexts = listOf(
+            "",
+            "companion object {}",
+            "companion {}\ncompanion {}",
+            "companion {}\nval extra = 0",
+            "companion {",
+        )
+
+        invalidTexts.forEach { text ->
+            Assertions.assertThrows(KotlinExceptionWithAttachments::class.java) {
+                psiFactory.createCompanionBlock(text)
+            }
+        }
+    }
 }