From debd30a47a7e6d3772795689ff546ca23cedc1a8 Mon Sep 17 00:00:00 2001 From: Sergey Vardanyan Date: Wed, 1 Oct 2025 20:24:45 +0400 Subject: [PATCH] Support custom builder types in `@GeneratedBuilder` annotation - Extended the `@GeneratedBuilder` annotation to accept a `type` parameter of `KClass`. GitOrigin-RevId: 2a54a95853367cd1a8c9de269d4d74ad93d5e7d7 --- platform/eel/codegen/BuildersGeneratorTest.kt | 26 +++++++++++++++++-- .../intellij/platform/eel/GeneratedBuilder.kt | 17 +++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/platform/eel/codegen/BuildersGeneratorTest.kt b/platform/eel/codegen/BuildersGeneratorTest.kt index 4b3a84430a2b..6fe68ffdbb72 100644 --- a/platform/eel/codegen/BuildersGeneratorTest.kt +++ b/platform/eel/codegen/BuildersGeneratorTest.kt @@ -474,11 +474,33 @@ private fun findBuilders(psiFile: PsiFile, methods: MutableList) element is KtImportDirective -> { imports += element.text } - element is KtAnnotationEntry && element.typeReference?.renderWithFqnTypes() == "com.intellij.platform.eel.GeneratedBuilder" -> { + element is KtAnnotationEntry && element.typeReference?.renderWithFqnTypes()?.contains("GeneratedBuilder") ?: false -> { val valueParameter = element.parent.parent as? KtParameter ?: return + + // Resolves the fully qualified type name for a value parameter by analyzing annotation arguments. + // First attempts to extract the type from a class literal expression in the annotation's first argument, + // then falls back to the value parameter's declared type if no annotation type is found. + // Filters out the GeneratedBuilder type and ensures type compatibility before returning the FQN. val typeFqn = analyze(valueParameter) { - valueParameter.typeReference?.type?.toString()?.replace("/", ".") ?: return + val valueParameterType = valueParameter.typeReference?.type ?: error("Parameter type is null") + val valueParameterTypeFqn = valueParameterType.toString().replace("/", ".") + + val annotationParameterExpression = (element.valueArguments.firstOrNull() as KtValueArgument?)?.getArgumentExpression() as KtClassLiteralExpression? + if (annotationParameterExpression == null) return@analyze valueParameterTypeFqn + + return@analyze analyze(annotationParameterExpression) { + val annotationParameterType = annotationParameterExpression.receiverType ?: return@analyze null + val annotationParameterTypeFqn = annotationParameterType.toString().replace("/", ".").takeIf { + it != "com.intellij.platform.eel.GeneratedBuilder" + } + if (annotationParameterTypeFqn == null) return@analyze null + + assert(annotationParameterType.isSubtypeOf(valueParameterType)) { "Specified type $this is not a subtype of the value parameter type $valueParameterType" } + + return@analyze annotationParameterTypeFqn + } ?: valueParameterTypeFqn } + val fn = valueParameter.parent.parent as? KtNamedFunction ?: return val methodName = fn.name ?: return val methodCls = valueParameter.containingClass()?.fqName ?: return diff --git a/platform/eel/src/com/intellij/platform/eel/GeneratedBuilder.kt b/platform/eel/src/com/intellij/platform/eel/GeneratedBuilder.kt index 2ec24454e205..cb813b576ce8 100644 --- a/platform/eel/src/com/intellij/platform/eel/GeneratedBuilder.kt +++ b/platform/eel/src/com/intellij/platform/eel/GeneratedBuilder.kt @@ -2,15 +2,30 @@ package com.intellij.platform.eel import org.jetbrains.annotations.ApiStatus +import kotlin.reflect.KClass /** * `com.intellij.platform.eel.codegen.BuildersGeneratorTest` generates builders for all methods * that have a single argument with this annotation. + * + * @property type The class type associated with the generated builder. + * It must be a subtype of the annotated argument's type. + * Default value of the type property is `GeneratedBuilder::class` which means that the annotated argument's type will be used. + * + * Examples: + * ```kotlin + * // Generates a builder for `ExecuteProcessWindowsOptions` + * fun spawnProcess(@GeneratedBuilder(ExecuteProcessWindowsOptions::class) generatedBuilder: ExecuteProcessOptions) + * ``` + * `````kotlin + * // Generates a builder for `ExecuteProcessOptions` + * fun spawnProcess(@GeneratedBuilder() generatedBuilder: ExecuteProcessOptions) + * ``` */ @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.VALUE_PARAMETER) @ApiStatus.Internal -annotation class GeneratedBuilder { +annotation class GeneratedBuilder(val type: KClass<*> = GeneratedBuilder::class) { /** * The only purpose of this annotation is to help to find generated builders via "Find Usages". */