mirror of
https://github.com/apple/pkl.git
synced 2026-01-16 08:26:56 +01:00
codegen-java: Support not annotating constructor parameters (#792)
Motivation: Spring Boot configuration classes neither require nor benefit from annotating constructor parameters with their name. The same is true for pkl-config-java configuration classes compiled with `-parameter`. Changes: - Change CLI parameter `--params-annotation` to accept a `none` value. This is recommended in https://clig.dev/#arguments-and-flags and is how the `-F` parameter of the `ssh` command works. - Change `paramsAnnotation` property in Gradle plugin, CliJavaCodeGeneratorOptions, and JavaCodegenOptions as follows: - Change meaning of `null` from "generate org.pkl.java.config.mapper.Named annotations" to "do not generate annotations". This is a breaking change (only) affecting users who explicitly set the property to `null` instead of omitting it. - Change property default from `null` to: `null` if `generateSpringBootConfig` is `true` and `org.pkl.java.config.mapper.Named` otherwise - add tests - update docs of this and other codegen options Result: Generated code does not contain unnecessary annotations.
This commit is contained in:
@@ -35,25 +35,35 @@ data class CliJavaCodeGeneratorOptions(
|
||||
*/
|
||||
val generateGetters: Boolean = false,
|
||||
|
||||
/** Whether to generate Javadoc based on doc comments for Pkl modules, classes, and properties. */
|
||||
/** Whether to preserve Pkl doc comments by generating corresponding Javadoc comments. */
|
||||
val generateJavadoc: Boolean = false,
|
||||
|
||||
/** Whether to generate config classes for use with Spring Boot. */
|
||||
val generateSpringBootConfig: Boolean = false,
|
||||
|
||||
/**
|
||||
* Fully qualified name of the annotation to use on constructor parameters. If this options is not
|
||||
* set, [org.pkl.config.java.mapper.Named] will be used.
|
||||
* Fully qualified name of the annotation type to use for annotating constructor parameters with
|
||||
* their name.
|
||||
*
|
||||
* The specified annotation type must have a `value` parameter of type [java.lang.String] or the
|
||||
* generated code may not compile.
|
||||
*
|
||||
* If set to `null`, constructor parameters are not annotated. The default value is `null` if
|
||||
* [generateSpringBootConfig] is `true` and `"org.pkl.config.java.mapper.Named"` otherwise.
|
||||
*/
|
||||
val paramsAnnotation: String? = null,
|
||||
val paramsAnnotation: String? =
|
||||
if (generateSpringBootConfig) null else "org.pkl.config.java.mapper.Named",
|
||||
|
||||
/**
|
||||
* Fully qualified name of the annotation to use on non-null properties. If this option is not
|
||||
* set, [org.pkl.config.java.mapper.NonNull] will be used.
|
||||
* Fully qualified name of the annotation type to use for annotating non-null types.
|
||||
*
|
||||
* The specified annotation type must have a [java.lang.annotation.Target] of
|
||||
* [java.lang.annotation.ElementType.TYPE_USE] or the generated code may not compile. If set to
|
||||
* `null`, [org.pkl.config.java.mapper.NonNull] will be used.
|
||||
*/
|
||||
val nonNullAnnotation: String? = null,
|
||||
|
||||
/** Whether to make generated classes implement [java.io.Serializable] */
|
||||
/** Whether to generate classes that implement [java.io.Serializable]. */
|
||||
val implementSerializable: Boolean = false,
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,25 +54,35 @@ data class JavaCodeGeneratorOptions(
|
||||
*/
|
||||
val generateGetters: Boolean = false,
|
||||
|
||||
/** Whether to generate Javadoc based on doc comments for Pkl modules, classes, and properties. */
|
||||
/** Whether to preserve Pkl doc comments by generating corresponding Javadoc comments. */
|
||||
val generateJavadoc: Boolean = false,
|
||||
|
||||
/** Whether to generate config classes for use with Spring Boot. */
|
||||
val generateSpringBootConfig: Boolean = false,
|
||||
|
||||
/**
|
||||
* Fully qualified name of the annotation to use on constructor parameters. If this options is not
|
||||
* set, [org.pkl.config.java.mapper.Named] will be used.
|
||||
* Fully qualified name of the annotation type to use for annotating constructor parameters with
|
||||
* their name.
|
||||
*
|
||||
* The specified annotation type must have a `value` parameter of type [java.lang.String] or the
|
||||
* generated code may not compile.
|
||||
*
|
||||
* If set to `null`, constructor parameters are not annotated. The default value is `null` if
|
||||
* [generateSpringBootConfig] is `true` and `"org.pkl.config.java.mapper.Named"` otherwise.
|
||||
*/
|
||||
val paramsAnnotation: String? = null,
|
||||
val paramsAnnotation: String? =
|
||||
if (generateSpringBootConfig) null else "org.pkl.config.java.mapper.Named",
|
||||
|
||||
/**
|
||||
* Fully qualified name of the annotation to use on non-null properties. If this option is not
|
||||
* set, [org.pkl.config.java.mapper.NonNull] will be used.
|
||||
* Fully qualified name of the annotation type to use for annotating non-null types.
|
||||
*
|
||||
* The specified annotation type must have a [java.lang.annotation.Target] of
|
||||
* [java.lang.annotation.ElementType.TYPE_USE] or the generated code may not compile. If set to
|
||||
* `null`, [org.pkl.config.java.mapper.NonNull] will be used.
|
||||
*/
|
||||
val nonNullAnnotation: String? = null,
|
||||
|
||||
/** Whether to make generated classes implement [java.io.Serializable] */
|
||||
/** Whether to generate classes that implement [java.io.Serializable]. */
|
||||
val implementSerializable: Boolean = false,
|
||||
|
||||
/**
|
||||
@@ -233,15 +243,15 @@ class JavaCodeGenerator(
|
||||
propJavaName: String,
|
||||
property: PClass.Property
|
||||
) {
|
||||
builder.addParameter(
|
||||
ParameterSpec.builder(property.type.toJavaPoetName(), propJavaName)
|
||||
.addAnnotation(
|
||||
AnnotationSpec.builder(namedAnnotationName)
|
||||
.addMember("value", "\$S", property.simpleName)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
val paramBuilder = ParameterSpec.builder(property.type.toJavaPoetName(), propJavaName)
|
||||
if (paramsAnnotationName != null) {
|
||||
paramBuilder.addAnnotation(
|
||||
AnnotationSpec.builder(paramsAnnotationName)
|
||||
.addMember("value", "\$S", property.simpleName)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
builder.addParameter(paramBuilder.build())
|
||||
}
|
||||
|
||||
fun generateConstructor(isInstantiable: Boolean): MethodSpec {
|
||||
@@ -671,12 +681,8 @@ class JavaCodeGenerator(
|
||||
return builder
|
||||
}
|
||||
|
||||
private val namedAnnotationName =
|
||||
if (codegenOptions.paramsAnnotation != null) {
|
||||
toClassName(codegenOptions.paramsAnnotation)
|
||||
} else {
|
||||
ClassName.get("org.pkl.config.java.mapper", "Named")
|
||||
}
|
||||
private val paramsAnnotationName: ClassName? =
|
||||
codegenOptions.paramsAnnotation?.let { toClassName(it) }
|
||||
|
||||
private fun appendPropertyMethod() =
|
||||
MethodSpec.methodBuilder("appendProperty")
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
|
||||
package org.pkl.codegen.java
|
||||
|
||||
import com.github.ajalt.clikt.parameters.options.associate
|
||||
import com.github.ajalt.clikt.parameters.options.default
|
||||
import com.github.ajalt.clikt.parameters.options.flag
|
||||
import com.github.ajalt.clikt.parameters.options.option
|
||||
import com.github.ajalt.clikt.parameters.options.*
|
||||
import com.github.ajalt.clikt.parameters.types.path
|
||||
import java.nio.file.Path
|
||||
import org.pkl.commons.cli.CliBaseOptions
|
||||
@@ -71,33 +68,37 @@ class PklJavaCodegenCommand :
|
||||
private val generateJavadoc: Boolean by
|
||||
option(
|
||||
names = arrayOf("--generate-javadoc"),
|
||||
help =
|
||||
"Whether to generate Javadoc based on doc comments " +
|
||||
"for Pkl modules, classes, and properties."
|
||||
help = "Whether to preserve Pkl doc comments by generating corresponding Javadoc comments."
|
||||
)
|
||||
.flag()
|
||||
|
||||
private val generateSpringboot: Boolean by
|
||||
private val generateSpringBoot: Boolean by
|
||||
option(
|
||||
names = arrayOf("--generate-spring-boot"),
|
||||
help = "Whether to generate config classes for use with Spring boot."
|
||||
help = "Whether to generate config classes for use with Spring Boot."
|
||||
)
|
||||
.flag()
|
||||
|
||||
private val paramsAnnotation: String? by
|
||||
private val paramsAnnotation: String by
|
||||
option(
|
||||
names = arrayOf("--params-annotation"),
|
||||
help = "Fully qualified name of the annotation to use on constructor parameters."
|
||||
)
|
||||
names = arrayOf("--params-annotation"),
|
||||
help =
|
||||
"Fully qualified name of the annotation type to use for annotating constructor parameters with their name."
|
||||
)
|
||||
.defaultLazy(
|
||||
"`none` if `--generate-spring-boot` is set, `org.pkl.config.java.mapper.Named` otherwise"
|
||||
) {
|
||||
if (generateSpringBoot) "none" else "org.pkl.config.java.mapper.Named"
|
||||
}
|
||||
|
||||
private val nonNullAnnotation: String? by
|
||||
option(
|
||||
names = arrayOf("--non-null-annotation"),
|
||||
help =
|
||||
"""
|
||||
Fully qualified named of the annotation class to use for non-null types.
|
||||
This annotation is required to have `java.lang.annotation.ElementType.TYPE_USE` as a `@Target`
|
||||
or it may generate code that does not compile.
|
||||
Fully qualified name of the annotation type to use for annotating non-null types.
|
||||
The specified annotation type must be annotated with `@java.lang.annotation.Target(ElementType.TYPE_USE)`
|
||||
or the generated code may not compile.
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
@@ -105,7 +106,7 @@ class PklJavaCodegenCommand :
|
||||
private val implementSerializable: Boolean by
|
||||
option(
|
||||
names = arrayOf("--implement-serializable"),
|
||||
help = "Whether to make generated classes implement java.io.Serializable."
|
||||
help = "Whether to generate classes that implement java.io.Serializable."
|
||||
)
|
||||
.flag()
|
||||
|
||||
@@ -117,7 +118,7 @@ class PklJavaCodegenCommand :
|
||||
"""
|
||||
Replace a prefix in the names of the generated Java classes (repeatable).
|
||||
By default, the names of generated classes are derived from the Pkl module names.
|
||||
With this option, you can override the modify the default names, renaming entire
|
||||
With this option, you can override or modify the default names, renaming entire
|
||||
classes or just their packages.
|
||||
"""
|
||||
.trimIndent()
|
||||
@@ -132,8 +133,8 @@ class PklJavaCodegenCommand :
|
||||
indent = indent,
|
||||
generateGetters = generateGetters,
|
||||
generateJavadoc = generateJavadoc,
|
||||
generateSpringBootConfig = generateSpringboot,
|
||||
paramsAnnotation = paramsAnnotation,
|
||||
generateSpringBootConfig = generateSpringBoot,
|
||||
paramsAnnotation = if (paramsAnnotation == "none") null else paramsAnnotation,
|
||||
nonNullAnnotation = nonNullAnnotation,
|
||||
implementSerializable = implementSerializable,
|
||||
renames = renames
|
||||
|
||||
@@ -104,26 +104,10 @@ class JavaCodeGeneratorTest {
|
||||
|
||||
private fun generateJavaCode(
|
||||
pklCode: String,
|
||||
generateGetters: Boolean = false,
|
||||
generateJavadoc: Boolean = false,
|
||||
generateSpringBootConfig: Boolean = false,
|
||||
nonNullAnnotation: String? = null,
|
||||
implementSerializable: Boolean = false,
|
||||
renames: Map<String, String> = emptyMap()
|
||||
options: JavaCodeGeneratorOptions = JavaCodeGeneratorOptions()
|
||||
): JavaSourceCode {
|
||||
val module = Evaluator.preconfigured().evaluateSchema(text(pklCode))
|
||||
val generator =
|
||||
JavaCodeGenerator(
|
||||
module,
|
||||
JavaCodeGeneratorOptions(
|
||||
generateGetters = generateGetters,
|
||||
generateJavadoc = generateJavadoc,
|
||||
generateSpringBootConfig = generateSpringBootConfig,
|
||||
nonNullAnnotation = nonNullAnnotation,
|
||||
implementSerializable = implementSerializable,
|
||||
renames = renames
|
||||
)
|
||||
)
|
||||
val generator = JavaCodeGenerator(module, options)
|
||||
return JavaSourceCode(generator.javaFile)
|
||||
}
|
||||
}
|
||||
@@ -235,7 +219,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateJavadoc = true
|
||||
JavaCodeGeneratorOptions(generateJavadoc = true)
|
||||
)
|
||||
assertThat(javaCode)
|
||||
.contains(
|
||||
@@ -274,8 +258,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateGetters = true,
|
||||
generateJavadoc = true
|
||||
JavaCodeGeneratorOptions(generateGetters = true, generateJavadoc = true)
|
||||
)
|
||||
assertThat(javaCode)
|
||||
.contains(
|
||||
@@ -322,7 +305,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateJavadoc = true
|
||||
JavaCodeGeneratorOptions(generateJavadoc = true)
|
||||
)
|
||||
assertThat(javaCode)
|
||||
.contains(
|
||||
@@ -349,7 +332,7 @@ class JavaCodeGeneratorTest {
|
||||
propertyInDeprecatedModuleClass : Int = 42
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateJavadoc = generateJavadoc
|
||||
JavaCodeGeneratorOptions(generateJavadoc = generateJavadoc)
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
@@ -389,7 +372,7 @@ class JavaCodeGeneratorTest {
|
||||
"""
|
||||
.trimIndent(),
|
||||
// no message, so no Javadoc, regardless of flag
|
||||
generateJavadoc = generateJavadoc
|
||||
JavaCodeGeneratorOptions(generateJavadoc = generateJavadoc)
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
@@ -424,7 +407,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateGetters = true
|
||||
JavaCodeGeneratorOptions(generateGetters = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
@@ -513,7 +496,7 @@ class JavaCodeGeneratorTest {
|
||||
@Deprecated { message = "property is deprecated" }
|
||||
deprecatedProperty: Int
|
||||
""",
|
||||
generateJavadoc = true
|
||||
JavaCodeGeneratorOptions(generateJavadoc = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
@@ -735,7 +718,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateGetters = true
|
||||
JavaCodeGeneratorOptions(generateGetters = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
@@ -889,7 +872,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateGetters = true
|
||||
JavaCodeGeneratorOptions(generateGetters = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode).compilesSuccessfully().isEqualToResourceFile("GenerateGetters.jva")
|
||||
@@ -1002,7 +985,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateJavadoc = true
|
||||
JavaCodeGeneratorOptions(generateJavadoc = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode).compilesSuccessfully().isEqualToResourceFile("Javadoc.jva")
|
||||
@@ -1026,8 +1009,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateGetters = true,
|
||||
generateJavadoc = true
|
||||
JavaCodeGeneratorOptions(generateGetters = true, generateJavadoc = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
@@ -1169,7 +1151,7 @@ class JavaCodeGeneratorTest {
|
||||
foo: String
|
||||
"""
|
||||
.trimIndent(),
|
||||
nonNullAnnotation = "com.example.Annotations\$NonNull"
|
||||
JavaCodeGeneratorOptions(nonNullAnnotation = "com.example.Annotations\$NonNull")
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
@@ -1463,6 +1445,40 @@ class JavaCodeGeneratorTest {
|
||||
.contains("public final @NonNull String v6;")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `custom constructor parameter annotation`() {
|
||||
val javaCode =
|
||||
generateJavaCode(
|
||||
"""
|
||||
module my.mod
|
||||
|
||||
name: String
|
||||
"""
|
||||
.trimIndent(),
|
||||
JavaCodeGeneratorOptions(paramsAnnotation = "org.project.MyAnnotation")
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
.contains("import org.project.MyAnnotation;")
|
||||
.contains("public Mod(@MyAnnotation(\"name\") @NonNull String name)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no constructor parameter annotation`() {
|
||||
val javaCode =
|
||||
generateJavaCode(
|
||||
"""
|
||||
module my.mod
|
||||
|
||||
name: String
|
||||
"""
|
||||
.trimIndent(),
|
||||
JavaCodeGeneratorOptions(paramsAnnotation = null)
|
||||
)
|
||||
|
||||
assertThat(javaCode).contains("public Mod(@NonNull String name)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `spring boot config`() {
|
||||
val javaCode =
|
||||
@@ -1478,7 +1494,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateSpringBootConfig = true
|
||||
JavaCodeGeneratorOptions(generateSpringBootConfig = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
@@ -1511,6 +1527,7 @@ class JavaCodeGeneratorTest {
|
||||
.trimMargin()
|
||||
)
|
||||
.doesNotContain("@ConstructorBinding")
|
||||
.doesNotContain("@Named")
|
||||
|
||||
// not worthwhile to add spring & spring boot dependency just so that this test can compile
|
||||
// their annotations
|
||||
@@ -1765,7 +1782,7 @@ class JavaCodeGeneratorTest {
|
||||
typealias Direction = "north"|"east"|"south"|"west"
|
||||
"""
|
||||
.trimIndent(),
|
||||
implementSerializable = true
|
||||
JavaCodeGeneratorOptions(implementSerializable = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
@@ -1846,7 +1863,7 @@ class JavaCodeGeneratorTest {
|
||||
abstract class Foo { str: String }
|
||||
"""
|
||||
.trimIndent(),
|
||||
implementSerializable = true
|
||||
JavaCodeGeneratorOptions(implementSerializable = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode).doesNotContain("Serializable")
|
||||
@@ -1857,7 +1874,7 @@ class JavaCodeGeneratorTest {
|
||||
module my.mod
|
||||
"""
|
||||
.trimIndent(),
|
||||
implementSerializable = true
|
||||
JavaCodeGeneratorOptions(implementSerializable = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode).doesNotContain("Serializable")
|
||||
@@ -1874,7 +1891,7 @@ class JavaCodeGeneratorTest {
|
||||
class Address { city: String }
|
||||
"""
|
||||
.trimIndent(),
|
||||
implementSerializable = true
|
||||
JavaCodeGeneratorOptions(implementSerializable = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
@@ -1960,7 +1977,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
generateGetters = true
|
||||
JavaCodeGeneratorOptions(generateGetters = true)
|
||||
)
|
||||
|
||||
assertThat(javaCode)
|
||||
|
||||
Reference in New Issue
Block a user