[PSI] KtPsiFactory: respect `eventSystemEnabled` for code fragments KtPsiFactory already uses eventSystemEnabled when it creates ordinary files, but code fragment factories skipped that value because the light file view provider always enabled the event system. This made fragments physical even for factories created with eventSystemEnabled = false, so import mutation took the physical PSI path and required a command. Pass the factory flag through type, expression, and block fragment creation while keeping the existing public constructors event-enabled by default. Add regression coverage for physical state and block fragment import updates. ^KT-86869 Fixed
diff --git a/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtBlockCodeFragment.kt b/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtBlockCodeFragment.kt index 56fd4ae..a6d6a1d 100644 --- a/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtBlockCodeFragment.kt +++ b/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtBlockCodeFragment.kt
@@ -34,7 +34,20 @@ imports: String?, context: PsiElement? ) : this( - createFileViewProviderForLightFile(project, name, text), + createFileViewProviderForLightFile(project, name, text, eventSystemEnabled = true), + imports, + context, + ) + + internal constructor( + project: Project, + name: String, + text: CharSequence, + imports: String?, + context: PsiElement?, + eventSystemEnabled: Boolean, + ) : this( + createFileViewProviderForLightFile(project, name, text, eventSystemEnabled), imports, context, )
diff --git a/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt b/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt index 0ed5146..40adc4e 100644 --- a/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt +++ b/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt
@@ -35,7 +35,22 @@ elementType: IElementType, context: PsiElement? ) : this( - createFileViewProviderForLightFile(project, name, text), + createFileViewProviderForLightFile(project, name, text, eventSystemEnabled = true), + imports, + elementType, + context, + ) + + internal constructor( + project: Project, + name: String, + text: CharSequence, + imports: String?, + elementType: IElementType, + context: PsiElement?, + eventSystemEnabled: Boolean, + ) : this( + createFileViewProviderForLightFile(project, name, text, eventSystemEnabled), imports, elementType, context, @@ -228,11 +243,16 @@ val FAKE_CONTEXT_FOR_JAVA_FILE: Key<Function0<KtElement>> = Key.create("FAKE_CONTEXT_FOR_JAVA_FILE") - internal fun createFileViewProviderForLightFile(project: Project, name: String, text: CharSequence): FileViewProvider { + internal fun createFileViewProviderForLightFile( + project: Project, + name: String, + text: CharSequence, + eventSystemEnabled: Boolean, + ): FileViewProvider { val psiManager = PsiManager.getInstance(project) as PsiManagerEx return psiManager.fileManager.createFileViewProvider( LightVirtualFile(name, KotlinFileType.INSTANCE, text), - /* eventSystemEnabled = */true + eventSystemEnabled ) } @@ -242,4 +262,4 @@ fun interface KotlinCodeFragmentImportModificationListener { fun onCodeFragmentImportsModification(codeFragment: KtCodeFragment) -} \ No newline at end of file +}
diff --git a/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtExpressionCodeFragment.kt b/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtExpressionCodeFragment.kt index 3754cfe..f1fc56a 100644 --- a/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtExpressionCodeFragment.kt +++ b/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtExpressionCodeFragment.kt
@@ -20,13 +20,26 @@ import com.intellij.psi.PsiElement import org.jetbrains.kotlin.KtNodeTypes -class KtExpressionCodeFragment( - project: Project, - name: String, - text: CharSequence, - imports: String?, - context: PsiElement? -) : KtCodeFragment(project, name, text, imports, KtNodeTypes.EXPRESSION_CODE_FRAGMENT, context) { +class KtExpressionCodeFragment : KtCodeFragment { + /** + * Creates an expression code fragment from [text] with optional [imports] and [context]. + */ + constructor( + project: Project, + name: String, + text: CharSequence, + imports: String?, + context: PsiElement? + ) : super(project, name, text, imports, KtNodeTypes.EXPRESSION_CODE_FRAGMENT, context) + + internal constructor( + project: Project, + name: String, + text: CharSequence, + imports: String?, + context: PsiElement?, + eventSystemEnabled: Boolean, + ) : super(project, name, text, imports, KtNodeTypes.EXPRESSION_CODE_FRAGMENT, context, eventSystemEnabled) override fun getContentElement() = findChildByClass(KtExpression::class.java) }
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 a93f9ce..879abc3 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
@@ -607,15 +607,15 @@ fun createLabeledExpression(@NonNls labelName: String): KtLabeledExpression = createExpression("$labelName@ 1") as KtLabeledExpression fun createTypeCodeFragment(@NonNls text: String, context: PsiElement?): KtTypeCodeFragment { - return KtTypeCodeFragment(project, "fragment.kt", text, context) + return KtTypeCodeFragment(project, "fragment.kt", text, context, eventSystemEnabled) } fun createExpressionCodeFragment(@NonNls text: String, context: PsiElement?): KtExpressionCodeFragment { - return KtExpressionCodeFragment(project, "fragment.kt", text, null, context) + return KtExpressionCodeFragment(project, "fragment.kt", text, null, context, eventSystemEnabled) } fun createBlockCodeFragment(@NonNls text: String, context: PsiElement?): KtBlockCodeFragment { - return KtBlockCodeFragment(project, "fragment.kt", text, null, context) + return KtBlockCodeFragment(project, "fragment.kt", text, null, context, eventSystemEnabled) } fun createIf(condition: KtExpression, thenExpr: KtExpression, elseExpr: KtExpression? = null): KtIfExpression {
diff --git a/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtTypeCodeFragment.kt b/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtTypeCodeFragment.kt index 1e824a3..0df5337 100644 --- a/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtTypeCodeFragment.kt +++ b/compiler/psi/psi-api/src/org/jetbrains/kotlin/psi/KtTypeCodeFragment.kt
@@ -20,11 +20,24 @@ import com.intellij.psi.PsiElement import org.jetbrains.kotlin.KtNodeTypes -class KtTypeCodeFragment( - project: Project, - name: String, - text: CharSequence, - context: PsiElement? -) : KtCodeFragment(project, name, text, null, KtNodeTypes.TYPE_CODE_FRAGMENT, context) { +class KtTypeCodeFragment : KtCodeFragment { + /** + * Creates a type code fragment from [text] with optional [context]. + */ + constructor( + project: Project, + name: String, + text: CharSequence, + context: PsiElement? + ) : super(project, name, text, null, KtNodeTypes.TYPE_CODE_FRAGMENT, context) + + internal constructor( + project: Project, + name: String, + text: CharSequence, + context: PsiElement?, + eventSystemEnabled: Boolean, + ) : super(project, name, text, null, KtNodeTypes.TYPE_CODE_FRAGMENT, context, eventSystemEnabled) + override fun getContentElement() = findChildByClass(KtTypeReference::class.java) }
diff --git a/compiler/psi/psi-impl/tests/org/jetbrains/kotlin/psi/KtCodeFragmentTest.kt b/compiler/psi/psi-impl/tests/org/jetbrains/kotlin/psi/KtCodeFragmentTest.kt index a81c217..1c25f77 100644 --- a/compiler/psi/psi-impl/tests/org/jetbrains/kotlin/psi/KtCodeFragmentTest.kt +++ b/compiler/psi/psi-impl/tests/org/jetbrains/kotlin/psi/KtCodeFragmentTest.kt
@@ -12,6 +12,8 @@ import org.jetbrains.kotlin.analysis.test.framework.test.configurators.DummyAnalysisApiTestConfigurator import org.junit.jupiter.api.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue class KtCodeFragmentTest : AbstractAnalysisApiBasedTest() { override val configurator: AnalysisApiTestConfigurator get() = DummyAnalysisApiTestConfigurator @@ -74,6 +76,37 @@ } @Test + fun testFactoryCreatesCodeFragmentsWithDisabledEventSystem() = runCodeFragmentTest { project -> + val psiFactory = KtPsiFactory(project, markGenerated = true, eventSystemEnabled = false) + + assertFalse(psiFactory.createTypeCodeFragment("String", context = null).isPhysical) + assertFalse(psiFactory.createExpressionCodeFragment("foo()", context = null).isPhysical) + assertFalse(psiFactory.createBlockCodeFragment("val a = 5", context = null).isPhysical) + } + + @Test + fun testFactoryCreatesCodeFragmentsWithEnabledEventSystem() = runCodeFragmentTest { project -> + val psiFactory = KtPsiFactory(project, markGenerated = true, eventSystemEnabled = true) + + assertTrue(psiFactory.createTypeCodeFragment("String", context = null).isPhysical) + assertTrue(psiFactory.createExpressionCodeFragment("foo()", context = null).isPhysical) + assertTrue(psiFactory.createBlockCodeFragment("val a = 5", context = null).isPhysical) + } + + @Test + fun testAddImportsToBlockCodeFragmentWithDisabledEventSystem() = runCodeFragmentTest { project -> + val context = KtPsiFactory(project).createExpression("context") + val codeFragment = KtPsiFactory(project, markGenerated = true, eventSystemEnabled = false) + .createBlockCodeFragment("val a = 5", context) + + assertFalse(codeFragment.isPhysical) + + codeFragment.addImportsFromString("foo.bar") + + assertEquals(listOf("import foo.bar"), codeFragment.importDirectives.map { it.text }) + } + + @Test fun testClone() = runCodeFragmentTest { project -> val context = KtPsiFactory(project).createNameIdentifier("context") val codeFragment = KtExpressionCodeFragment(project, "fragment.kt", "foo()", "import lib.foo", context)