Support custom builder types in @GeneratedBuilder annotation

- Extended the `@GeneratedBuilder` annotation to accept a `type` parameter of `KClass`.

GitOrigin-RevId: 2a54a95853367cd1a8c9de269d4d74ad93d5e7d7
This commit is contained in:
Sergey Vardanyan
2025-10-01 19:12:43 +00:00
committed by intellij-monorepo-bot
parent ae43109823
commit debd30a47a
2 changed files with 40 additions and 3 deletions
+24 -2
View File
@@ -474,11 +474,33 @@ private fun findBuilders(psiFile: PsiFile, methods: MutableList<BuilderRequest>)
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
@@ -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".
*/