diff --git a/plugins/lombok/lombok-compiler-plugin/README.md b/plugins/lombok/lombok-compiler-plugin/README.md index 3af9af34056..52b95d82452 100644 --- a/plugins/lombok/lombok-compiler-plugin/README.md +++ b/plugins/lombok/lombok-compiler-plugin/README.md @@ -26,7 +26,7 @@ Features support: [~] [@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor](https://projectlombok.org/features/constructor) - [x] @NoArgsConstructor - [x] @AllArgsConstructor - - [ ] @RequiredArgsConstructor + - [x] @RequiredArgsConstructor [ ] [@Data](https://projectlombok.org/features/Data) diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/LombokSyntheticJavaPartsProvider.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/LombokSyntheticJavaPartsProvider.kt index 37aac5d08a7..c43ae4e48b3 100644 --- a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/LombokSyntheticJavaPartsProvider.kt +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/LombokSyntheticJavaPartsProvider.kt @@ -26,7 +26,8 @@ class LombokSyntheticJavaPartsProvider(private val config: LombokConfig) : Synth SetterProcessor(config), WithProcessor(config), NoArgsConstructorProcessor(), - AllArgsConstructorProcessor() + AllArgsConstructorProcessor(), + RequiredArgsConstructorProcessor() ) private val partsCache: MutableMap = WeakHashMap() diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/config/annotationConfig.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/config/annotationConfig.kt index f0dd55c9010..86ce4448fea 100644 --- a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/config/annotationConfig.kt +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/config/annotationConfig.kt @@ -72,31 +72,51 @@ data class With(val visibility: DescriptorVisibility) { } } -data class NoArgsConstructor( - val visibility: DescriptorVisibility, +interface ConstructorAnnotation { + val visibility: DescriptorVisibility val staticName: String? -) { +} + +data class NoArgsConstructor( + override val visibility: DescriptorVisibility, + override val staticName: String? +) : ConstructorAnnotation { companion object : AnnotationCompanion() { override val name: FqName = LombokNames.NO_ARGS_CONSTRUCTOR override fun extract(annotation: AnnotationDescriptor): NoArgsConstructor = NoArgsConstructor( - visibility = getVisibility(annotation), + visibility = getVisibility(annotation, "access"), staticName = annotation.getNonBlankStringArgument("staticName") ) } } data class AllArgsConstructor( - val visibility: DescriptorVisibility, - val staticName: String? -) { + override val visibility: DescriptorVisibility, + override val staticName: String? +) : ConstructorAnnotation { companion object : AnnotationCompanion() { override val name: FqName = LombokNames.ALL_ARGS_CONSTRUCTOR override fun extract(annotation: AnnotationDescriptor): AllArgsConstructor = AllArgsConstructor( - visibility = getVisibility(annotation), + visibility = getVisibility(annotation, "access"), + staticName = annotation.getNonBlankStringArgument("staticName") + ) + } +} + +data class RequiredArgsConstructor( + override val visibility: DescriptorVisibility, + override val staticName: String? +) : ConstructorAnnotation { + companion object : AnnotationCompanion() { + override val name: FqName = LombokNames.REQUIRED_ARGS_CONSTRUCTOR + + override fun extract(annotation: AnnotationDescriptor): RequiredArgsConstructor = + RequiredArgsConstructor( + visibility = getVisibility(annotation, "access"), staticName = annotation.getNonBlankStringArgument("staticName") ) } diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/AbstractConstructorProcessor.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/AbstractConstructorProcessor.kt new file mode 100644 index 00000000000..0fb95c5977f --- /dev/null +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/AbstractConstructorProcessor.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2021 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.ClassDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl +import org.jetbrains.kotlin.lombok.config.AllArgsConstructor +import org.jetbrains.kotlin.lombok.config.AnnotationCompanion +import org.jetbrains.kotlin.lombok.config.ConstructorAnnotation +import org.jetbrains.kotlin.lombok.utils.ValueParameter +import org.jetbrains.kotlin.lombok.utils.createConstructor +import org.jetbrains.kotlin.lombok.utils.createFunction +import org.jetbrains.kotlin.lombok.utils.getJavaFields +import org.jetbrains.kotlin.name.Name + +abstract class AbstractConstructorProcessor( + private val annotationCompanion: AnnotationCompanion +) : Processor { + + override fun contribute(classDescriptor: ClassDescriptor, jClass: JavaClassImpl): Parts { + val valueParameters = getPropertiesForParameters(classDescriptor).map { property -> + ValueParameter(property.name, property.type) + } + + val result = annotationCompanion.getOrNull(classDescriptor)?.let { annotation -> + if (annotation.staticName == null) { + val constructor = classDescriptor.createConstructor( + valueParameters = valueParameters, + visibility = annotation.visibility + ) + Parts(constructors = listOfNotNull(constructor)) + } else { + val function = classDescriptor.createFunction( + Name.identifier(annotation.staticName!!), + valueParameters, + classDescriptor.defaultType, + visibility = annotation.visibility, + receiver = null + ) + Parts(staticFunctions = listOf(function)) + } + } + return result ?: Parts.Empty + } + + protected abstract fun getPropertiesForParameters(classDescriptor: ClassDescriptor): List + + +} diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/AllArgsConstructorProcessor.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/AllArgsConstructorProcessor.kt index cdad5931f2e..cfba5d2e227 100644 --- a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/AllArgsConstructorProcessor.kt +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/AllArgsConstructorProcessor.kt @@ -6,41 +6,12 @@ package org.jetbrains.kotlin.lombok.processor import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl +import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.lombok.config.AllArgsConstructor -import org.jetbrains.kotlin.lombok.utils.ValueParameter -import org.jetbrains.kotlin.lombok.utils.createConstructor -import org.jetbrains.kotlin.lombok.utils.createFunction import org.jetbrains.kotlin.lombok.utils.getJavaFields -import org.jetbrains.kotlin.name.Name -class AllArgsConstructorProcessor : Processor { +class AllArgsConstructorProcessor : AbstractConstructorProcessor(AllArgsConstructor) { - override fun contribute(classDescriptor: ClassDescriptor, jClass: JavaClassImpl): Parts { - - val valueParameters = classDescriptor.getJavaFields().map { property -> - ValueParameter(property.name, property.type) - } - - val result = AllArgsConstructor.getOrNull(classDescriptor)?.let { annotation -> - if (annotation.staticName == null) { - val constructor = classDescriptor.createConstructor( - valueParameters = valueParameters, - visibility = annotation.visibility - ) - Parts(constructors = listOfNotNull(constructor)) - } else { - val function = classDescriptor.createFunction( - Name.identifier(annotation.staticName), - valueParameters, - classDescriptor.defaultType, - visibility = annotation.visibility, - receiver = null - ) - Parts(staticFunctions = listOf(function)) - } - } - return result ?: Parts.Empty - } + override fun getPropertiesForParameters(classDescriptor: ClassDescriptor): List = classDescriptor.getJavaFields() } diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/NoArgsConstructorProcessor.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/NoArgsConstructorProcessor.kt index a9054689552..75bdce22982 100644 --- a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/NoArgsConstructorProcessor.kt +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/NoArgsConstructorProcessor.kt @@ -6,34 +6,10 @@ package org.jetbrains.kotlin.lombok.processor import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl +import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.lombok.config.NoArgsConstructor -import org.jetbrains.kotlin.lombok.utils.createConstructor -import org.jetbrains.kotlin.lombok.utils.createFunction -import org.jetbrains.kotlin.name.Name -class NoArgsConstructorProcessor : Processor { - - override fun contribute(classDescriptor: ClassDescriptor, jClass: JavaClassImpl): Parts { - val result = NoArgsConstructor.getOrNull(classDescriptor)?.let { annotation -> - if (annotation.staticName == null) { - val constructor = classDescriptor.createConstructor( - valueParameters = emptyList(), - visibility = annotation.visibility - ) - Parts(constructors = listOfNotNull(constructor)) - } else { - val function = classDescriptor.createFunction( - Name.identifier(annotation.staticName), - emptyList(), - classDescriptor.defaultType, - visibility = annotation.visibility, - receiver = null - ) - Parts(staticFunctions = listOf(function)) - } - } - return result ?: Parts.Empty - } +class NoArgsConstructorProcessor : AbstractConstructorProcessor(NoArgsConstructor) { + override fun getPropertiesForParameters(classDescriptor: ClassDescriptor): List = emptyList() } diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/RequiredArgsConstructorProcessor.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/RequiredArgsConstructorProcessor.kt new file mode 100644 index 00000000000..674b4686d2f --- /dev/null +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/processor/RequiredArgsConstructorProcessor.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2021 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 com.intellij.psi.PsiField +import com.intellij.psi.PsiModifier +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.lombok.config.RequiredArgsConstructor +import org.jetbrains.kotlin.lombok.utils.LombokNames +import org.jetbrains.kotlin.lombok.utils.getJavaFields +import org.jetbrains.kotlin.resolve.source.getPsi + +class RequiredArgsConstructorProcessor : AbstractConstructorProcessor(RequiredArgsConstructor) { + + override fun getPropertiesForParameters(classDescriptor: ClassDescriptor): List = + classDescriptor.getJavaFields().filter(this::isFieldRequired) + + private fun isFieldRequired(field: PropertyDescriptor): Boolean { + val psi = field.source.getPsi()!! as PsiField + + val final = psi.modifierList?.hasModifierProperty(PsiModifier.FINAL) ?: false || + field.annotations.any { annotation -> LombokNames.NON_NULL_ANNOTATIONS.contains(annotation.fqName) } + + return final && !psi.hasInitializer() + } + +} diff --git a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/LombokNames.kt b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/LombokNames.kt index ea1122252c5..22d29aeea89 100644 --- a/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/LombokNames.kt +++ b/plugins/lombok/lombok-compiler-plugin/src/org/jetbrains/kotlin/lombok/utils/LombokNames.kt @@ -13,7 +13,27 @@ object LombokNames { val GETTER = FqName("lombok.Getter") val SETTER = FqName("lombok.Setter") val WITH = FqName("lombok.With") + val VALUE = FqName("lombok.lombok.Value") val NO_ARGS_CONSTRUCTOR = FqName("lombok.NoArgsConstructor") val ALL_ARGS_CONSTRUCTOR = FqName("lombok.AllArgsConstructor") + val REQUIRED_ARGS_CONSTRUCTOR = FqName("lombok.RequiredArgsConstructor") + + + //taken from idea lombok plugin + val NON_NULL_ANNOTATIONS = listOf( + "androidx.annotation.NonNull", + "android.support.annotation.NonNull", + "com.sun.istack.internal.NotNull", + "edu.umd.cs.findbugs.annotations.NonNull", + "javax.annotation.Nonnull", + "lombok.NonNull", + "org.checkerframework.checker.nullness.qual.NonNull", + "org.eclipse.jdt.annotation.NonNull", + "org.eclipse.jgit.annotations.NonNull", + "org.jetbrains.annotations.NotNull", + "org.jmlspecs.annotation.NonNull", + "org.netbeans.api.annotations.common.NonNull", + "org.springframework.lang.NonNull" + ).map { FqName(it) }.toSet() } diff --git a/plugins/lombok/lombok-compiler-plugin/testData/compile/requiredArgsConstructor.kt b/plugins/lombok/lombok-compiler-plugin/testData/compile/requiredArgsConstructor.kt new file mode 100644 index 00000000000..b6b50a1069c --- /dev/null +++ b/plugins/lombok/lombok-compiler-plugin/testData/compile/requiredArgsConstructor.kt @@ -0,0 +1,40 @@ +//FILE: ConstructorExample.java + +import lombok.*; + +@RequiredArgsConstructor +public class ConstructorExample { + + //not required + @Getter @Setter private int age; + + //required final + @Getter @Setter private final String foo; + + //not required final, because has initializer + @Getter @Setter private final int bar = 234; + + //not required + @Getter(AccessLevel.PROTECTED) private String name; + + //required by annotation + @NonNull + private boolean otherField; + + //not required by annotation, because has initializer + @NonNull + private Long zzzz = 23L; + + static void javaUsage() { + ConstructorExample generated = new ConstructorExample("foo", true); + } +} + + +//FILE: test.kt + +object Test { + fun usage() { + val generated = ConstructorExample("foo", true) + } +} diff --git a/plugins/lombok/lombok-compiler-plugin/testData/compile/requiredArgsConstructorStatic.kt b/plugins/lombok/lombok-compiler-plugin/testData/compile/requiredArgsConstructorStatic.kt new file mode 100644 index 00000000000..085aae58686 --- /dev/null +++ b/plugins/lombok/lombok-compiler-plugin/testData/compile/requiredArgsConstructorStatic.kt @@ -0,0 +1,42 @@ +//FILE: ConstructorExample.java + +import lombok.*; + +@RequiredArgsConstructor(staticName = "build") +public class ConstructorExample { + + //not required + @Getter @Setter private int age; + + //required final + @Getter @Setter private final String foo; + + //not required final, because has initializer + @Getter @Setter private final int bar = 234; + + //not required + @Getter(AccessLevel.PROTECTED) private String name; + + //required by annotation + @NonNull + private boolean otherField; + + //not required by annotation, because has initializer + @NonNull + private Long zzzz = 23L; + + @NonNull Integer somethingElse; + + static void javaUsage() { + ConstructorExample generated = ConstructorExample.build("foo", true, 12); + } +} + + +//FILE: test.kt + +object Test { + fun usage() { + val generated = ConstructorExample.build("foo", true, 12) + } +} diff --git a/plugins/lombok/lombok-compiler-plugin/tests/org/jetbrains/kotlin/lombok/LombokCompileTestGenerated.java b/plugins/lombok/lombok-compiler-plugin/tests/org/jetbrains/kotlin/lombok/LombokCompileTestGenerated.java index 9f7a1709373..063e2b9f891 100644 --- a/plugins/lombok/lombok-compiler-plugin/tests/org/jetbrains/kotlin/lombok/LombokCompileTestGenerated.java +++ b/plugins/lombok/lombok-compiler-plugin/tests/org/jetbrains/kotlin/lombok/LombokCompileTestGenerated.java @@ -66,6 +66,16 @@ public class LombokCompileTestGenerated extends AbstractLombokCompileTest { runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/noArgsConstructorStatic.kt"); } + @TestMetadata("requiredArgsConstructor.kt") + public void testRequiredArgsConstructor() throws Exception { + runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/requiredArgsConstructor.kt"); + } + + @TestMetadata("requiredArgsConstructorStatic.kt") + public void testRequiredArgsConstructorStatic() throws Exception { + runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/requiredArgsConstructorStatic.kt"); + } + @TestMetadata("getters.kt") public void testGetters() throws Exception { runTest("plugins/lombok/lombok-compiler-plugin/testData/compile/getters.kt");