mirror of
https://github.com/tiennm99/lombok.git
synced 2026-10-11 03:13:38 +00:00
[feature][@CheckReturnValue] Add @lombok.CheckReturnValue to generated methods where the return value should not be ignored.
Adds the annotation to @With, @WithBy, @Builder.build(), and @SuperBuilder.build() methods. Controlled via lombok.addCheckReturnValueAnnotation config key (default: true). Static analysis tools (Error Prone, IntelliJ, SpotBugs) recognize @CheckReturnValue by simple name and will warn when the return value is discarded.
This commit is contained in:
1 parent
277c7bf34a
commit
cdfb0f551e
21 files changed
+277
-8
No files matched your search
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Project Lombok Authors.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package lombok;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Lombok adds this annotation to generated methods where the return value should not be ignored.
|
||||
* <p>
|
||||
* For example, {@code @With} methods return a new instance, so ignoring the return value is always a bug.
|
||||
* Similarly, {@code @Builder}'s {@code build()} method produces the built object.
|
||||
* <p>
|
||||
* Static analysis tools (Error Prone, IntelliJ, SpotBugs) recognize {@code @CheckReturnValue}
|
||||
* by simple class name, regardless of package, and will warn when the return value is discarded.
|
||||
* <p>
|
||||
* If you want to opt out, you can add {@code lombok.addCheckReturnValueAnnotation = false} to
|
||||
* {@code lombok.config}.
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface CheckReturnValue {
|
||||
}
|
||||
@@ -89,7 +89,15 @@ public class ConfigurationKeys {
|
||||
* If {@code true}, lombok generates {@code @lombok.Generated} on all fields, methods, and types that are generated.
|
||||
*/
|
||||
public static final ConfigurationKey<Boolean> ADD_LOMBOK_GENERATED_ANNOTATIONS = new ConfigurationKey<Boolean>("lombok.addLombokGeneratedAnnotation", "Generate @lombok.Generated on all generated code (default: true).") {};
|
||||
|
||||
|
||||
/**
|
||||
* lombok configuration: {@code lombok.addCheckReturnValueAnnotation} = {@code true} | {@code false}.
|
||||
*
|
||||
* If {@code true}, lombok generates {@code @lombok.CheckReturnValue} on generated methods where the return value should not be ignored,
|
||||
* such as {@code @With} methods and {@code @Builder}'s {@code build()} method.
|
||||
*/
|
||||
public static final ConfigurationKey<Boolean> ADD_CHECK_RETURN_VALUE_ANNOTATIONS = new ConfigurationKey<Boolean>("lombok.addCheckReturnValueAnnotation", "Generate @lombok.CheckReturnValue on generated methods where the return value should not be ignored (default: true).") {};
|
||||
|
||||
/**
|
||||
* lombok configuration: {@code lombok.extern.findbugs.addSuppressFBWarnings} = {@code true} | {@code false}.
|
||||
*
|
||||
|
||||
@@ -2096,6 +2096,7 @@ public class EclipseHandlerUtil {
|
||||
private static final char[][] JAKARTA_ANNOTATION_GENERATED = Eclipse.fromQualifiedName("jakarta.annotation.Generated");
|
||||
private static final char[][] LOMBOK_GENERATED = Eclipse.fromQualifiedName("lombok.Generated");
|
||||
private static final char[][] EDU_UMD_CS_FINDBUGS_ANNOTATIONS_SUPPRESSFBWARNINGS = Eclipse.fromQualifiedName("edu.umd.cs.findbugs.annotations.SuppressFBWarnings");
|
||||
private static final char[][] LOMBOK_CHECK_RETURN_VALUE = Eclipse.fromQualifiedName("lombok.CheckReturnValue");
|
||||
|
||||
public static Annotation[] addSuppressWarningsAll(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) {
|
||||
Annotation[] anns = originalAnnotationArray;
|
||||
@@ -2126,6 +2127,11 @@ public class EclipseHandlerUtil {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Annotation[] addCheckReturnValue(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) {
|
||||
if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_CHECK_RETURN_VALUE_ANNOTATIONS))) return originalAnnotationArray;
|
||||
return addAnnotation(source, originalAnnotationArray, LOMBOK_CHECK_RETURN_VALUE);
|
||||
}
|
||||
|
||||
static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn) {
|
||||
return addAnnotation(source, originalAnnotationArray, annotationTypeFqn, (ASTNode[]) null);
|
||||
}
|
||||
|
||||
@@ -889,11 +889,12 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
|
||||
out.annotations = new Annotation[] {generateNamedAnnotation(job.source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE)};
|
||||
}
|
||||
out.receiver = generateBuildReceiver(job);
|
||||
out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations);
|
||||
if (staticName == null) createRelevantNonNullAnnotation(job.builderType, out);
|
||||
out.traverse(new SetGeneratedByVisitor(job.source), (ClassScope) null);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
private TypeReference[] typeParameterNames(TypeParameter[] typeParameters) {
|
||||
if (typeParameters == null) return null;
|
||||
|
||||
|
||||
@@ -893,6 +893,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> {
|
||||
statements.add(new ReturnStatement(allocationStatement, 0, 0));
|
||||
out.statements = statements.isEmpty() ? null : statements.toArray(new Statement[0]);
|
||||
out.receiver = HandleBuilder.generateBuildReceiver(job);
|
||||
out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations);
|
||||
createRelevantNonNullAnnotation(job.builderType, out);
|
||||
out.traverse(new SetGeneratedByVisitor(job.source), (ClassScope) null);
|
||||
return out;
|
||||
|
||||
@@ -292,7 +292,8 @@ public class HandleWith extends EclipseAnnotationHandler<With> {
|
||||
param.annotations = copyAnnotations(source, copyableAnnotations, onParam.toArray(new Annotation[0]));
|
||||
|
||||
EclipseHandlerUtil.createRelevantNonNullAnnotation(fieldNode, method);
|
||||
|
||||
method.annotations = EclipseHandlerUtil.addCheckReturnValue(fieldNode, source, method.annotations);
|
||||
|
||||
method.traverse(new SetGeneratedByVisitor(source), parent.scope);
|
||||
copyJavadoc(fieldNode, method, CopyJavadoc.WITH);
|
||||
return method;
|
||||
|
||||
@@ -375,7 +375,8 @@ public class HandleWithBy extends EclipseAnnotationHandler<WithBy> {
|
||||
|
||||
createRelevantNonNullAnnotation(sourceNode, param, method);
|
||||
createRelevantNonNullAnnotation(fieldNode, method);
|
||||
|
||||
method.annotations = addCheckReturnValue(fieldNode, source, method.annotations);
|
||||
|
||||
method.traverse(new SetGeneratedByVisitor(source), parent.scope);
|
||||
copyJavadoc(fieldNode, method, CopyJavadoc.WITH_BY);
|
||||
return method;
|
||||
|
||||
@@ -801,10 +801,11 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
|
||||
} else {
|
||||
methodDef = maker.MethodDef(maker.Modifiers(toJavacModifier(job.accessInners), annsOnMethod), job.toName(job.buildMethodName), returnTypeCopy, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null);
|
||||
}
|
||||
addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode);
|
||||
if (staticName == null) createRelevantNonNullAnnotation(job.builderType, methodDef);
|
||||
return methodDef;
|
||||
}
|
||||
|
||||
|
||||
public static JCMethodDecl generateDefaultProvider(Name methodName, JavacNode fieldNode, List<JCTypeParameter> params, BuilderJob job) {
|
||||
JavacTreeMaker maker = fieldNode.getTreeMaker();
|
||||
JCVariableDecl field = (JCVariableDecl) fieldNode.get();
|
||||
|
||||
@@ -869,10 +869,11 @@ public class HandleSuperBuilder extends JavacAnnotationHandler<SuperBuilder> {
|
||||
} else {
|
||||
methodDef = maker.MethodDef(modifiers, job.toName(job.buildMethodName), cloneSelfType(job.parentType), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null);
|
||||
}
|
||||
addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode);
|
||||
createRelevantNonNullAnnotation(job.builderType, methodDef);
|
||||
return methodDef;
|
||||
}
|
||||
|
||||
|
||||
private JCMethodDecl generateCleanMethod(java.util.List<BuilderFieldData> builderFields, JavacNode type, JavacNode source) {
|
||||
JavacTreeMaker maker = type.getTreeMaker();
|
||||
ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>();
|
||||
|
||||
@@ -281,15 +281,16 @@ public class HandleWith extends JavacAnnotationHandler<With> {
|
||||
List<JCAnnotation> annsOnMethod = copyAnnotations(onMethod, maker);
|
||||
CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source);
|
||||
if (checkerFramework.generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.<JCExpression>nil()));
|
||||
|
||||
|
||||
if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.<JCExpression>nil()));
|
||||
|
||||
|
||||
if (makeAbstract) access |= Flags.ABSTRACT;
|
||||
AnnotationValues<Accessors> accessors = JavacHandlerUtil.getAccessorsForField(field);
|
||||
boolean makeFinal = shouldMakeFinal(field, accessors);
|
||||
if (makeFinal) access |= Flags.FINAL;
|
||||
JCMethodDecl decl = recursiveSetGeneratedBy(maker.MethodDef(maker.Modifiers(access, annsOnMethod), methodName, returnType,
|
||||
methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source);
|
||||
addCheckReturnValue(decl.mods, field, source);
|
||||
copyJavadoc(field, decl, CopyJavadoc.WITH);
|
||||
return decl;
|
||||
}
|
||||
|
||||
@@ -334,6 +334,7 @@ public class HandleWithBy extends JavacAnnotationHandler<WithBy> {
|
||||
createRelevantNonNullAnnotation(source, param);
|
||||
JCMethodDecl decl = recursiveSetGeneratedBy(maker.MethodDef(maker.Modifiers(access, annsOnMethod), methodName, returnType,
|
||||
methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source);
|
||||
addCheckReturnValue(decl.mods, field, source);
|
||||
copyJavadoc(field, decl, CopyJavadoc.WITH_BY);
|
||||
createRelevantNonNullAnnotation(source, decl);
|
||||
return decl;
|
||||
|
||||
@@ -1554,6 +1554,11 @@ public class JavacHandlerUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static void addCheckReturnValue(JCModifiers mods, JavacNode node, JavacNode source) {
|
||||
if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_CHECK_RETURN_VALUE_ANNOTATIONS))) return;
|
||||
addAnnotation(mods, node, source, "lombok.CheckReturnValue", null);
|
||||
}
|
||||
|
||||
public static void addAnnotation(JCModifiers mods, JavacNode node, JavacNode source, String annotationTypeFqn, JCExpression arg) {
|
||||
boolean isJavaLangBased;
|
||||
String simpleName; {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
class CheckReturnValueBuilder {
|
||||
private final int x;
|
||||
private final String name;
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
CheckReturnValueBuilder(final int x, final String name) {
|
||||
this.x = x;
|
||||
this.name = name;
|
||||
}
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
public static class CheckReturnValueBuilderBuilder {
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
private int x;
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
private String name;
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
CheckReturnValueBuilderBuilder() {
|
||||
}
|
||||
/**
|
||||
* @return {@code this}.
|
||||
*/
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
public CheckReturnValueBuilder.CheckReturnValueBuilderBuilder x(final int x) {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* @return {@code this}.
|
||||
*/
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
public CheckReturnValueBuilder.CheckReturnValueBuilderBuilder name(final String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
@lombok.CheckReturnValue
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
public CheckReturnValueBuilder build() {
|
||||
return new CheckReturnValueBuilder(this.x, this.name);
|
||||
}
|
||||
@java.lang.Override
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
public java.lang.String toString() {
|
||||
return "CheckReturnValueBuilder.CheckReturnValueBuilderBuilder(x=" + this.x + ", name=" + this.name + ")";
|
||||
}
|
||||
}
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
public static CheckReturnValueBuilder.CheckReturnValueBuilderBuilder builder() {
|
||||
return new CheckReturnValueBuilder.CheckReturnValueBuilderBuilder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
class CheckReturnValueOff {
|
||||
final int x;
|
||||
CheckReturnValueOff(int x) {
|
||||
this.x = x;
|
||||
}
|
||||
/**
|
||||
* @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
|
||||
*/
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
public CheckReturnValueOff withX(final int x) {
|
||||
return this.x == x ? this : new CheckReturnValueOff(x);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
class CheckReturnValueWith {
|
||||
final int x;
|
||||
final String name;
|
||||
CheckReturnValueWith(int x, String name) {
|
||||
this.x = x;
|
||||
this.name = name;
|
||||
}
|
||||
/**
|
||||
* @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
|
||||
*/
|
||||
@lombok.CheckReturnValue
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
public CheckReturnValueWith withX(final int x) {
|
||||
return this.x == x ? this : new CheckReturnValueWith(x, this.name);
|
||||
}
|
||||
/**
|
||||
* @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
|
||||
*/
|
||||
@lombok.CheckReturnValue
|
||||
@java.lang.SuppressWarnings("all")
|
||||
@lombok.Generated
|
||||
public CheckReturnValueWith withName(final String name) {
|
||||
return this.name == name ? this : new CheckReturnValueWith(this.x, name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
@lombok.Builder class CheckReturnValueBuilder {
|
||||
public static @java.lang.SuppressWarnings("all") @lombok.Generated class CheckReturnValueBuilderBuilder {
|
||||
private @java.lang.SuppressWarnings("all") @lombok.Generated int x;
|
||||
private @java.lang.SuppressWarnings("all") @lombok.Generated String name;
|
||||
@java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilderBuilder() {
|
||||
super();
|
||||
}
|
||||
/**
|
||||
* @return {@code this}.
|
||||
*/
|
||||
public @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder.CheckReturnValueBuilderBuilder x(final int x) {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* @return {@code this}.
|
||||
*/
|
||||
public @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder.CheckReturnValueBuilderBuilder name(final String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
public @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder build() {
|
||||
return new CheckReturnValueBuilder(this.x, this.name);
|
||||
}
|
||||
public @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated java.lang.String toString() {
|
||||
return (((("CheckReturnValueBuilder.CheckReturnValueBuilderBuilder(x=" + this.x) + ", name=") + this.name) + ")");
|
||||
}
|
||||
}
|
||||
private final int x;
|
||||
private final String name;
|
||||
@java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder(final int x, final String name) {
|
||||
super();
|
||||
this.x = x;
|
||||
this.name = name;
|
||||
}
|
||||
public static @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder.CheckReturnValueBuilderBuilder builder() {
|
||||
return new CheckReturnValueBuilder.CheckReturnValueBuilderBuilder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import lombok.With;
|
||||
class CheckReturnValueOff {
|
||||
final @With int x;
|
||||
CheckReturnValueOff(int x) {
|
||||
super();
|
||||
this.x = x;
|
||||
}
|
||||
/**
|
||||
* @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
|
||||
*/
|
||||
public @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueOff withX(final int x) {
|
||||
return ((this.x == x) ? this : new CheckReturnValueOff(x));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import lombok.With;
|
||||
class CheckReturnValueWith {
|
||||
final @With int x;
|
||||
final @With String name;
|
||||
CheckReturnValueWith(int x, String name) {
|
||||
super();
|
||||
this.x = x;
|
||||
this.name = name;
|
||||
}
|
||||
/**
|
||||
* @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
|
||||
*/
|
||||
public @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueWith withX(final int x) {
|
||||
return ((this.x == x) ? this : new CheckReturnValueWith(x, this.name));
|
||||
}
|
||||
/**
|
||||
* @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
|
||||
*/
|
||||
public @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueWith withName(final String name) {
|
||||
return ((this.name == name) ? this : new CheckReturnValueWith(this.x, name));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@lombok.Builder
|
||||
class CheckReturnValueBuilder {
|
||||
private final int x;
|
||||
private final String name;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//CONF: lombok.addCheckReturnValueAnnotation = false
|
||||
import lombok.With;
|
||||
class CheckReturnValueOff {
|
||||
@With final int x;
|
||||
|
||||
CheckReturnValueOff(int x) {
|
||||
this.x = x;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import lombok.With;
|
||||
class CheckReturnValueWith {
|
||||
@With final int x;
|
||||
@With final String name;
|
||||
|
||||
CheckReturnValueWith(int x, String name) {
|
||||
this.x = x;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user