Add @Generated annotation to generated Java types (#1075)

JaCoCo automatically excludes methods and classes annotated with @Generated from the coverage reports. This is very important to us as generated code should not normally be included in the coverage report. We want to measure the coverage of the code that we actually wrote and maintain, not the code that was automatically generated by tools.

By introducing a property generatedAnnotation (default value false) one could enable writing @Generated on Java types to be generated.

Co-authored-by: Nullpointer <mike.schulze@tealium.com>
This commit is contained in:
André Rouél
2025-07-08 00:31:49 +02:00
committed by GitHub
parent dbf57280ba
commit 0973774a5d
11 changed files with 141 additions and 4 deletions

View File

@@ -29,6 +29,9 @@ data class CliJavaCodeGeneratorOptions(
/** The characters to use for indenting generated source code. */
val indent: String = " ",
/** Whether to add a <code>@Generated</code> annotation to the types to be generated. */
val generatedAnnotation: Boolean = false,
/**
* Whether to generate public getter methods and private/protected fields instead of public
* fields.
@@ -82,6 +85,7 @@ data class CliJavaCodeGeneratorOptions(
internal fun toJavaCodeGeneratorOptions() =
JavaCodeGeneratorOptions(
indent,
generatedAnnotation,
generateGetters,
generateJavadoc,
generateSpringBootConfig,

View File

@@ -47,6 +47,9 @@ data class JavaCodeGeneratorOptions(
/** The characters to use for indenting generated Java code. */
val indent: String = " ",
/** Whether to add a <code>@Generated</code> annotation to the types to be generated. */
val generatedAnnotation: Boolean = false,
/**
* Whether to generate public getter methods and protected final fields instead of public final
* fields.
@@ -560,6 +563,11 @@ class JavaCodeGenerator(
fun generateClass(): TypeSpec.Builder {
val builder =
TypeSpec.classBuilder(javaPoetClassName.simpleName()).addModifiers(Modifier.PUBLIC)
if (codegenOptions.generatedAnnotation) {
val name = ClassName.get("org.pkl.config.java", "Generated")
val generated = AnnotationSpec.builder(name).build()
builder.addAnnotation(generated)
}
// stateless final module classes are non-instantiable by choice
val isInstantiable =

View File

@@ -55,6 +55,13 @@ class PklJavaCodegenCommand : ModulesCommand(name = "pkl-codegen-java", helpLink
)
.default(defaults.indent)
private val generatedAnnotation: Boolean by
option(
names = arrayOf("--generated-annotation"),
help = "Whether to add a @Generated annotation to the types to be generated.",
)
.flag()
private val generateGetters: Boolean by
option(
names = arrayOf("--generate-getters"),
@@ -132,6 +139,7 @@ class PklJavaCodegenCommand : ModulesCommand(name = "pkl-codegen-java", helpLink
base = baseOptions.baseOptions(modules, projectOptions),
outputDir = outputDir,
indent = indent,
generatedAnnotation = generatedAnnotation,
generateGetters = generateGetters,
generateJavadoc = generateJavadoc,
generateSpringBootConfig = generateSpringBoot,

View File

@@ -859,6 +859,24 @@ class JavaCodeGeneratorTest {
assertThat(fooClass.declaredFields).allSatisfy(Consumer { it.name.startsWith("_") })
}
@Test
fun generatedAnnotation() {
val javaCode =
generateJavaCode(
"""
module my.mod
class GeneratedAnnotation {
test: Boolean = true
}
"""
.trimIndent(),
JavaCodeGeneratorOptions(generatedAnnotation = true),
)
assertThat(javaCode).compilesSuccessfully().isEqualToResourceFile("GeneratedAnnotation.jva")
}
@Test
fun getters() {
val javaCode =

View File

@@ -0,0 +1,63 @@
package my;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.Objects;
import org.pkl.config.java.Generated;
import org.pkl.config.java.mapper.Named;
@Generated
public final class Mod {
private Mod() {
}
private static void appendProperty(StringBuilder builder, String name, Object value) {
builder.append("\n ").append(name).append(" = ");
String[] lines = Objects.toString(value).split("\n");
builder.append(lines[0]);
for (int i = 1; i < lines.length; i++) {
builder.append("\n ").append(lines[i]);
}
}
@Generated
public static final class GeneratedAnnotation {
public final boolean test;
public GeneratedAnnotation(@Named("test") boolean test) {
this.test = test;
}
public GeneratedAnnotation withTest(boolean test) {
return new GeneratedAnnotation(test);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
GeneratedAnnotation other = (GeneratedAnnotation) obj;
if (!Objects.equals(this.test, other.test)) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + Objects.hashCode(this.test);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(100);
builder.append(GeneratedAnnotation.class.getSimpleName()).append(" {");
appendProperty(builder, "test", this.test);
builder.append("\n}");
return builder.toString();
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.config.java;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Indicates that the annotated class was generated by Pkl. */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Generated {}

View File

@@ -190,6 +190,7 @@ public class PklPlugin implements Plugin<Project> {
configureBaseSpec(spec);
configureCodeGenSpec(spec);
spec.getAddGeneratedAnnotation().convention(false);
spec.getGenerateGetters().convention(false);
spec.getGenerateJavadoc().convention(false);
// Not using `convention()` so that users can disable generation of
@@ -206,6 +207,7 @@ public class PklPlugin implements Plugin<Project> {
.configure(
task -> {
configureCodeGenTask(task, spec);
task.getGeneratedAnnotation().set(spec.getAddGeneratedAnnotation());
task.getGenerateGetters().set(spec.getGenerateGetters());
task.getGenerateJavadoc().set(spec.getGenerateJavadoc());
task.getParamsAnnotation().set(spec.getParamsAnnotation());

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,8 @@ import org.gradle.api.tasks.SourceSet;
public interface CodeGenSpec extends ModulesSpec {
DirectoryProperty getOutputDir();
Property<Boolean> getAddGeneratedAnnotation();
Property<SourceSet> getSourceSet();
Property<String> getIndent();

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,12 +19,17 @@ import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
public abstract class CodeGenTask extends ModulesTask {
@OutputDirectory
public abstract DirectoryProperty getOutputDir();
@Input
@Optional
public abstract Property<Boolean> getGeneratedAnnotation();
@Input
public abstract Property<String> getIndent();

View File

@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@ public abstract class JavaCodeGenTask extends CodeGenTask {
getCliBaseOptions(),
getProject().file(getOutputDir()).toPath(),
getIndent().get(),
getGeneratedAnnotation().get(),
getGenerateGetters().get(),
getGenerateJavadoc().get(),
getGenerateSpringBootConfig().get(),