[Lombok K1] Support @Builder annotation
^KT-46959
This commit is contained in:
@@ -19,6 +19,7 @@ object LombokNames {
|
||||
val NO_ARGS_CONSTRUCTOR = FqName("lombok.NoArgsConstructor")
|
||||
val ALL_ARGS_CONSTRUCTOR = FqName("lombok.AllArgsConstructor")
|
||||
val REQUIRED_ARGS_CONSTRUCTOR = FqName("lombok.RequiredArgsConstructor")
|
||||
val BUILDER = FqName("lombok.Builder")
|
||||
|
||||
|
||||
val ACCESSORS_ID = ClassId.topLevel(ACCESSORS)
|
||||
|
||||
+10
-7
@@ -10,6 +10,8 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
|
||||
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.lazy.descriptors.SyntheticJavaClassDescriptor
|
||||
import org.jetbrains.kotlin.lombok.config.LombokConfig
|
||||
import org.jetbrains.kotlin.lombok.processor.*
|
||||
import org.jetbrains.kotlin.lombok.utils.getJavaClass
|
||||
@@ -30,7 +32,8 @@ class LombokSyntheticJavaPartsProvider(config: LombokConfig) : SyntheticJavaPart
|
||||
WithProcessor(),
|
||||
NoArgsConstructorProcessor(),
|
||||
AllArgsConstructorProcessor(),
|
||||
RequiredArgsConstructorProcessor()
|
||||
RequiredArgsConstructorProcessor(),
|
||||
BuilderProcessor(config)
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -85,12 +88,12 @@ class LombokSyntheticJavaPartsProvider(config: LombokConfig) : SyntheticJavaPart
|
||||
}
|
||||
|
||||
context(LazyJavaResolverContext)
|
||||
private fun getSyntheticParts(descriptor: ClassDescriptor): SyntheticParts =
|
||||
descriptor.getJavaClass()?.let {
|
||||
partsCache.getOrPut(descriptor) {
|
||||
computeSyntheticParts(descriptor)
|
||||
}
|
||||
} ?: SyntheticParts.Empty
|
||||
private fun getSyntheticParts(descriptor: ClassDescriptor): SyntheticParts {
|
||||
if (descriptor !is LazyJavaClassDescriptor && descriptor !is SyntheticJavaClassDescriptor) return SyntheticParts.Empty
|
||||
return partsCache.getOrPut(descriptor) {
|
||||
computeSyntheticParts(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
context(LazyJavaResolverContext)
|
||||
private fun computeSyntheticParts(descriptor: ClassDescriptor): SyntheticParts {
|
||||
|
||||
@@ -189,6 +189,36 @@ object LombokAnnotations {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Builder(
|
||||
val builderClassName: String,
|
||||
val buildMethodName: String,
|
||||
val builderMethodName: String,
|
||||
val requiresToBuilder: Boolean,
|
||||
val visibility: AccessLevel,
|
||||
val setterPrefix: String?
|
||||
) {
|
||||
companion object : AnnotationAndConfigCompanion<Builder>(LombokNames.BUILDER) {
|
||||
private const val DEFAULT_BUILDER_CLASS_NAME = "*Builder"
|
||||
private const val DEFAULT_BUILD_METHOD_NAME = "build"
|
||||
private const val DEFAULT_BUILDER_METHOD_NAME = "builder"
|
||||
private const val DEFAULT_REQUIRES_TO_BUILDER = false
|
||||
|
||||
|
||||
override fun extract(annotation: AnnotationDescriptor?, config: LombokConfig): Builder {
|
||||
return Builder(
|
||||
builderClassName = annotation?.getStringArgument("builderClassName")
|
||||
?: config.getString("lombok.builder.className")
|
||||
?: DEFAULT_BUILDER_CLASS_NAME,
|
||||
buildMethodName = annotation?.getStringArgument("buildMethodName") ?: DEFAULT_BUILD_METHOD_NAME,
|
||||
builderMethodName = annotation?.getStringArgument("builderMethodName") ?: DEFAULT_BUILDER_METHOD_NAME,
|
||||
requiresToBuilder = annotation?.getBooleanArgument("toBuilder") ?: DEFAULT_REQUIRES_TO_BUILDER,
|
||||
visibility = annotation?.getAccessLevel("access") ?: AccessLevel.PUBLIC,
|
||||
setterPrefix = annotation?.getStringArgument("setterPrefix")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.lombok.processor
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities
|
||||
import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
|
||||
import org.jetbrains.kotlin.load.java.lazy.descriptors.SyntheticJavaClassDescriptor
|
||||
import org.jetbrains.kotlin.lombok.config.LombokAnnotations.Builder
|
||||
import org.jetbrains.kotlin.lombok.config.LombokConfig
|
||||
import org.jetbrains.kotlin.lombok.config.toDescriptorVisibility
|
||||
import org.jetbrains.kotlin.lombok.utils.*
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
class BuilderProcessor(private val config: LombokConfig) : Processor {
|
||||
companion object {
|
||||
private const val BUILDER_DATA = "Lombok.BuilderData"
|
||||
private const val TO_BUILDER = "toBuilder"
|
||||
}
|
||||
|
||||
context(LazyJavaResolverContext)
|
||||
@Suppress("IncorrectFormatting") // KTIJ-22227
|
||||
override fun contribute(classDescriptor: ClassDescriptor, partsBuilder: SyntheticPartsBuilder) {
|
||||
if (classDescriptor is SyntheticJavaClassDescriptor) {
|
||||
val builderData = classDescriptor.attributes[BUILDER_DATA] as? BuilderData ?: return
|
||||
contributeToBuilderClass(classDescriptor, builderData.constructingClass, builderData.builder, partsBuilder)
|
||||
} else {
|
||||
val builder = Builder.getIfAnnotated(classDescriptor, config) ?: return
|
||||
contributeToAnnotatedClass(classDescriptor, builder, partsBuilder)
|
||||
}
|
||||
}
|
||||
|
||||
context(LazyJavaResolverContext)
|
||||
@Suppress("IncorrectFormatting") // KTIJ-22227
|
||||
private fun contributeToAnnotatedClass(classDescriptor: ClassDescriptor, builder: Builder, partsBuilder: SyntheticPartsBuilder) {
|
||||
val builderName = Name.identifier(builder.builderClassName.replace("*", classDescriptor.name.asString()))
|
||||
val visibility = builder.visibility.toDescriptorVisibility()
|
||||
val builderDescriptor = SyntheticJavaClassDescriptor(
|
||||
outerContext = this@LazyJavaResolverContext,
|
||||
name = builderName,
|
||||
outerClass = classDescriptor,
|
||||
classKind = ClassKind.CLASS,
|
||||
modality = Modality.FINAL,
|
||||
visibility = visibility,
|
||||
isInner = false,
|
||||
isRecord = false,
|
||||
annotations = Annotations.EMPTY,
|
||||
declaredTypeParameters = emptyList(),
|
||||
sealedSubclasses = emptyList(),
|
||||
supertypes = listOf(components.module.builtIns.anyType),
|
||||
attributes = mapOf(BUILDER_DATA to BuilderData(builder, classDescriptor))
|
||||
)
|
||||
partsBuilder.addClass(builderDescriptor)
|
||||
val builderFunction = classDescriptor.createFunction(
|
||||
Name.identifier(builder.builderMethodName),
|
||||
valueParameters = emptyList(),
|
||||
returnType = builderDescriptor.defaultType,
|
||||
modality = Modality.FINAL,
|
||||
visibility = visibility,
|
||||
receiver = null
|
||||
)
|
||||
partsBuilder.addStaticFunction(builderFunction)
|
||||
|
||||
if (builder.requiresToBuilder) {
|
||||
val toBuilderFunction = classDescriptor.createFunction(
|
||||
Name.identifier(TO_BUILDER),
|
||||
valueParameters = emptyList(),
|
||||
returnType = builderDescriptor.defaultType,
|
||||
modality = Modality.FINAL,
|
||||
visibility = visibility
|
||||
)
|
||||
partsBuilder.addMethod(toBuilderFunction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun contributeToBuilderClass(
|
||||
builderClass: ClassDescriptor,
|
||||
constructingClass: ClassDescriptor,
|
||||
builder: Builder,
|
||||
partsBuilder: SyntheticPartsBuilder
|
||||
) {
|
||||
val constructor = builderClass.createJavaConstructor(valueParameters = emptyList(), JavaDescriptorVisibilities.PACKAGE_VISIBILITY)
|
||||
partsBuilder.addConstructor(constructor)
|
||||
|
||||
val visibility = builder.visibility.toDescriptorVisibility()
|
||||
|
||||
val buildFunction = builderClass.createFunction(
|
||||
Name.identifier(builder.buildMethodName),
|
||||
valueParameters = emptyList(),
|
||||
returnType = constructingClass.defaultType,
|
||||
visibility = visibility
|
||||
)
|
||||
partsBuilder.addMethod(buildFunction)
|
||||
|
||||
for (field in constructingClass.getJavaFields()) {
|
||||
createSetterMethod(builder, field, builderClass, partsBuilder)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSetterMethod(
|
||||
builder: Builder,
|
||||
field: PropertyDescriptor,
|
||||
builderClass: ClassDescriptor,
|
||||
partsBuilder: SyntheticPartsBuilder
|
||||
) {
|
||||
val prefix = builder.setterPrefix
|
||||
val fieldName = field.name
|
||||
val setterName = if (prefix.isNullOrBlank()) fieldName else Name.identifier("${prefix}${field.name.asString().capitalize()}")
|
||||
val setFunction = builderClass.createFunction(
|
||||
name = setterName,
|
||||
valueParameters = listOf(LombokValueParameter(fieldName, field.type)),
|
||||
returnType = builderClass.defaultType,
|
||||
modality = Modality.FINAL,
|
||||
visibility = builder.visibility.toDescriptorVisibility()
|
||||
)
|
||||
partsBuilder.addMethod(setFunction)
|
||||
}
|
||||
|
||||
private class BuilderData(val builder: Builder, val constructingClass: ClassDescriptor)
|
||||
}
|
||||
@@ -20,6 +20,11 @@ fun getAccessLevel(annotation: AnnotationDescriptor, field: String = "value"): A
|
||||
return AccessLevel.valueOf(value)
|
||||
}
|
||||
|
||||
@JvmName("getAccessLevelWithReceiver")
|
||||
fun AnnotationDescriptor.getAccessLevel(field: String = "value"): AccessLevel {
|
||||
return getAccessLevel(this, field)
|
||||
}
|
||||
|
||||
fun AnnotationDescriptor.getStringArgument(argumentName: String): String? {
|
||||
val argument = allValueArguments[Name.identifier(argumentName)]
|
||||
?: return null
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// FILE: User.java
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
public class User {
|
||||
@Builder.Default private int created = 0;
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
|
||||
|
||||
// FILE: test.kt
|
||||
|
||||
fun box(): String {
|
||||
val userBuilder = User.builder()
|
||||
.created(10)
|
||||
.name("John")
|
||||
.age(42)
|
||||
|
||||
val user = userBuilder.build()
|
||||
return if (user.created == 10 && user.name == "John" && user.age == 42) {
|
||||
"OK"
|
||||
} else {
|
||||
"Error: $user"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// FIR_IDENTICAL
|
||||
// FILE: User.java
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Builder(
|
||||
builderClassName = "SpecialUserBuilder",
|
||||
buildMethodName = "execute",
|
||||
builderMethodName = "createBuilder",
|
||||
toBuilder = true,
|
||||
access = AccessLevel.PACKAGE,
|
||||
setterPrefix = "set"
|
||||
)
|
||||
@Data
|
||||
public class User {
|
||||
@Builder.Default private int created = 0;
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
|
||||
|
||||
// FILE: test.kt
|
||||
|
||||
fun test() {
|
||||
val userBuilder: User.SpecialUserBuilder = User.createBuilder()
|
||||
val user = userBuilder
|
||||
.setCreated(10)
|
||||
.setName("John")
|
||||
.setAge(42)
|
||||
.execute()
|
||||
}
|
||||
|
||||
fun testToBuilder(user: User) {
|
||||
val userBuilder: User.SpecialUserBuilder = user.toBuilder()
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package
|
||||
|
||||
public fun </*0*/ T> assertEquals(/*0*/ a: T, /*1*/ b: T): kotlin.Unit
|
||||
public fun test(): kotlin.Unit
|
||||
public fun testToBuilder(/*0*/ user: User): kotlin.Unit
|
||||
|
||||
@lombok.Builder(access = AccessLevel.PACKAGE, buildMethodName = "execute", builderClassName = "SpecialUserBuilder", builderMethodName = "createBuilder", setterPrefix = "set", toBuilder = true) @lombok.Data public open class User {
|
||||
public constructor User()
|
||||
private final var age: kotlin.Int
|
||||
@lombok.Builder.Default private final var created: kotlin.Int
|
||||
private final var name: kotlin.String!
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open /*synthesized*/ fun getAge(): kotlin.Int
|
||||
public open /*synthesized*/ fun getCreated(): kotlin.Int
|
||||
public open /*synthesized*/ fun getName(): kotlin.String!
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open /*synthesized*/ fun setAge(/*0*/ age: kotlin.Int): kotlin.Unit
|
||||
public open /*synthesized*/ fun setCreated(/*0*/ created: kotlin.Int): kotlin.Unit
|
||||
public open /*synthesized*/ fun setName(/*0*/ name: kotlin.String!): kotlin.Unit
|
||||
public/*package*/ final /*synthesized*/ fun toBuilder(): User.SpecialUserBuilder
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
// Static members
|
||||
public/*package*/ final /*synthesized*/ fun createBuilder(): User.SpecialUserBuilder
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// FILE: User.java
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
public class User {
|
||||
private String name;
|
||||
}
|
||||
|
||||
|
||||
// FILE: test.kt
|
||||
fun test() {
|
||||
val userBuilder: User.SpecialUserBuilder = User.builder()
|
||||
}
|
||||
|
||||
// FILE: lombok.config
|
||||
lombok.builder.className=Special*Builder
|
||||
@@ -0,0 +1,17 @@
|
||||
package
|
||||
|
||||
public fun </*0*/ T> assertEquals(/*0*/ a: T, /*1*/ b: T): kotlin.Unit
|
||||
public fun test(): kotlin.Unit
|
||||
|
||||
@lombok.Builder @lombok.Data public open class User {
|
||||
public constructor User()
|
||||
private final var name: kotlin.String!
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public open /*synthesized*/ fun getName(): kotlin.String!
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open /*synthesized*/ fun setName(/*0*/ name: kotlin.String!): kotlin.Unit
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
|
||||
// Static members
|
||||
public final /*synthesized*/ fun builder(): User.SpecialUserBuilder
|
||||
}
|
||||
Generated
+6
@@ -49,6 +49,12 @@ public class BlackBoxCodegenTestForLombokGenerated extends AbstractBlackBoxCodeg
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/lombok/testData/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("builder.kt")
|
||||
public void testBuilder() throws Exception {
|
||||
runTest("plugins/lombok/testData/box/builder.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("configAccessors.kt")
|
||||
public void testConfigAccessors() throws Exception {
|
||||
|
||||
Generated
+12
@@ -36,6 +36,18 @@ public class DiagnosticTestForLombokGenerated extends AbstractDiagnosticTestForL
|
||||
runTest("plugins/lombok/testData/diagnostics/annotationTypes.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("builderAnnotationArguments.kt")
|
||||
public void testBuilderAnnotationArguments() throws Exception {
|
||||
runTest("plugins/lombok/testData/diagnostics/builderAnnotationArguments.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("builderConfig.kt")
|
||||
public void testBuilderConfig() throws Exception {
|
||||
runTest("plugins/lombok/testData/diagnostics/builderConfig.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("clashAccessors.kt")
|
||||
public void testClashAccessors() throws Exception {
|
||||
|
||||
Generated
+6
@@ -49,6 +49,12 @@ public class FirBlackBoxCodegenTestForLombokGenerated extends AbstractFirBlackBo
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/lombok/testData/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("builder.kt")
|
||||
public void testBuilder() throws Exception {
|
||||
runTest("plugins/lombok/testData/box/builder.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("configAccessors.kt")
|
||||
public void testConfigAccessors() throws Exception {
|
||||
|
||||
Generated
+12
@@ -36,6 +36,18 @@ public class FirDiagnosticTestForLombokGenerated extends AbstractFirDiagnosticTe
|
||||
runTest("plugins/lombok/testData/diagnostics/annotationTypes.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("builderAnnotationArguments.kt")
|
||||
public void testBuilderAnnotationArguments() throws Exception {
|
||||
runTest("plugins/lombok/testData/diagnostics/builderAnnotationArguments.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("builderConfig.kt")
|
||||
public void testBuilderConfig() throws Exception {
|
||||
runTest("plugins/lombok/testData/diagnostics/builderConfig.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("clashAccessors.kt")
|
||||
public void testClashAccessors() throws Exception {
|
||||
|
||||
Generated
+6
@@ -49,6 +49,12 @@ public class IrBlackBoxCodegenTestForLombokGenerated extends AbstractIrBlackBoxC
|
||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/lombok/testData/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("builder.kt")
|
||||
public void testBuilder() throws Exception {
|
||||
runTest("plugins/lombok/testData/box/builder.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("configAccessors.kt")
|
||||
public void testConfigAccessors() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user