diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml index 26fe5426..f8da3f42 100644 --- a/.github/workflows/ant.yml +++ b/.github/workflows/ant.yml @@ -6,7 +6,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout @@ -38,13 +38,13 @@ jobs: test-javac: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: build env: - EA_JDK: 23 + EA_JDK: 24 strategy: matrix: - jdk: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] + jdk: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24] goal: [javacCurrent] include: - jdk: 11 @@ -71,15 +71,6 @@ jobs: website: jdk.java.net release: ${{ matrix.jdk }} version: latest - - - name: Install Ant - env: - ANT_VERSION: 1.10.14 - run: | - wget https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.zip - unzip apache-ant-$ANT_VERSION-bin.zip -d "${HOME}" - echo "ANT_HOME=${HOME}/apache-ant-$ANT_VERSION" >> $GITHUB_ENV - echo "${HOME}/apache-ant-$ANT_VERSION/bin" >> $GITHUB_PATH - name: Cache dependencies uses: actions/cache@v4 @@ -95,43 +86,36 @@ jobs: run: ant -noinput test.${{ matrix.goal }} test-eclipse: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: build strategy: matrix: version: - - eclipse-oxygen - - eclipse-202006 - - eclipse-202006-jdk8 - - eclipse-202212 - - eclipse-202309 - - eclipse-202312 - - eclipse-202403 - - eclipse-202406 - - eclipse-202409 - - eclipse-I-build - - eclipse-oxygen-full - - eclipse-2022-03-full - - eclipse-2023-09-full - - eclipse-2023-12-full - - eclipse-2024-03-full - - eclipse-2024-06-full - - eclipse-2024-09-full - - eclipse-I-build-full - - ecj11 - - ecj14 - - ecj16 - - ecj19 + - eclipse-oxygen + - eclipse-202006 + - eclipse-202006-jdk8 + - eclipse-202212 + - eclipse-202403 + - eclipse-202503 + - eclipse-I-build + - eclipse-oxygen-full + - eclipse-2022-03-full + - eclipse-2024-03-full + - eclipse-2025-03-full + - eclipse-I-build-full + - ecj11 + - ecj14 + - ecj16 + - ecj19 fail-fast: false - steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'zulu' - name: Cache dependencies @@ -164,7 +148,7 @@ jobs: run: xvfb-run ant -noinput dist test.${{ matrix.version }} docker-integration-test: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: build strategy: matrix: @@ -192,3 +176,32 @@ jobs: - name: Compile in container run: docker run --entrypoint="" -v $(pwd)/lombok.jar:/workspace/lombok.jar $IMAGE_NAME /bin/bash -c "cd classpath; ${{ matrix.tool.cmd }}" + + manual-tests: + runs-on: ubuntu-24.04 + needs: build + strategy: + matrix: + jdk: [8, 11, 17, 21, 23] + dir: [compileTests] + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK ${{ matrix.jdk }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.jdk }} + distribution: 'zulu' + + - uses: actions/download-artifact@v4 + with: + name: lombok.jar + path: dist + + - name: Run tests + working-directory: ./test/manual/${{ matrix.dir }}/ + run: ./runTests.sh + diff --git a/AUTHORS b/AUTHORS index 67e80060..d83703de 100755 --- a/AUTHORS +++ b/AUTHORS @@ -1,10 +1,10 @@ Lombok contributors in alphabetical order: Adam Juraszek -Aleksandar Kanchev <136312841+kanchev1@users.noreply.github.com> Aleksandr Zhelezniak Amine Touzani Andre Brait +Anshuman Mishra Bulgakov Alexander Caleb Brinkman Christian Nüssgens @@ -37,6 +37,7 @@ Mateusz Matela Michael Dardis Michael Ernst Michiel Verheul +MoonFruit Ole Ludwig Pascal Bihler Peter Grant diff --git a/README.md b/README.md index 88fc6e53..afc38a72 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,5 @@ # Project Lombok -Sponsored Content -> [![Sponsor logo Fern](https://projectlombok.org/files/logos/fern.png)](https://form.typeform.com/to/bShdJw7z) -> Offer API docs that look as good as Stripe's using our sponsor [Fern](https://form.typeform.com/to/bShdJw7z). Request a [preview of your docs](https://form.typeform.com/to/bShdJw7z) on Fern. - -
- **Project Lombok** is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, automate your logging variables, and much more. diff --git a/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.54.xml b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.54.xml new file mode 100644 index 00000000..5779f3a3 --- /dev/null +++ b/buildScripts/ivy-repo/org.projectlombok-lombok.patcher-0.54.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/buildScripts/ivy.xml b/buildScripts/ivy.xml index ec30d337..ee4c39db 100644 --- a/buildScripts/ivy.xml +++ b/buildScripts/ivy.xml @@ -30,18 +30,14 @@ - - - - - + - + @@ -112,44 +108,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -163,31 +121,19 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/buildScripts/setup.ant.xml b/buildScripts/setup.ant.xml index abd8d386..7c7667bc 100644 --- a/buildScripts/setup.ant.xml +++ b/buildScripts/setup.ant.xml @@ -160,28 +160,16 @@ This buildfile is part of projectlombok.org. It sets up the build itself. - + - - - - - - - - - + - - - - - - + + diff --git a/buildScripts/tests.ant.xml b/buildScripts/tests.ant.xml index c9d4a2b4..90fb89c3 100644 --- a/buildScripts/tests.ant.xml +++ b/buildScripts/tests.ant.xml @@ -1,5 +1,5 @@ - - + + @@ -198,11 +198,6 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn - - - - - @@ -217,29 +212,14 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn - - - - - - - - - - - - - - - - - - + + + @@ -257,6 +237,7 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn + @@ -302,28 +283,12 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn - - - - - - - - - - - - - + - - - - - - + + @@ -371,6 +336,6 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn - - + + diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 96894b96..ab8ad2e9 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -1,7 +1,12 @@ Lombok Changelog ---------------- -### v1.18.35 "Edgy Guinea Pig" +### v1.18.37 "Edgy Guinea Pig" +* PLATFORM: JDK24 support added. +* FEATURE: Lombok's nullity annotation now supports [JSpecify](https://jspecify.dev) out of the box, using [config key](https://projectlombok.org/features/configuration) `jspecify`. +* BUGFIX: Recent eclipse releases would get you 'negative length' error. The bug had always been in lombok but didn't matter until recent releases. [Issue #3823](https://github.com/projectlombok/lombok/issues/3823). + +### v1.18.36 (November 15th, 2024) * PLATFORM: JDK23 support added. * BUGFIX: Eclipse projects using the `com.pro-crafting.tools:jasperreports-maven-plugin` will now compile. diff --git a/src/bindings/mapstruct/lombok/mapstruct/NotifierHider.java b/src/bindings/mapstruct/lombok/mapstruct/NotifierHider.java index 834b5bf2..222e9c0b 100644 --- a/src/bindings/mapstruct/lombok/mapstruct/NotifierHider.java +++ b/src/bindings/mapstruct/lombok/mapstruct/NotifierHider.java @@ -1,29 +1,33 @@ package lombok.mapstruct; +import java.lang.reflect.Field; + +import javax.lang.model.type.TypeMirror; + import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeMirror; -import java.util.List; - -/** - * Report to MapStruct that a type is completed when there aren't any Lombok annotations left on it. Lombok annotations - * are removed whenever a class is processed. This way, annotations which require multiple rounds to process are also - * correctly handled, and MapStruct processing will be delayed until Lombok completely finishes processing required types. - */ class NotifierHider { public static class AstModificationNotifier implements AstModifyingAnnotationProcessor { - @Override - public boolean isTypeComplete(TypeMirror typeMirror) { + private static Field lombokInvoked; + + @Override public boolean isTypeComplete(TypeMirror type) { if (System.getProperty("lombok.disable") != null) return true; - List annotationMirrors = typeMirror.getAnnotationMirrors(); - if (annotationMirrors == null || annotationMirrors.isEmpty()) return true; - - for (AnnotationMirror annotationMirror : annotationMirrors) { - String annotationName = String.valueOf(annotationMirror); - if (annotationName.startsWith("@lombok.")) return false; + return isLombokInvoked(); + } + + private static boolean isLombokInvoked() { + if (lombokInvoked != null) { + try { + return lombokInvoked.getBoolean(null); + } catch (Exception e) {} + return true; } + try { + Class data = Class.forName("lombok.launch.AnnotationProcessorHider$AstModificationNotifierData"); + lombokInvoked = data.getField("lombokInvoked"); + return lombokInvoked.getBoolean(null); + } catch (Exception e) {} return true; } } diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index e2ec1426..c060d7b0 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -106,7 +106,7 @@ public class ConfigurationKeys { public static final ConfigurationKey ADD_SUPPRESSWARNINGS_ANNOTATIONS = new ConfigurationKey("lombok.addSuppressWarnings", "Generate @java.lang.SuppressWarnings(\"all\") on all generated code (default: true).") {}; /** - * lombok configuration: {@code lombok.addNullAnnotations = }one of: [{@code none}, {@code javax}, {@code eclipse}, {@code jetbrains}, {@code netbeans}, {@code androidx}, {@code android.support}, {@code checkerframework}, {@code findbugs}, {@code spring}, {@code JML}, or a custom set of fully qualified annotation types]. + * lombok configuration: {@code lombok.addNullAnnotations = }one of: [{@code none}, {@code javax}, {@code eclipse}, {@code jetbrains}, {@code netbeans}, {@code androidx}, {@code android.support}, {@code checkerframework}, {@code findbugs}, {@code spring}, {@code JML}, {@code jspecify} or a custom set of fully qualified annotation types]. * * Lombok generally copies relevant nullity annotations from your source code to the right places. However, sometimes lombok generates code where the nullability of some node is not dependent on something in your source code. You can configure lombok to add an appropriate nullity annotation in this case.
    *
  • {@code none} (the default) - no annotations are added.
  • @@ -121,6 +121,7 @@ public class ConfigurationKeys { *
  • {@code findbugs} - The annotations {@code edu.umd.cs.findbugs.annotations.NonNull} and {@code edu.umd.cs.findbugs.annotations.Nullable} are used.
  • *
  • {@code spring} - The annotations {@code org.springframework.lang.NonNull} and {@code org.springframework.lang.Nullable} are used.
  • *
  • {@code jml} - The annotations {@code org.jmlspecs.annotation.NonNull} and {@code org.jmlspecs.annotation.Nullable} are used.
  • + *
  • {@code jspecify} - The annotations {@code org.jspecify.annotations.NonNull} and {@code org.jspecify.annotations.Nullable} are used.
  • *
  • CUSTOM:fully.qualified.nonnull.annotation:fully.qualified.nullable.annotation to configure your own types; the nullable annotation (and the colon) are optional.
  • *
*

diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java index 0e18719b..789bbf5e 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.java @@ -30,7 +30,7 @@ public class Version { // ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries). // Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example. // Official builds always end in an even number. (Since 0.10.2). - private static final String VERSION = "1.18.35"; + private static final String VERSION = "1.18.37"; private static final String RELEASE_NAME = "Edgy Guinea Pig"; // private static final String RELEASE_NAME = "Envious Ferret"; diff --git a/src/core/lombok/core/configuration/NullAnnotationLibrary.java b/src/core/lombok/core/configuration/NullAnnotationLibrary.java index e1d31004..1187685f 100644 --- a/src/core/lombok/core/configuration/NullAnnotationLibrary.java +++ b/src/core/lombok/core/configuration/NullAnnotationLibrary.java @@ -73,7 +73,8 @@ public final class NullAnnotationLibrary implements ConfigurationValueType { public static final NullAnnotationLibrary FINDBUGS = new NullAnnotationLibrary("findbugs", "edu.umd.cs.findbugs.annotations.NonNull", "edu.umd.cs.findbugs.annotations.Nullable", false); public static final NullAnnotationLibrary SPRING = new NullAnnotationLibrary("spring", "org.springframework.lang.NonNull", "org.springframework.lang.Nullable", false); public static final NullAnnotationLibrary JML = new NullAnnotationLibrary("jml", "org.jmlspecs.annotation.NonNull", "org.jmlspecs.annotation.Nullable", false); - + public static final NullAnnotationLibrary JSPECIFY = new NullAnnotationLibrary("jspecify", "org.jspecify.annotations.NonNull", "org.jspecify.annotations.Nullable", true); + private static final List ALL_AVAILABLE; private static final String EXAMPLE_VALUE; diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 9c2fbe83..65fc4a05 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2024 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -938,7 +938,7 @@ public class HandlerUtil { private Pattern pattern; JavadocTag(String regexpFragment) { - pattern = Pattern.compile("\\s?^[ \\t]*\\**[ \\t]*" + regexpFragment + "(\\S|\\s)*?(?=(\\s^\\s*\\**\\s*@|\\Z))", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + pattern = Pattern.compile("\\s?^[ \\t]*\\**[ \\t]*" + regexpFragment + ".*?(?=(\\s^\\s*\\**\\s*@|\\Z))", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.DOTALL); } } @@ -1010,7 +1010,7 @@ public class HandlerUtil { public static String getParamJavadoc(String methodComment, String param) { if (methodComment == null || methodComment.isEmpty()) return methodComment; - Pattern pattern = Pattern.compile("@param " + param + " (\\S|\\s)+?(?=^ ?@|\\z)", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + Pattern pattern = Pattern.compile("@param " + param + " .+?(?=^ ?@|\\z)", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE | Pattern.DOTALL); Matcher matcher = pattern.matcher(methodComment); if (matcher.find()) { return matcher.group(); diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 6f5ae8bc..eeed200f 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2024 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -2156,7 +2156,7 @@ public class EclipseHandlerUtil { for (int i = 0; i < args.length; i++) { args[i].sourceStart = pS; args[i].sourceEnd = pE; - na.memberValuePairs[i] = (MemberValuePair) args[i]; + na.memberValuePairs[i] = (MemberValuePair) args[i]; } setGeneratedBy(na.memberValuePairs[0], source); setGeneratedBy(na.memberValuePairs[0].value, source); @@ -2749,10 +2749,8 @@ public class EclipseHandlerUtil { * Returns {@code true} if the provided node is a record declaration (so, not an annotation definition, interface, enum, or plain class). */ public static boolean isRecord(EclipseNode typeNode) { - TypeDeclaration typeDecl = null; - if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); - int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; - return (modifiers & AccRecord) != 0; + ASTNode node = typeNode.get(); + return node instanceof TypeDeclaration && isRecord((TypeDeclaration) node); } /** @@ -2786,6 +2784,14 @@ public class EclipseHandlerUtil { return typeNode.isStatic() || typeNode.up() == null || typeNode.up().getKind() == Kind.COMPILATION_UNIT || isRecord(typeNode); } + /** + * Returns {@code true} if the provided type declaration is a record declaration (so, not an annotation definition, interface, enum, or plain class). + */ + public static boolean isRecord(TypeDeclaration typeDecl) { + int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; + return (modifiers & AccRecord) != 0; + } + public static AbstractVariableDeclaration[] getRecordComponents(TypeDeclaration typeDeclaration) { if (typeDeclaration == null || (typeDeclaration.modifiers & AccRecord) == 0) return null; try { @@ -2812,8 +2818,8 @@ public class EclipseHandlerUtil { return annotations; } - private static final Pattern JAVADOC_PATTERN = Pattern.compile("^\\s*\\/\\*\\*((?:\\S|\\s)*?)\\*\\/", Pattern.MULTILINE); - private static final Pattern LEADING_ASTERISKS_PATTERN = Pattern.compile("(?m)^\\s*\\* ?"); + private static final Pattern JAVADOC_PATTERN = Pattern.compile("^\\s*\\/\\*\\*(.*?)\\*\\/", Pattern.MULTILINE | Pattern.DOTALL); + private static final Pattern LEADING_ASTERISKS_PATTERN = Pattern.compile("^\\s*\\* ?", Pattern.MULTILINE); public static String getDocComment(EclipseNode eclipseNode) { if (eclipseNode.getAst().getSource() == null) return null; diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index a22eb49b..96366b9a 100755 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2024 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -59,7 +59,6 @@ import org.eclipse.jdt.internal.compiler.ast.QualifiedThisReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.Receiver; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; -import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; @@ -778,27 +777,23 @@ public class HandleBuilder extends EclipseAnnotationHandler { if (mandatories.size() == 0) return null; - int pS = job.source.sourceStart, pE = job.source.sourceEnd; - char[][] nameCalled = fromQualifiedName(CheckerFrameworkVersion.NAME__CALLED); - SingleMemberAnnotation ann = new SingleMemberAnnotation(new QualifiedTypeReference(nameCalled, poss(job.source, nameCalled.length)), pS); + Expression memberValue; if (mandatories.size() == 1) { - ann.memberValue = new StringLiteral(mandatories.get(0), 0, 0, 0); + memberValue = new StringLiteral(mandatories.get(0), 0, 0, 0); } else { ArrayInitializer arr = new ArrayInitializer(); - arr.sourceStart = pS; - arr.sourceEnd = pE; arr.expressions = new Expression[mandatories.size()]; for (int i = 0; i < arr.expressions.length; i++) { - arr.expressions[i] = new StringLiteral(mandatories.get(i), pS, pE, 0); + arr.expressions[i] = new StringLiteral(mandatories.get(i), job.source.sourceStart, job.source.sourceEnd, 0); } - ann.memberValue = arr; + memberValue = arr; } TypeReference typeReference = job.createBuilderTypeReference(); int len = typeReference.getTypeName().length; typeReference.annotations = new Annotation[len][]; - typeReference.annotations[len - 1] = new Annotation[] {ann}; + typeReference.annotations[len - 1] = EclipseHandlerUtil.addAnnotation(job.source, null, nameCalled, memberValue); return new Receiver(new char[] { 't', 'h', 'i', 's' }, 0, typeReference, null, 0); } diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index 31d15445..ca885cd6 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -364,7 +364,7 @@ public class HandleConstructor { if (addConstructorProperties && !isLocalType(typeNode) && LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) { addConstructorProperties(mods, typeNode, fieldsToParam); } - if (onConstructor != null) mods.annotations = mods.annotations.appendList(copyAnnotations(onConstructor)); + if (onConstructor != null) mods.annotations = mods.annotations.appendList(copyAnnotations(onConstructor, maker)); return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName(""), null, List.nil(), params.toList(), List.nil(), maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source); diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 259880fa..07610a0b 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -185,12 +185,12 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler { JCExpression annotationMethodDefaultValue = null; List copyableAnnotations = findCopyableAnnotations(field); - List copyableToGetterAnnotations = copyAnnotations(findCopyableToGetterAnnotations(field)); + List copyableToGetterAnnotations = copyAnnotations(findCopyableToGetterAnnotations(field), treeMaker); List delegates = findDelegatesAndRemoveFromField(field); - List annsOnMethod = copyAnnotations(onMethod).appendList(copyableAnnotations).appendList(copyableToGetterAnnotations); + List annsOnMethod = copyAnnotations(onMethod, treeMaker).appendList(copyableAnnotations).appendList(copyableToGetterAnnotations); if (field.isFinal()) { if (getCheckerFrameworkVersion(field).generatePure()) annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genTypeRef(field, CheckerFrameworkVersion.NAME__PURE), List.nil())); } else { diff --git a/src/core/lombok/javac/handlers/HandleJacksonized.java b/src/core/lombok/javac/handlers/HandleJacksonized.java index 7b41ec13..b6788ac5 100644 --- a/src/core/lombok/javac/handlers/HandleJacksonized.java +++ b/src/core/lombok/javac/handlers/HandleJacksonized.java @@ -181,7 +181,7 @@ public class HandleJacksonized extends JavacAnnotationHandler { // Copy annotations from the class to the builder class. List copyableAnnotations = findJacksonAnnotationsOnClass(tdNode); - List copiedAnnotations = copyAnnotations(copyableAnnotations); + List copiedAnnotations = copyAnnotations(copyableAnnotations, maker); for (JCAnnotation anno : copiedAnnotations) { recursiveSetGeneratedBy(anno, annotationNode); } diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java index 7c88e2b8..7484ae66 100644 --- a/src/core/lombok/javac/handlers/HandleLog.java +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2021 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -43,7 +43,6 @@ import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; -import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.List; @@ -156,11 +155,9 @@ public class HandleLog { expressions[i] = maker.Apply(List.nil(), method, List.nil()); break; case TOPIC: - if (loggerTopic instanceof JCLiteral) { - expressions[i] = maker.Literal(((JCLiteral) loggerTopic).value); - } else { - expressions[i] = cloneType(maker, loggerTopic, typeNode); - } + JCExpression topicExpression = copyExpression(loggerTopic, maker); + recursiveSetGeneratedBy(topicExpression, typeNode); + expressions[i] = topicExpression; break; case NULL: expressions[i] = maker.Literal(CTC_BOT, null); diff --git a/src/core/lombok/javac/handlers/HandleNonNull.java b/src/core/lombok/javac/handlers/HandleNonNull.java index e817916d..d3649ff7 100644 --- a/src/core/lombok/javac/handlers/HandleNonNull.java +++ b/src/core/lombok/javac/handlers/HandleNonNull.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -94,17 +94,17 @@ public class HandleNonNull extends JavacAnnotationHandler { JCModifiers mods = maker.Modifiers(toJavacModifier(AccessLevel.PUBLIC) | COMPACT_RECORD_CONSTRUCTOR, List.nil()); JCBlock body = maker.Block(0L, List.nil()); - if (existingCtr == null) { - JCMethodDecl constr = maker.MethodDef(mods, typeNode.toName(""), null, List.nil(), params.toList(), List.nil(), body, null); - return recursiveSetGeneratedBy(constr, source); + JCMethodDecl constuctor = existingCtr; + if (constuctor == null) { + constuctor = maker.MethodDef(mods, typeNode.toName(""), null, List.nil(), params.toList(), List.nil(), body, null); } else { - existingCtr.mods = mods; - existingCtr.body = body; - existingCtr = recursiveSetGeneratedBy(existingCtr, source); - addSuppressWarningsAll(existingCtr.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(existingCtr)), typeNode.getContext()); - addGenerated(existingCtr.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(existingCtr)), typeNode.getContext()); - return existingCtr; + constuctor.mods = mods; + constuctor.body = body; } + recursiveSetGeneratedBy(constuctor, source); + addSuppressWarningsAll(constuctor.mods, typeNode, source, typeNode.getContext()); + addGenerated(constuctor.mods, typeNode, source, typeNode.getContext()); + return constuctor; } /** @@ -132,7 +132,6 @@ public class HandleNonNull extends JavacAnnotationHandler { if (md.name.contentEquals("")) { if ((md.mods.flags & Flags.GENERATEDCONSTR) != 0) { existingCtr = md; - existingCtr.mods.flags = existingCtr.mods.flags & ~Flags.GENERATEDCONSTR; generateConstructor = true; } else { if (!isTolerate(typeNode, md)) { @@ -307,7 +306,7 @@ public class HandleNonNull extends JavacAnnotationHandler { if (invocation.args.isEmpty()) return null; JCExpression firstArgument = invocation.args.head; if (!(firstArgument instanceof JCIdent)) return null; - return ((JCIdent) firstArgument).toString(); + return firstArgument.toString(); } if (isIf) { diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 0dfd2312..438e7b8f 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -245,7 +245,7 @@ public class HandleSetter extends JavacAnnotationHandler { List copyableAnnotations = findCopyableAnnotations(field); Name methodName = field.toName(setterName); - List annsOnParam = copyAnnotations(onParam).appendList(copyableAnnotations); + List annsOnParam = copyAnnotations(onParam, treeMaker).appendList(copyableAnnotations); long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, field.getContext()); JCExpression pType = cloneType(treeMaker, fieldDecl.vartype, source); @@ -284,7 +284,7 @@ public class HandleSetter extends JavacAnnotationHandler { Boolean fluentConfig = field.getAst().readConfiguration(ConfigurationKeys.ACCESSORS_FLUENT); if (fluentConfig != null && fluentConfig) fluent = fluentConfig; - List annsOnMethod = mergeAnnotations(copyAnnotations(onMethod), findCopyableToSetterAnnotations(field, fluent)); + List annsOnMethod = mergeAnnotations(copyAnnotations(onMethod, treeMaker), findCopyableToSetterAnnotations(field, fluent)); if (isFieldDeprecated(field) || deprecate) { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.nil())); } diff --git a/src/core/lombok/javac/handlers/HandleUtilityClass.java b/src/core/lombok/javac/handlers/HandleUtilityClass.java index b20aee3a..bc171b8f 100644 --- a/src/core/lombok/javac/handlers/HandleUtilityClass.java +++ b/src/core/lombok/javac/handlers/HandleUtilityClass.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 The Project Lombok Authors. + * Copyright (C) 2015-2024 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 @@ -107,7 +107,7 @@ public class HandleUtilityClass extends JavacAnnotationHandler { if ((typeDecl.mods.flags & (Flags.INTERFACE | Flags.ANNOTATION)) != 0) markStatic = false; } - if (markStatic) classDecl.mods.flags |= Flags.STATIC; + if (markStatic) markClassAsStatic(classDecl); for (JavacNode element : typeNode.down()) { if (element.getKind() == Kind.FIELD) { @@ -125,21 +125,23 @@ public class HandleUtilityClass extends JavacAnnotationHandler { methodDecl.mods.flags |= Flags.STATIC; } else if (element.getKind() == Kind.TYPE) { - JCClassDecl innerClassDecl = (JCClassDecl) element.get(); - innerClassDecl.mods.flags |= Flags.STATIC; - ClassSymbol innerClassSymbol = innerClassDecl.sym; - if (innerClassSymbol != null) { - if (innerClassSymbol.type instanceof ClassType) { - ((ClassType) innerClassSymbol.type).setEnclosingType(Type.noType); - } - innerClassSymbol.erasure_field = null; - } + markClassAsStatic((JCClassDecl) element.get()); } } if (makeConstructor) createPrivateDefaultConstructor(typeNode); } + private void markClassAsStatic(JCClassDecl classDecl) { + classDecl.mods.flags |= Flags.STATIC; // maybe Flags.NOOUTERTHIS too? + ClassSymbol innerClassSymbol = classDecl.sym; + if (innerClassSymbol != null && innerClassSymbol.type instanceof ClassType) { + ClassType classType = (ClassType) innerClassSymbol.type; + classType.setEnclosingType(Type.noType); + innerClassSymbol.erasure_field = null; + } + } + private void createPrivateDefaultConstructor(JavacNode typeNode) { JavacTreeMaker maker = typeNode.getTreeMaker(); JCModifiers mods = maker.Modifiers(Flags.PRIVATE, List.nil()); diff --git a/src/core/lombok/javac/handlers/HandleWith.java b/src/core/lombok/javac/handlers/HandleWith.java index c7fa0531..52f2c192 100644 --- a/src/core/lombok/javac/handlers/HandleWith.java +++ b/src/core/lombok/javac/handlers/HandleWith.java @@ -229,7 +229,7 @@ public class HandleWith extends JavacAnnotationHandler { JCBlock methodBody = null; long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, field.getContext()); - List annsOnParam = copyAnnotations(onParam).appendList(copyableAnnotations); + List annsOnParam = copyAnnotations(onParam, maker).appendList(copyableAnnotations); JCExpression pType = cloneType(maker, fieldDecl.vartype, source); JCVariableDecl param = maker.VarDef(maker.Modifiers(flags, annsOnParam), fieldDecl.name, pType, null); @@ -278,7 +278,7 @@ public class HandleWith extends JavacAnnotationHandler { List throwsClauses = List.nil(); JCExpression annotationMethodDefaultValue = null; - List annsOnMethod = copyAnnotations(onMethod); + List annsOnMethod = copyAnnotations(onMethod, maker); CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source); if (checkerFramework.generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())); diff --git a/src/core/lombok/javac/handlers/HandleWithBy.java b/src/core/lombok/javac/handlers/HandleWithBy.java index ff67fd9f..7978928f 100644 --- a/src/core/lombok/javac/handlers/HandleWithBy.java +++ b/src/core/lombok/javac/handlers/HandleWithBy.java @@ -321,7 +321,7 @@ public class HandleWithBy extends JavacAnnotationHandler { List throwsClauses = List.nil(); JCExpression annotationMethodDefaultValue = null; - List annsOnMethod = copyAnnotations(onMethod); + List annsOnMethod = copyAnnotations(onMethod, maker); CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source); if (checkerFramework.generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 7e67aee3..94b58fff 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2022 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -37,6 +37,7 @@ import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; +import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Flags; @@ -74,6 +75,7 @@ import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCWildcard; import com.sun.tools.javac.tree.JCTree.TypeBoundKind; +import com.sun.tools.javac.tree.TreeCopier; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.TreeScanner; import com.sun.tools.javac.util.Context; @@ -590,38 +592,42 @@ public class JavacHandlerUtil { private static final Field astField; private static final Field originalAnnos; - private static final Method findRecordComponentToRemove; + private static final Method getRecordComponents = Permit.permissiveGetMethod(ClassSymbol.class, "getRecordComponents"); static { Field a = null; Field o = null; - Method m = null; try { Class forName = Class.forName("com.sun.tools.javac.code.Symbol$RecordComponent"); a = Permit.permissiveGetField(forName, "ast"); o = Permit.permissiveGetField(forName, "originalAnnos"); - m = Permit.permissiveGetMethod(ClassSymbol.class, "findRecordComponentToRemove", JCVariableDecl.class); } catch (Throwable e) { // Ignore } astField = a; originalAnnos = o; - findRecordComponentToRemove = m; } static void deleteAnnotation(JCClassDecl record, JCVariableDecl component, JCTree annotation) { - if ((astField == null && originalAnnos == null) || findRecordComponentToRemove == null) return; + if ((astField == null && originalAnnos == null) || getRecordComponents == null) return; try { - Object toRemove = Permit.invokeSneaky(findRecordComponentToRemove, record.sym, component); - if (astField != null) { - // OpenJDK - JCVariableDecl variable = Permit.get(astField, toRemove); - variable.mods.annotations = filterListByPos(variable.mods.annotations, annotation); - } else { - // Zulu JDK 17 - List annotations = Permit.get(originalAnnos, toRemove); - Permit.set(originalAnnos, toRemove, filterListByPos(annotations, annotation)); + List recordComponents = (List) Permit.invokeSneaky(getRecordComponents, record.sym); + + for (Object recordComponent : recordComponents) { + Name recordComponentName = ((Symbol) recordComponent).name; + if (!recordComponentName.equals(component.name)) continue; + + if (astField != null) { + // OpenJDK + JCVariableDecl variable = Permit.get(astField, recordComponent); + variable.mods.annotations = filterListByPos(variable.mods.annotations, annotation); + } else { + // Zulu JDK 17 + List annotations = Permit.get(originalAnnos, recordComponent); + Permit.set(originalAnnos, recordComponent, filterListByPos(annotations, annotation)); + } + return; } } catch (Throwable e) { // Ignore @@ -1746,7 +1752,8 @@ public class JavacHandlerUtil { } } } - return copyAnnotations(result.toList()); + + return copyAnnotations(result.toList(), node.getTreeMaker()); } /** @@ -1797,7 +1804,7 @@ public class JavacHandlerUtil { if (annoName == null) return List.nil(); if (!annoName.isEmpty()) { - for (String bn : annotationsToFind) if (typeMatches(bn, node, annoName)) return copyAnnotations(List.of(anno)); + for (String bn : annotationsToFind) if (typeMatches(bn, node, annoName)) return copyAnnotations(List.of(anno), node.getTreeMaker()); } ListBuffer result = new ListBuffer(); @@ -1812,7 +1819,7 @@ public class JavacHandlerUtil { } } } - return copyAnnotations(result.toList()); + return copyAnnotations(result.toList(), node.getTreeMaker()); } /** @@ -2121,15 +2128,21 @@ public class JavacHandlerUtil { errorNode.addError(out.append(" are not allowed on builder classes.").toString()); } - static List copyAnnotations(List in) { + static List copyAnnotations(List in, JavacTreeMaker maker) { ListBuffer out = new ListBuffer(); for (JCExpression expr : in) { if (!(expr instanceof JCAnnotation)) continue; - out.append((JCAnnotation) expr.clone()); + out.append(copyExpression((JCAnnotation) expr, maker)); } return out.toList(); } + @SuppressWarnings("unchecked") + static T copyExpression(T expression, JavacTreeMaker maker) { + TreeVisitor visitor = new TreeCopier(maker.getUnderlyingTreeMaker()); + return (T) expression.accept(visitor, null); + } + static List mergeAnnotations(List a, List b) { if (a == null || a.isEmpty()) return b; if (b == null || b.isEmpty()) return a; @@ -2279,7 +2292,7 @@ public class JavacHandlerUtil { if (JCAnnotatedTypeReflect.is(in)) { JCExpression underlyingType = cloneType0(maker, JCAnnotatedTypeReflect.getUnderlyingType(in)); - List anns = copyAnnotations(JCAnnotatedTypeReflect.getAnnotations(in)); + List anns = copyAnnotations(JCAnnotatedTypeReflect.getAnnotations(in), maker); return JCAnnotatedTypeReflect.create(anns, underlyingType); } diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java index 512bb3c5..78e4bcdf 100644 --- a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java +++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java @@ -315,7 +315,7 @@ public class JavacSingularsRecipes { if (!setterPrefix.isEmpty()) name = builderType.toName(HandlerUtil.buildAccessorName(source, setterPrefix, name.toString())); statements.prepend(createConstructBuilderVarIfNeeded(maker, data, builderType, source)); - List methodAnnotations = copyAnnotations(findCopyableToBuilderSingularSetterAnnotations(data.annotation.up())); + List methodAnnotations = copyAnnotations(findCopyableToBuilderSingularSetterAnnotations(data.annotation.up()), maker); finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, name, params, methodAnnotations, access, null); } @@ -362,7 +362,7 @@ public class JavacSingularsRecipes { statements.prepend(JavacHandlerUtil.generateNullCheck(maker, null, data.getPluralName(), builderType, "%s cannot be null")); } - List methodAnnotations = copyAnnotations(findCopyableToSetterAnnotations(data.annotation.up(), true)); + List methodAnnotations = copyAnnotations(findCopyableToSetterAnnotations(data.annotation.up(), true), maker); finishAndInjectMethod(cfv, maker, returnType, returnStatement, data, builderType, source, deprecate, statements, name, List.of(param), methodAnnotations, access, ignoreNullCollections); } diff --git a/src/delombok/lombok/delombok/Delombok.java b/src/delombok/lombok/delombok/Delombok.java index e5fb440e..a0510f6c 100755 --- a/src/delombok/lombok/delombok/Delombok.java +++ b/src/delombok/lombok/delombok/Delombok.java @@ -103,6 +103,7 @@ public class Delombok { private LinkedHashMap fileToBase = new LinkedHashMap(); private List filesToParse = new ArrayList(); private Map formatPrefs = new HashMap(); + private List preLombokProcessors = new ArrayList(); private List additionalAnnotationProcessors = new ArrayList(); /** If null, output to standard out. */ @@ -653,6 +654,10 @@ public class Delombok { fileToBase.put(f, base); } + public void addPreLombokProcessors(AbstractProcessor processor) { + preLombokProcessors.add(processor); + } + public void addAdditionalAnnotationProcessor(AbstractProcessor processor) { additionalAnnotationProcessors.add(processor); } @@ -735,6 +740,7 @@ public class Delombok { Map baseMap = new IdentityHashMap(); Set processors = new LinkedHashSet(); + processors.addAll(preLombokProcessors); processors.add(new lombok.javac.apt.LombokProcessor()); processors.addAll(additionalAnnotationProcessors); diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index 18d6594a..83330faf 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -96,6 +96,7 @@ public class EclipsePatcher implements AgentLauncher.AgentLaunchable { patchSyntaxAndOccurrencesHighlighting(sm); patchSortMembersOperation(sm); patchExtractInterfaceAndPullUp(sm); + patchExtractVariable(sm); patchAboutDialog(sm); patchEclipseDebugPatches(sm); patchJavadoc(sm); @@ -225,6 +226,17 @@ public class EclipsePatcher implements AgentLauncher.AgentLaunchable { .build()); } + private static void patchExtractVariable(ScriptManager sm) { + /* Fix sourceEnding for generated nodes to avoid null pointer */ + sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.replaceMethodCall() + .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.util.SideEffectChecker", "findFunctionDefinition", "org.eclipse.jdt.core.dom.MethodDeclaration", "org.eclipse.jdt.core.dom.ITypeBinding", "org.eclipse.jdt.core.dom.IMethodBinding")) + .methodToReplace(new Hook("org.eclipse.jdt.core.dom.NodeFinder", "perform", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.core.ISourceRange")) + .replacementMethod(new Hook("lombok.launch.PatchFixesHider$PatchFixes", "findGeneratedNode", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.core.dom.ASTNode", "org.eclipse.jdt.core.ISourceRange", "org.eclipse.jdt.core.dom.IMethodBinding")) + .requestExtra(StackRequest.PARAM2) + .transplant() + .build()); + } + private static void patchInline(ScriptManager sm) { sm.addScriptIfWitness(OSGI_TYPES, ScriptBuilder.wrapReturnValue() .target(new MethodTarget("org.eclipse.jdt.internal.corext.refactoring.code.SourceProvider", "getCodeBlocks", "java.lang.String[]", "org.eclipse.jdt.internal.corext.refactoring.code.CallContext", "org.eclipse.jdt.core.dom.rewrite.ImportRewrite")) diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java index fee6db43..547810e9 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2024 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -48,13 +48,13 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; -import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldReference; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MessageSend; @@ -129,7 +129,8 @@ public class PatchDelegate { } private static boolean hasDelegateMarkedFieldsOrMethods(TypeDeclaration decl) { - if (decl.fields != null) for (FieldDeclaration field : decl.fields) { + AbstractVariableDeclaration[] fields = getFieldsOrRecordComponents(decl); + if (fields != null) for (AbstractVariableDeclaration field : fields) { if (field.annotations == null) continue; for (Annotation ann : field.annotations) { if (isDelegate(ann, decl)) return true; @@ -224,11 +225,19 @@ public class PatchDelegate { Annotation_applied.set(annotation, true); } + private static AbstractVariableDeclaration[] getFieldsOrRecordComponents(TypeDeclaration decl) { + if (isRecord(decl)) return getRecordComponents(decl); + return decl.fields; + } + private static void fillMethodBindingsForFields(CompilationUnitDeclaration cud, ClassScope scope, List methodsToDelegate) { TypeDeclaration decl = scope.referenceContext; if (decl == null) return; - if (decl.fields != null) for (FieldDeclaration field : decl.fields) { + AbstractVariableDeclaration[] fields = getFieldsOrRecordComponents(decl); + if (fields == null) return; + + for (AbstractVariableDeclaration field : fields) { if (field.annotations == null) continue; for (Annotation ann : field.annotations) { if (!isDelegate(ann, decl)) continue; diff --git a/src/eclipseAgent/lombok/launch/PatchFixesHider.java b/src/eclipseAgent/lombok/launch/PatchFixesHider.java index 1b005c57..5556771f 100755 --- a/src/eclipseAgent/lombok/launch/PatchFixesHider.java +++ b/src/eclipseAgent/lombok/launch/PatchFixesHider.java @@ -41,12 +41,16 @@ import org.eclipse.jdt.core.IAnnotatable; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; @@ -951,6 +955,16 @@ final class PatchFixesHider { return blocks; } } + + public static ASTNode findGeneratedNode(ASTNode root, ISourceRange sourceRange, IMethodBinding methodBinding) { + ASTNode result = NodeFinder.perform(root, sourceRange); + if (result instanceof MethodDeclaration){ + return result; + } + + CompilationUnit cu = (CompilationUnit) root; + return cu.findDeclaringNode(methodBinding.getKey()); + } } public static class FieldInitializer { diff --git a/src/launch/lombok/launch/AnnotationProcessor.java b/src/launch/lombok/launch/AnnotationProcessor.java index 28a28d72..456a8cef 100644 --- a/src/launch/lombok/launch/AnnotationProcessor.java +++ b/src/launch/lombok/launch/AnnotationProcessor.java @@ -39,6 +39,10 @@ import sun.misc.Unsafe; class AnnotationProcessorHider { + public static class AstModificationNotifierData { + public volatile static boolean lombokInvoked = false; + } + public static class AnnotationProcessor extends AbstractProcessor { private final AbstractProcessor instance = createWrappedInstance(); @@ -56,6 +60,7 @@ class AnnotationProcessorHider { @Override public void init(ProcessingEnvironment processingEnv) { disableJava9SillyWarning(); + AstModificationNotifierData.lombokInvoked = true; instance.init(processingEnv); super.init(processingEnv); } diff --git a/src/stubs/com/sun/tools/javac/parser/Tokens.java b/src/stubs/com/sun/tools/javac/parser/Tokens.java index f86bcefa..8bc2e3b0 100644 --- a/src/stubs/com/sun/tools/javac/parser/Tokens.java +++ b/src/stubs/com/sun/tools/javac/parser/Tokens.java @@ -1,5 +1,7 @@ package com.sun.tools.javac.parser; +import com.sun.tools.javac.util.JCDiagnostic; + public class Tokens { public static class Token { public int pos; @@ -12,6 +14,8 @@ public class Tokens { String getText(); + JCDiagnostic.DiagnosticPosition getPos(); + int getSourcePos(int index); CommentStyle getStyle(); diff --git a/src/stubs/com/sun/tools/javac/util/JCDiagnostic.java b/src/stubs/com/sun/tools/javac/util/JCDiagnostic.java new file mode 100644 index 00000000..74d7615b --- /dev/null +++ b/src/stubs/com/sun/tools/javac/util/JCDiagnostic.java @@ -0,0 +1,5 @@ +package com.sun.tools.javac.util; + +public class JCDiagnostic { + public interface DiagnosticPosition {} +} diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index f7834687..af19114d 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2024 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -22,7 +22,7 @@ package lombok.javac; import static lombok.javac.JavacTreeMaker.TreeTag.treeTag; -import static lombok.javac.JavacTreeMaker.TypeTag.typeTag; +import static lombok.javac.JavacTreeMaker.TypeTag.*; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -55,6 +55,7 @@ import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.TreeInfo; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import lombok.core.ClassLiteral; @@ -184,7 +185,8 @@ public class Javac { public static final TypeTag CTC_NONE = typeTag("NONE"); public static final TypeTag CTC_BOT = typeTag("BOT"); public static final TypeTag CTC_ERROR = typeTag("ERROR"); - public static final TypeTag CTC_UNKNOWN = typeTag("UNKNOWN"); + public static final TypeTag CTC_UNKNOWN = typeTagPermissive("UNKNOWN"); // UNKNOWN has been removed in JDK24, hence, we need to look it up permissively (just make it `null` if it does not exist). + public static final TypeTag CTC_UNDETVAR = typeTag("UNDETVAR"); public static final TypeTag CTC_CLASS = typeTag("CLASS"); @@ -381,6 +383,10 @@ public class Javac { @Override public boolean isDeprecated() { return text.contains("@deprecated") && field instanceof JCVariableDecl && isFieldDeprecated(field); } + + @Override public DiagnosticPosition getPos() { + return field; + } }; } } @@ -416,7 +422,7 @@ public class Javac { throw sneakyThrow(e.getCause()); } } - + public static void storeEnd(JCTree tree, int pos, JCCompilationUnit top) { try { Object endPositions = JCCOMPILATIONUNIT_ENDPOSITIONS.get(top); diff --git a/src/utils/lombok/javac/JavacTreeMaker.java b/src/utils/lombok/javac/JavacTreeMaker.java index 04f0e208..c49db768 100644 --- a/src/utils/lombok/javac/JavacTreeMaker.java +++ b/src/utils/lombok/javac/JavacTreeMaker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2020 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -258,6 +258,15 @@ public class JavacTreeMaker { public static TypeTag typeTag(String identifier) { return new TypeTag(getFieldCached(TYPE_TAG_CACHE, Javac.getJavaCompilerVersion() < 8 ? "com.sun.tools.javac.code.TypeTags" : "com.sun.tools.javac.code.TypeTag", identifier)); } + + public static TypeTag typeTagPermissive(String identifier) { + try { + return typeTag(identifier); + } catch (Exception e) { + if (e instanceof NoSuchFieldException) return null; + throw Javac.sneakyThrow(e); + } + } } public static class TreeTag extends SchroedingerType { diff --git a/src/utils/lombok/javac/TreeMirrorMaker.java b/src/utils/lombok/javac/TreeMirrorMaker.java index 44e26ab6..798a687a 100644 --- a/src/utils/lombok/javac/TreeMirrorMaker.java +++ b/src/utils/lombok/javac/TreeMirrorMaker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2021 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -105,7 +105,7 @@ public class TreeMirrorMaker extends TreeCopier { boolean wipeSymAndType = copy.type.isErroneous(); if (!wipeSymAndType) { TypeTag typeTag = TypeTag.typeTag(copy.type); - wipeSymAndType = (CTC_NONE.equals(typeTag) || CTC_ERROR.equals(typeTag) || CTC_UNKNOWN.equals(typeTag) || CTC_UNDETVAR.equals(typeTag)); + wipeSymAndType = (typeTag.equals(CTC_NONE) || typeTag.equals(CTC_ERROR) || typeTag.equals(CTC_UNKNOWN) || typeTag.equals(CTC_UNDETVAR)); } if (wipeSymAndType) { diff --git a/src/utils/lombok/permit/Permit.java b/src/utils/lombok/permit/Permit.java index 477f06c1..d58a35e6 100644 --- a/src/utils/lombok/permit/Permit.java +++ b/src/utils/lombok/permit/Permit.java @@ -132,16 +132,15 @@ public class Permit { public static Field getField(Class c, String fName) throws NoSuchFieldException { Field f = null; - Class oc = c; - while (c != null) { + Class d = c; + while (d != null) { try { - f = c.getDeclaredField(fName); + f = d.getDeclaredField(fName); break; } catch (NoSuchFieldException e) {} - c = c.getSuperclass(); + d = d.getSuperclass(); } - - if (f == null) throw new NoSuchFieldException(oc.getName() + " :: " + fName); + if (f == null) throw new NoSuchFieldException(c.getName() + " :: " + fName); return setAccessible(f); } diff --git a/test/core/src/lombok/RunTestsViaDelombok.java b/test/core/src/lombok/RunTestsViaDelombok.java index 918a0de4..8175fb71 100644 --- a/test/core/src/lombok/RunTestsViaDelombok.java +++ b/test/core/src/lombok/RunTestsViaDelombok.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2024 The Project Lombok Authors. + * Copyright (C) 2009-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 @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; +import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Locale; @@ -83,7 +84,11 @@ public class RunTestsViaDelombok extends AbstractRunTests { delombok.setDiagnosticsListener(new CapturingDiagnosticListener(file, result.getMessages())); - if (parameters.isCheckPositions()) delombok.addAdditionalAnnotationProcessor(new ValidatePositionProcessor(parameters.getMinVersion())); + if (parameters.isCheckPositions()) { + NodePositionMapper nodePositionMapper = new NodePositionMapper(); + delombok.addPreLombokProcessors(nodePositionMapper); + delombok.addAdditionalAnnotationProcessor(new ValidatePositionProcessor(parameters.getMinVersion(), nodePositionMapper)); + } delombok.addAdditionalAnnotationProcessor(new ValidateTypesProcessor()); delombok.addAdditionalAnnotationProcessor(new ValidateNoDuplicateTreeNodeProcessor()); @@ -105,15 +110,36 @@ public class RunTestsViaDelombok extends AbstractRunTests { } } + public static class NodePositionMapper extends TreeProcessor { + Map nodePositions = new HashMap(); + + @Override void processCompilationUnit(final JCCompilationUnit unit) { + unit.accept(new TreeScanner() { + @Override public void scan(JCTree tree) { + if (tree == null) return; + if (tree instanceof JCMethodDecl && (((JCMethodDecl) tree).mods.flags & Flags.GENERATEDCONSTR) != 0) return; + if (tree.pos >= 0) { + nodePositions.put(tree, tree.pos); + } + super.scan(tree); + + } + }); + } + + } + public static class ValidatePositionProcessor extends TreeProcessor { private final int version; + private final NodePositionMapper nodePositionMapper; - public ValidatePositionProcessor(int version) { + public ValidatePositionProcessor(int version, NodePositionMapper nodePositionMapper) { this.version = version; + this.nodePositionMapper = nodePositionMapper; } private String craftFailMsg(String problematicNode, Deque astContext) { - StringBuilder msg = new StringBuilder(problematicNode).append(" position of node not set: "); + StringBuilder msg = new StringBuilder(problematicNode); for (JCTree t : astContext) { msg.append("\n ").append(t.getClass().getSimpleName()); String asStr = t.toString(); @@ -153,10 +179,14 @@ public class RunTestsViaDelombok extends AbstractRunTests { if (tree instanceof JCVariableDecl && (((JCVariableDecl) tree).mods.flags & Javac.GENERATED_MEMBER) != 0) return; - if (check && tree.pos == -1) fail(craftFailMsg("Start", astContext)); + if (check && tree.pos == -1) fail(craftFailMsg("Start position of node not set: ", astContext)); + Integer expectedPos = nodePositionMapper.nodePositions.get(tree); + if (expectedPos != null && !expectedPos.equals(tree.pos)) { + fail(craftFailMsg(String.format("Expected node position %d, actual node position %d: ", expectedPos, tree.pos), astContext)); + } if (check && Javac.getEndPosition(tree, unit) == -1) { - fail(craftFailMsg("End", astContext)); + fail(craftFailMsg("End position of node not set: ", astContext)); } } finally { try { @@ -289,14 +319,6 @@ public class RunTestsViaDelombok extends AbstractRunTests { super.scan(tree); parents.pop(); } - - /** - * We always generate shallow copies for annotations - */ - @Override - public void visitAnnotation(JCAnnotation tree) { - return; - } }); } diff --git a/test/core/src/lombok/RunTestsViaEcj.java b/test/core/src/lombok/RunTestsViaEcj.java index 3a0f02e2..53008987 100644 --- a/test/core/src/lombok/RunTestsViaEcj.java +++ b/test/core/src/lombok/RunTestsViaEcj.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2024 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -242,7 +242,7 @@ public class RunTestsViaEcj extends AbstractRunTests { return source; } }; - return AST.convertCompilationUnit(4, cud, options, false, ccu, 0, null); + return AST.convertCompilationUnit(8, cud, options, false, ccu, 0, null); } } diff --git a/test/eclipse/resource/extractvariable/multiple/after/A.java b/test/eclipse/resource/extractvariable/multiple/after/A.java new file mode 100644 index 00000000..17cf0cdf --- /dev/null +++ b/test/eclipse/resource/extractvariable/multiple/after/A.java @@ -0,0 +1,15 @@ +package pkg; + +import java.util.Arrays; + +import lombok.Getter; + +@Getter +public class A { + private String string; + + public List test() { + String temp = getString(); + return Arrays.asList(temp, temp, temp); + } +} \ No newline at end of file diff --git a/test/eclipse/resource/extractvariable/multiple/before/A.java b/test/eclipse/resource/extractvariable/multiple/before/A.java new file mode 100644 index 00000000..d85d0628 --- /dev/null +++ b/test/eclipse/resource/extractvariable/multiple/before/A.java @@ -0,0 +1,14 @@ +package pkg; + +import java.util.Arrays; + +import lombok.Getter; + +@Getter +public class A { + private String string; + + public List test() { + return Arrays.asList(getString(), getString(), getString()); + } +} \ No newline at end of file diff --git a/test/eclipse/resource/extractvariable/single/after/A.java b/test/eclipse/resource/extractvariable/single/after/A.java new file mode 100644 index 00000000..c11ab11b --- /dev/null +++ b/test/eclipse/resource/extractvariable/single/after/A.java @@ -0,0 +1,13 @@ +package pkg; + +import lombok.Getter; + +@Getter +public class A { + private String string; + + public String test() { + String temp = getString(); + return temp; + } +} \ No newline at end of file diff --git a/test/eclipse/resource/extractvariable/single/before/A.java b/test/eclipse/resource/extractvariable/single/before/A.java new file mode 100644 index 00000000..1a00bf5a --- /dev/null +++ b/test/eclipse/resource/extractvariable/single/before/A.java @@ -0,0 +1,12 @@ +package pkg; + +import lombok.Getter; + +@Getter +public class A { + private String string; + + public String test() { + return getString(); + } +} \ No newline at end of file diff --git a/test/eclipse/src/lombok/eclipse/EclipseTests.java b/test/eclipse/src/lombok/eclipse/EclipseTests.java index 66f5d9fa..c0306aa6 100644 --- a/test/eclipse/src/lombok/eclipse/EclipseTests.java +++ b/test/eclipse/src/lombok/eclipse/EclipseTests.java @@ -31,12 +31,13 @@ import lombok.eclipse.edit.SelectTest; import lombok.eclipse.misc.DelegateTest; import lombok.eclipse.misc.JavadocTest; import lombok.eclipse.refactoring.ExtractInterfaceTest; +import lombok.eclipse.refactoring.ExtractVariableTest; import lombok.eclipse.refactoring.InlineTest; import lombok.eclipse.refactoring.RenameTest; import lombok.eclipse.references.FindReferencesTest; @RunWith(Suite.class) -@SuiteClasses({ExtractInterfaceTest.class, RenameTest.class, SelectTest.class, CleanupTest.class, FindReferencesTest.class, InlineTest.class, NoErrorsTest.class, JavadocTest.class, DelegateTest.class}) +@SuiteClasses({ExtractInterfaceTest.class, RenameTest.class, SelectTest.class, CleanupTest.class, FindReferencesTest.class, InlineTest.class, NoErrorsTest.class, JavadocTest.class, DelegateTest.class, ExtractVariableTest.class}) public class EclipseTests { } diff --git a/test/eclipse/src/lombok/eclipse/RefactoringUtils.java b/test/eclipse/src/lombok/eclipse/RefactoringUtils.java index 2362ff11..de6d7e49 100644 --- a/test/eclipse/src/lombok/eclipse/RefactoringUtils.java +++ b/test/eclipse/src/lombok/eclipse/RefactoringUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Project Lombok Authors. + * Copyright (C) 2022-2024 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 @@ -21,7 +21,7 @@ */ package lombok.eclipse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; @@ -45,7 +45,7 @@ public class RefactoringUtils { ResourcesPlugin.getWorkspace().run(perform, null); - assertTrue("Condition failed", change.getConditionCheckingStatus().isOK()); + assertEquals("Condition failed", "", change.getConditionCheckingStatus().toString()); assertTrue("Perform failed", perform.changeExecuted()); } } diff --git a/test/eclipse/src/lombok/eclipse/refactoring/ExtractVariableTest.java b/test/eclipse/src/lombok/eclipse/refactoring/ExtractVariableTest.java new file mode 100644 index 00000000..50e5b066 --- /dev/null +++ b/test/eclipse/src/lombok/eclipse/refactoring/ExtractVariableTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 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.eclipse.refactoring; + +import static lombok.eclipse.RefactoringUtils.performRefactoring; + +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.internal.corext.refactoring.code.ExtractTempRefactoring; +import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; +import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import lombok.eclipse.EclipseRunner; +import lombok.eclipse.SetupBeforeAfterTest; + +@RunWith(EclipseRunner.class) +public class ExtractVariableTest { + + @Rule + public SetupBeforeAfterTest setup = new SetupBeforeAfterTest(); + + @Test + public void single() throws Exception { + ICompilationUnit cu = setup.getPackageFragment().getCompilationUnit("A.java"); + + CompilationUnit compilationUnit = new RefactoringASTParser(ASTProvider.SHARED_AST_LEVEL).parse(cu, true); + + ExtractTempRefactoring extractTempRefactoring = new ExtractTempRefactoring(compilationUnit, 121, 11); + extractTempRefactoring.setTempName("temp"); + performRefactoring(extractTempRefactoring); + } + + @Test + public void multiple() throws Exception { + ICompilationUnit cu = setup.getPackageFragment().getCompilationUnit("A.java"); + + CompilationUnit compilationUnit = new RefactoringASTParser(ASTProvider.SHARED_AST_LEVEL).parse(cu, true); + + ExtractTempRefactoring extractTempRefactoring = new ExtractTempRefactoring(compilationUnit, 167, 11); + extractTempRefactoring.setTempName("temp"); + performRefactoring(extractTempRefactoring); + } +} diff --git a/test/manual/compileTests/.gitignore b/test/manual/compileTests/.gitignore new file mode 100644 index 00000000..e2e7327c --- /dev/null +++ b/test/manual/compileTests/.gitignore @@ -0,0 +1 @@ +/out diff --git a/test/manual/compileTests/runTests.sh b/test/manual/compileTests/runTests.sh new file mode 100755 index 00000000..1b129920 --- /dev/null +++ b/test/manual/compileTests/runTests.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -e +echo 'This will compile a selection of files in test/transform/resource/before. If the compilation works without error, lombok is working as designed.' + +mkdir -p out/ + +# list of files to iterate over +FILES="UtilityClassInner.java" +BASE_PATH="../../../test/transform/resource/before" +LOMBOK_JAR="../../../dist/lombok.jar" + +# compile all files in list +for f in $FILES +do + + echo "Compiling $f" + javac -processorpath $LOMBOK_JAR -cp $LOMBOK_JAR -d out/ $BASE_PATH/$f +done + +rm -rf out/ diff --git a/test/transform/resource/after-delombok/DelegateOnRecord.java b/test/transform/resource/after-delombok/DelegateOnRecord.java new file mode 100644 index 00000000..fc5b9744 --- /dev/null +++ b/test/transform/resource/after-delombok/DelegateOnRecord.java @@ -0,0 +1,8 @@ +// version 14: +record DelegateOnRecord(Runnable runnable) { + @java.lang.SuppressWarnings("all") + @lombok.Generated + public void run() { + this.runnable.run(); + } +} diff --git a/test/transform/resource/after-delombok/GetterSetterJavadoc.java b/test/transform/resource/after-delombok/GetterSetterJavadoc.java index 76afbd2d..f1bb0324 100644 --- a/test/transform/resource/after-delombok/GetterSetterJavadoc.java +++ b/test/transform/resource/after-delombok/GetterSetterJavadoc.java @@ -163,3 +163,207 @@ class GetterSetterJavadoc5 { return this; } } +class GetterSetterJavadocLong { + /** + * This field represents the unique identifier for a user in the system. It is used + * throughout the application to uniquely identify and retrieve user-related data. + * The ID is typically generated by the database and is guaranteed to be unique + * within the context of the system. + * + *

The ID is an integral part of various operations, including but not limited to: + *

    + *
  • Authenticating users during login processes.
  • + *
  • Associating user-specific preferences and settings.
  • + *
  • Tracking user activity and logs for auditing purposes.
  • + *
  • Facilitating relationships between users and other entities such as orders, + * messages, or roles within the system.
  • + *
+ * + *

Key characteristics of the {@code userId} field: + *

    + *
  • Immutability: Once assigned, the ID must not be modified to + * ensure data integrity.
  • + *
  • Security: Access to this field should be controlled to prevent + * unauthorized modifications or data leakage.
  • + *
  • Uniqueness: The ID must be unique within the system. For + * distributed systems, consider using GUIDs or UUIDs to avoid collisions.
  • + *
+ * + *

Example usage: + *

+	 *     User user = userService.getUserById(userId);
+	 *     if (user != null) {
+	 *         System.out.println("User found: " + user.getName());
+	 *     } else {
+	 *         System.out.println("User not found.");
+	 *     }
+	 * 
+ * + *

Developers should ensure that the ID complies with constraints imposed by the + * database schema, such as length and format restrictions. Furthermore, it is + * recommended to validate the ID before persisting or using it in critical operations. + * + *

For methods or constructors that accept the {@code userId} as a parameter, the + * following guidelines should be followed: + *

    + *
  • Validate the format of the ID to ensure it adheres to system requirements.
  • + *
  • Handle null or empty values gracefully, providing appropriate error messages + * or default behavior where necessary.
  • + *
+ */ + private String userId; + @java.lang.SuppressWarnings("all") + @lombok.Generated + public GetterSetterJavadocLong() { + } + /** + * This field represents the unique identifier for a user in the system. It is used + * throughout the application to uniquely identify and retrieve user-related data. + * The ID is typically generated by the database and is guaranteed to be unique + * within the context of the system. + * + *

The ID is an integral part of various operations, including but not limited to: + *

    + *
  • Authenticating users during login processes.
  • + *
  • Associating user-specific preferences and settings.
  • + *
  • Tracking user activity and logs for auditing purposes.
  • + *
  • Facilitating relationships between users and other entities such as orders, + * messages, or roles within the system.
  • + *
+ * + *

Key characteristics of the {@code userId} field: + *

    + *
  • Immutability: Once assigned, the ID must not be modified to + * ensure data integrity.
  • + *
  • Security: Access to this field should be controlled to prevent + * unauthorized modifications or data leakage.
  • + *
  • Uniqueness: The ID must be unique within the system. For + * distributed systems, consider using GUIDs or UUIDs to avoid collisions.
  • + *
+ * + *

Example usage: + *

+	 *     User user = userService.getUserById(userId);
+	 *     if (user != null) {
+	 *         System.out.println("User found: " + user.getName());
+	 *     } else {
+	 *         System.out.println("User not found.");
+	 *     }
+	 * 
+ * + *

Developers should ensure that the ID complies with constraints imposed by the + * database schema, such as length and format restrictions. Furthermore, it is + * recommended to validate the ID before persisting or using it in critical operations. + * + *

For methods or constructors that accept the {@code userId} as a parameter, the + * following guidelines should be followed: + *

    + *
  • Validate the format of the ID to ensure it adheres to system requirements.
  • + *
  • Handle null or empty values gracefully, providing appropriate error messages + * or default behavior where necessary.
  • + *
+ */ + @java.lang.SuppressWarnings("all") + @lombok.Generated + public String getUserId() { + return this.userId; + } + /** + * This field represents the unique identifier for a user in the system. It is used + * throughout the application to uniquely identify and retrieve user-related data. + * The ID is typically generated by the database and is guaranteed to be unique + * within the context of the system. + * + *

The ID is an integral part of various operations, including but not limited to: + *

    + *
  • Authenticating users during login processes.
  • + *
  • Associating user-specific preferences and settings.
  • + *
  • Tracking user activity and logs for auditing purposes.
  • + *
  • Facilitating relationships between users and other entities such as orders, + * messages, or roles within the system.
  • + *
+ * + *

Key characteristics of the {@code userId} field: + *

    + *
  • Immutability: Once assigned, the ID must not be modified to + * ensure data integrity.
  • + *
  • Security: Access to this field should be controlled to prevent + * unauthorized modifications or data leakage.
  • + *
  • Uniqueness: The ID must be unique within the system. For + * distributed systems, consider using GUIDs or UUIDs to avoid collisions.
  • + *
+ * + *

Example usage: + *

+	 *     User user = userService.getUserById(userId);
+	 *     if (user != null) {
+	 *         System.out.println("User found: " + user.getName());
+	 *     } else {
+	 *         System.out.println("User not found.");
+	 *     }
+	 * 
+ * + *

Developers should ensure that the ID complies with constraints imposed by the + * database schema, such as length and format restrictions. Furthermore, it is + * recommended to validate the ID before persisting or using it in critical operations. + * + *

For methods or constructors that accept the {@code userId} as a parameter, the + * following guidelines should be followed: + *

    + *
  • Validate the format of the ID to ensure it adheres to system requirements.
  • + *
  • Handle null or empty values gracefully, providing appropriate error messages + * or default behavior where necessary.
  • + *
+ * + * @param userId the unique identifier assigned to a user. This value must be non-null + * and conform to the format defined by the system. Passing a null or + * invalid ID may result in an {@link IllegalArgumentException} or + * similar error. + * @param databaseConnection the connection to the database used for retrieving or + * persisting the user ID. This parameter is required for + * database-related operations and must be properly closed + * after use to prevent resource leaks. + * @param userRole the role associated with the user (e.g., "admin", "user", "guest"). + * This parameter may influence access control and permissions + * granted to the user within the system. + */ + @java.lang.SuppressWarnings("all") + @lombok.Generated + public void setUserId(final String userId) { + this.userId = userId; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @lombok.Generated + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof GetterSetterJavadocLong)) return false; + final GetterSetterJavadocLong other = (GetterSetterJavadocLong) o; + if (!other.canEqual((java.lang.Object) this)) return false; + final java.lang.Object this$userId = this.getUserId(); + final java.lang.Object other$userId = other.getUserId(); + if (this$userId == null ? other$userId != null : !this$userId.equals(other$userId)) return false; + return true; + } + @java.lang.SuppressWarnings("all") + @lombok.Generated + protected boolean canEqual(final java.lang.Object other) { + return other instanceof GetterSetterJavadocLong; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @lombok.Generated + public int hashCode() { + final int PRIME = 59; + int result = 1; + final java.lang.Object $userId = this.getUserId(); + result = result * PRIME + ($userId == null ? 43 : $userId.hashCode()); + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @lombok.Generated + public java.lang.String toString() { + return "GetterSetterJavadocLong(userId=" + this.getUserId() + ")"; + } +} diff --git a/test/transform/resource/after-delombok/UtilityClassInner.java b/test/transform/resource/after-delombok/UtilityClassInner.java index 4c4c6743..cd91bc2d 100644 --- a/test/transform/resource/after-delombok/UtilityClassInner.java +++ b/test/transform/resource/after-delombok/UtilityClassInner.java @@ -1,5 +1,6 @@ +@java.lang.SuppressWarnings("serial") class UtilityClassInner { - static final class UtilClass { + static final class UtilClass implements java.io.Serializable { @java.lang.SuppressWarnings("all") @lombok.Generated private UtilClass() { diff --git a/test/transform/resource/after-ecj/DelegateOnRecord.java b/test/transform/resource/after-ecj/DelegateOnRecord.java new file mode 100644 index 00000000..7194a014 --- /dev/null +++ b/test/transform/resource/after-ecj/DelegateOnRecord.java @@ -0,0 +1,8 @@ +// version 14: +import lombok.experimental.Delegate; +record DelegateOnRecord(Runnable runnable) { +/* Implicit */ private final Runnable runnable; + public @java.lang.SuppressWarnings("all") @lombok.Generated void run() { + this.runnable.run(); + } +} diff --git a/test/transform/resource/after-ecj/GetterSetterJavadoc.java b/test/transform/resource/after-ecj/GetterSetterJavadoc.java index 03ac4075..89b6af5a 100644 --- a/test/transform/resource/after-ecj/GetterSetterJavadoc.java +++ b/test/transform/resource/after-ecj/GetterSetterJavadoc.java @@ -132,3 +132,148 @@ class GetterSetterJavadoc3 { return this; } } +@lombok.Data class GetterSetterJavadocLong { + private String userId; + /** + * This field represents the unique identifier for a user in the system. It is used + * throughout the application to uniquely identify and retrieve user-related data. + * The ID is typically generated by the database and is guaranteed to be unique + * within the context of the system. + * + *

The ID is an integral part of various operations, including but not limited to: + *

    + *
  • Authenticating users during login processes.
  • + *
  • Associating user-specific preferences and settings.
  • + *
  • Tracking user activity and logs for auditing purposes.
  • + *
  • Facilitating relationships between users and other entities such as orders, + * messages, or roles within the system.
  • + *
+ * + *

Key characteristics of the {@code userId} field: + *

    + *
  • Immutability: Once assigned, the ID must not be modified to + * ensure data integrity.
  • + *
  • Security: Access to this field should be controlled to prevent + * unauthorized modifications or data leakage.
  • + *
  • Uniqueness: The ID must be unique within the system. For + * distributed systems, consider using GUIDs or UUIDs to avoid collisions.
  • + *
+ * + *

Example usage: + *

+   *     User user = userService.getUserById(userId);
+   *     if (user != null) {
+   *         System.out.println("User found: " + user.getName());
+   *     } else {
+   *         System.out.println("User not found.");
+   *     }
+   * 
+ * + *

Developers should ensure that the ID complies with constraints imposed by the + * database schema, such as length and format restrictions. Furthermore, it is + * recommended to validate the ID before persisting or using it in critical operations. + * + *

For methods or constructors that accept the {@code userId} as a parameter, the + * following guidelines should be followed: + *

    + *
  • Validate the format of the ID to ensure it adheres to system requirements.
  • + *
  • Handle null or empty values gracefully, providing appropriate error messages + * or default behavior where necessary.
  • + *
+ */ + public @java.lang.SuppressWarnings("all") @lombok.Generated String getUserId() { + return this.userId; + } + /** + * This field represents the unique identifier for a user in the system. It is used + * throughout the application to uniquely identify and retrieve user-related data. + * The ID is typically generated by the database and is guaranteed to be unique + * within the context of the system. + * + *

The ID is an integral part of various operations, including but not limited to: + *

    + *
  • Authenticating users during login processes.
  • + *
  • Associating user-specific preferences and settings.
  • + *
  • Tracking user activity and logs for auditing purposes.
  • + *
  • Facilitating relationships between users and other entities such as orders, + * messages, or roles within the system.
  • + *
+ * + *

Key characteristics of the {@code userId} field: + *

    + *
  • Immutability: Once assigned, the ID must not be modified to + * ensure data integrity.
  • + *
  • Security: Access to this field should be controlled to prevent + * unauthorized modifications or data leakage.
  • + *
  • Uniqueness: The ID must be unique within the system. For + * distributed systems, consider using GUIDs or UUIDs to avoid collisions.
  • + *
+ * + *

Example usage: + *

+   *     User user = userService.getUserById(userId);
+   *     if (user != null) {
+   *         System.out.println("User found: " + user.getName());
+   *     } else {
+   *         System.out.println("User not found.");
+   *     }
+   * 
+ * + *

Developers should ensure that the ID complies with constraints imposed by the + * database schema, such as length and format restrictions. Furthermore, it is + * recommended to validate the ID before persisting or using it in critical operations. + * + *

For methods or constructors that accept the {@code userId} as a parameter, the + * following guidelines should be followed: + *

    + *
  • Validate the format of the ID to ensure it adheres to system requirements.
  • + *
  • Handle null or empty values gracefully, providing appropriate error messages + * or default behavior where necessary.
  • + *
+ * + * @param userId the unique identifier assigned to a user. This value must be non-null + * and conform to the format defined by the system. Passing a null or + * invalid ID may result in an {@link IllegalArgumentException} or + * similar error. + * @param databaseConnection the connection to the database used for retrieving or + * persisting the user ID. This parameter is required for + * database-related operations and must be properly closed + * after use to prevent resource leaks. + * @param userRole the role associated with the user (e.g., "admin", "user", "guest"). + * This parameter may influence access control and permissions + * granted to the user within the system. + */ + public @java.lang.SuppressWarnings("all") @lombok.Generated void setUserId(final String userId) { + this.userId = userId; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof GetterSetterJavadocLong))) + return false; + final GetterSetterJavadocLong other = (GetterSetterJavadocLong) o; + if ((! other.canEqual((java.lang.Object) this))) + return false; + final java.lang.Object this$userId = this.getUserId(); + final java.lang.Object other$userId = other.getUserId(); + if (((this$userId == null) ? (other$userId != null) : (! this$userId.equals(other$userId)))) + return false; + return true; + } + protected @java.lang.SuppressWarnings("all") @lombok.Generated boolean canEqual(final java.lang.Object other) { + return (other instanceof GetterSetterJavadocLong); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated int hashCode() { + final int PRIME = 59; + int result = 1; + final java.lang.Object $userId = this.getUserId(); + result = ((result * PRIME) + (($userId == null) ? 43 : $userId.hashCode())); + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated java.lang.String toString() { + return (("GetterSetterJavadocLong(userId=" + this.getUserId()) + ")"); + } + public @java.lang.SuppressWarnings("all") @lombok.Generated GetterSetterJavadocLong() { + super(); + } +} \ No newline at end of file diff --git a/test/transform/resource/after-ecj/UtilityClassInner.java b/test/transform/resource/after-ecj/UtilityClassInner.java index 9086367c..31bd396c 100644 --- a/test/transform/resource/after-ecj/UtilityClassInner.java +++ b/test/transform/resource/after-ecj/UtilityClassInner.java @@ -1,5 +1,5 @@ -class UtilityClassInner { - static final @lombok.experimental.UtilityClass class UtilClass { +@java.lang.SuppressWarnings("serial") class UtilityClassInner { + static final @lombok.experimental.UtilityClass class UtilClass implements java.io.Serializable { private @java.lang.SuppressWarnings("all") @lombok.Generated UtilClass() { super(); throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); diff --git a/test/transform/resource/before/DelegateOnRecord.java b/test/transform/resource/before/DelegateOnRecord.java new file mode 100644 index 00000000..daaeba5b --- /dev/null +++ b/test/transform/resource/before/DelegateOnRecord.java @@ -0,0 +1,6 @@ +//platform !eclipse: Requires a 'full' eclipse with intialized workspace, and we don't (yet) have that set up properly in the test run. +//version 14: +import lombok.experimental.Delegate; + +record DelegateOnRecord(@Delegate Runnable runnable) { +} diff --git a/test/transform/resource/before/GetterSetterJavadoc.java b/test/transform/resource/before/GetterSetterJavadoc.java index 2ad46f8d..b14f2c02 100644 --- a/test/transform/resource/before/GetterSetterJavadoc.java +++ b/test/transform/resource/before/GetterSetterJavadoc.java @@ -62,3 +62,67 @@ class GetterSetterJavadoc5 { */ @lombok.Getter @lombok.Setter private int fieldName; } + +@lombok.Data +class GetterSetterJavadocLong { + /** + * This field represents the unique identifier for a user in the system. It is used + * throughout the application to uniquely identify and retrieve user-related data. + * The ID is typically generated by the database and is guaranteed to be unique + * within the context of the system. + * + *

The ID is an integral part of various operations, including but not limited to: + *

    + *
  • Authenticating users during login processes.
  • + *
  • Associating user-specific preferences and settings.
  • + *
  • Tracking user activity and logs for auditing purposes.
  • + *
  • Facilitating relationships between users and other entities such as orders, + * messages, or roles within the system.
  • + *
+ * + *

Key characteristics of the {@code userId} field: + *

    + *
  • Immutability: Once assigned, the ID must not be modified to + * ensure data integrity.
  • + *
  • Security: Access to this field should be controlled to prevent + * unauthorized modifications or data leakage.
  • + *
  • Uniqueness: The ID must be unique within the system. For + * distributed systems, consider using GUIDs or UUIDs to avoid collisions.
  • + *
+ * + *

Example usage: + *

+	 *     User user = userService.getUserById(userId);
+	 *     if (user != null) {
+	 *         System.out.println("User found: " + user.getName());
+	 *     } else {
+	 *         System.out.println("User not found.");
+	 *     }
+	 * 
+ * + *

Developers should ensure that the ID complies with constraints imposed by the + * database schema, such as length and format restrictions. Furthermore, it is + * recommended to validate the ID before persisting or using it in critical operations. + * + *

For methods or constructors that accept the {@code userId} as a parameter, the + * following guidelines should be followed: + *

    + *
  • Validate the format of the ID to ensure it adheres to system requirements.
  • + *
  • Handle null or empty values gracefully, providing appropriate error messages + * or default behavior where necessary.
  • + *
+ * + * @param userId the unique identifier assigned to a user. This value must be non-null + * and conform to the format defined by the system. Passing a null or + * invalid ID may result in an {@link IllegalArgumentException} or + * similar error. + * @param databaseConnection the connection to the database used for retrieving or + * persisting the user ID. This parameter is required for + * database-related operations and must be properly closed + * after use to prevent resource leaks. + * @param userRole the role associated with the user (e.g., "admin", "user", "guest"). + * This parameter may influence access control and permissions + * granted to the user within the system. + */ + private String userId; +} diff --git a/test/transform/resource/before/UtilityClassInner.java b/test/transform/resource/before/UtilityClassInner.java index ccb0101b..8fd3a9e7 100644 --- a/test/transform/resource/before/UtilityClassInner.java +++ b/test/transform/resource/before/UtilityClassInner.java @@ -1,4 +1,5 @@ +@java.lang.SuppressWarnings("serial") class UtilityClassInner { @lombok.experimental.UtilityClass - class UtilClass {} + class UtilClass implements java.io.Serializable {} } \ No newline at end of file diff --git a/website/templates/_download-edge.html b/website/templates/_download-edge.html index 0e04596c..7b98d3b2 100644 --- a/website/templates/_download-edge.html +++ b/website/templates/_download-edge.html @@ -10,7 +10,13 @@

Download edge release now!

- You can use the edge release from maven using the projectlombok.org repository:

+				You can use the edge release from maven using the projectlombok.org repository.
+				This requires:
    +
  • Adding the lombok maven repository to your repositories section
  • +
  • Adding lombok as a dependency to your project in the dependencies section, specifying version edge-SNAPSHOT.
  • +
  • Adding lombok as an annotation processor in the annotationProcessorPaths section, specifying version edge-SNAPSHOT.
  • +
+ These sections in your pom file should look like this:
 <repositories>
 	<repository>
 		<id>projectlombok.org</id>
@@ -26,6 +32,26 @@
 		<scope>provided</scope>
 	</dependency>
 </dependencies>
+
+<build>
+	<plugins>
+		<plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-compiler-plugin</artifactId>
+			<configuration>
+				<annotationProcessorPaths>
+					<path>
+						<groupId>org.projectlombok</groupId>
+						<artifactId>lombok</artifactId>
+						<version>edge-SNAPSHOT</version>
+					</path>
+				</annotationProcessorPaths>
+			</configuration>
+		</plugin>
+	</plugins>
+</build>
+
+

You can use the edge release from gradle using the projectlombok.org repository:

diff --git a/website/templates/features/Builder.html b/website/templates/features/Builder.html
index 05881083..4fd90741 100644
--- a/website/templates/features/Builder.html
+++ b/website/templates/features/Builder.html
@@ -66,7 +66,7 @@
 		

Now that the "method" mode is clear, putting a @Builder annotation on a constructor functions similarly; effectively, constructors are just static methods that have a special syntax to invoke them: Their 'return type' is the class they construct, and their type parameters are the same as the type parameters of the class itself.

- Finally, applying @Builder to a class is as if you added @AllArgsConstructor(access = AccessLevel.PACKAGE) to the class and applied the @Builder annotation to this all-args-constructor. This only works if you haven't written any explicit constructors yourself or allowed lombok to create one such as with @NoArgsConstructor. If you do have an explicit constructor, put the @Builder annotation on the constructor instead of on the class. Note that if you put both `@Value` and `@Builder` on a class, the package-private constructor that `@Builder` wants to generate 'wins' and suppresses the constructor that `@Value` wants to make. + Finally, applying @Builder to a class is as if you added @AllArgsConstructor(access = AccessLevel.PACKAGE) to the class and applied the @Builder annotation to this all-args-constructor. This only works if you haven't written any explicit constructors yourself or allowed lombok to create one such as with @NoArgsConstructor. If you do have an explicit constructor, put the @Builder annotation on the constructor instead of on the class. Note that if you put both @Value and @Builder on a class, the package-private constructor that @Builder wants to generate 'wins' and suppresses the constructor that @Value wants to make.

If using @Builder to generate builders to produce instances of your own class (this is always the case unless adding @Builder to a method that doesn't return your own type), you can use @Builder(toBuilder = true) to also generate an instance method in your class called toBuilder(); it creates a new builder that starts out with all the values of this instance. You can put the @Builder.ObtainVia annotation on the parameters (in case of a constructor or method) or fields (in case of @Builder on a type) to indicate alternative means by which the value for that field/parameter is obtained from this instance. For example, you can specify a method to be invoked: @Builder.ObtainVia(method = "calculateFoo").

@@ -228,7 +228,7 @@ public class JacksonExample { You can use @Builder for copy constructors: foo.toBuilder().build() makes a shallow clone. Consider suppressing the generating of the builder method if you just want this functionality, by using: @Builder(toBuilder = true, builderMethodName = "").

- Due to a peculiar way javac processes static imports, trying to do a non-star static import of the static builder() method won't work. Either use a star static import: `import static TypeThatHasABuilder.*;` or don't statically import the builder method. + Due to a peculiar way javac processes static imports, trying to do a non-star static import of the static builder() method won't work. Either use a star static import: import static TypeThatHasABuilder.*; or don't statically import the builder method.

If setting the access level to PROTECTED, all methods generated inside the builder class are actually generated as public; the meaning of the protected keyword is different inside the inner class, and the precise behavior that PROTECTED would indicate (access by any source in the same package is allowed, as well as any subclasses from the outer class, marked with @Builder is not possible, and marking the inner members public is as close as we can get. diff --git a/website/templates/features/Locked.html b/website/templates/features/Locked.html index fa041975..a3fef167 100644 --- a/website/templates/features/Locked.html +++ b/website/templates/features/Locked.html @@ -3,7 +3,7 @@ <@f.scaffold title="@Locked" logline="Pop it and lock it! ReentrantLock, now with less hassle."> <@f.history>

- @Locked was introduced in lombok v1.20. + @Locked was introduced in lombok v1.18.32.

diff --git a/website/templates/features/With.html b/website/templates/features/With.html index 867c17eb..f4809d26 100644 --- a/website/templates/features/With.html +++ b/website/templates/features/With.html @@ -27,7 +27,7 @@ <@f.snippets name="With" /> <@f.confKeys> - <>
+
lombok.accessors.prefix += a field prefix (default: empty list)
This is a list property; entries can be added with the += operator. Inherited prefixes from parent config files can be removed with the -= operator. Lombok will strip any matching field prefix from the name of a field in order to determine the name of the getter/setter to generate. For example, if m is one of the prefixes listed in this setting, then a field named mFoobar will result in a getter named getFoobar(), not getMFoobar(). An explicitly configured prefix parameter of an @Accessors annotation takes precedence over this setting. diff --git a/website/templates/features/configuration.html b/website/templates/features/configuration.html index c3c39835..6675d61e 100644 --- a/website/templates/features/configuration.html +++ b/website/templates/features/configuration.html @@ -87,7 +87,7 @@
  1. lombok.addNullAnnotations = <flavor>
- Many flavors are available: javax (=JSR305; not recommended), jakarta, eclipse, jetbrains, netbeans, androidx, android.support (deprecated within android), checkerframework (recommended), findbugs, spring, jml, or define your own via CUSTOM:fully.qualified.NonNullAnnotation:fully.qualified.NullableAnnotation; if your nullity annotation is solely of the type use style (it annotates types, such as eclipse's and checkerframework's offerings, versus annotating methods and parameters), the format is CUSTOM:TYPE_USE:nonnullanno:nullableanno.
+ Many flavors are available: jspecify (recommended), checkerframework (recommended), jakarta, eclipse, jetbrains, netbeans, androidx, findbugs, spring, jml, javax (=JSR305; not recommended), android.support (deprecated within android), or define your own via CUSTOM:fully.qualified.NonNullAnnotation:fully.qualified.NullableAnnotation; if your nullity annotation is solely of the type use style (it annotates types, such as eclipse's and checkerframework's offerings, versus annotating methods and parameters), the format is CUSTOM:TYPE_USE:nonnullanno:nullableanno.
This feature was added in lombok v1.18.12.

Lombok can be configured to add @lombok.Generated annotations to all generated nodes where possible; useful for JaCoCo (which has built in support), diff --git a/website/templates/features/experimental/onX.html b/website/templates/features/experimental/onX.html index fd2e7b58..418a0a39 100644 --- a/website/templates/features/experimental/onX.html +++ b/website/templates/features/experimental/onX.html @@ -11,8 +11,6 @@

  • Ugly syntax. The syntax of this feature is not optimal, but it is the least convoluted syntax that could possibly work (for now!) -
  • - Possibly java 9 will offer (much) better ways of supporting this feature.
  • Uncertainty: Future versions of javac may break this feature, and we may not be able to restore it.
  • diff --git a/website/templates/setup/gradle.html b/website/templates/setup/gradle.html index 69e2e71f..0e79007b 100644 --- a/website/templates/setup/gradle.html +++ b/website/templates/setup/gradle.html @@ -17,17 +17,17 @@ <@s.section title="Gradle without a plugin">

    - If you don't want to use the plugin, gradle has the built-in compileOnly scope, which can be used to tell gradle to add lombok only during compilation. Your build.gradle will look like:

    +			If you don't want to use the plugin, gradle has the built-in compileOnly scope, which can be used to tell gradle to add lombok only during compilation. Your build.gradle or build.gradle.kts will look like:
     repositories {
     	mavenCentral()
     }
     
     dependencies {
    -	compileOnly 'org.projectlombok:lombok:${version}'
    -	annotationProcessor 'org.projectlombok:lombok:${version}'
    +	compileOnly("org.projectlombok:lombok:${version}")
    +	annotationProcessor("org.projectlombok:lombok:${version}")
     	
    -	testCompileOnly 'org.projectlombok:lombok:${version}'
    -	testAnnotationProcessor 'org.projectlombok:lombok:${version}'
    +	testCompileOnly("org.projectlombok:lombok:${version}")
    +	testAnnotationProcessor("org.projectlombok:lombok:${version}")
     }

    Remember that you still have to download lombok.jar (or find it in gradle's caches) and run it as a jarfile, if you wish to program in eclipse. The plugin makes that part easier. diff --git a/website/templates/setup/maven.html b/website/templates/setup/maven.html index f6c36822..27153cb3 100644 --- a/website/templates/setup/maven.html +++ b/website/templates/setup/maven.html @@ -22,20 +22,25 @@ </dependencies>

    - - - <@s.section title="JDK9+ with module-info.java">

    - The configuration of the compiler plug-in should contain the following:

    -<annotationProcessorPaths>
    -	<path>
    -		<groupId>org.projectlombok</groupId>
    -		<artifactId>lombok</artifactId>
    -		<version>${version}</version>
    -	</path>
    -</annotationProcessorPaths>
    -
    -

    + You also need to add lombok as an annotation processor. This is mandatory starting with JDK23, or JDK9+ if compiling as modules (you have module-info.java files in your build):
    +<build>
    +	<plugins>
    +		<plugin>
    +			<groupId>org.apache.maven.plugins</groupId>
    +			<artifactId>maven-compiler-plugin</artifactId>
    +			<configuration>
    +				<annotationProcessorPaths>
    +					<path>
    +						<groupId>org.projectlombok</groupId>
    +						<artifactId>lombok</artifactId>
    +						<version>${version}</version>
    +					</path>
    +				</annotationProcessorPaths>
    +			</configuration>
    +		</plugin>
    +	</plugins>
    +</build>
     	
     	
     	<@s.section title="Eclipse Compiler (ECJ/JDT)">
    diff --git a/website/usageExamples/experimental/onXExample_pre.jpage b/website/usageExamples/experimental/onXExample_pre.jpage
    index e54371ae..91dc09fe 100644
    --- a/website/usageExamples/experimental/onXExample_pre.jpage
    +++ b/website/usageExamples/experimental/onXExample_pre.jpage
    @@ -7,11 +7,9 @@ import javax.persistence.Id;
     import javax.persistence.Column;
     import javax.validation.constraints.Max;
     
    -@AllArgsConstructor(onConstructor=@__(@Inject))
    +@AllArgsConstructor(onConstructor_=@Inject)
     public class OnXExample {
    -//	@Getter(onMethod=@__({@Id, @Column(name="unique-id")})) //JDK7
    -//	@Setter(onParam=@__(@Max(10000))) //JDK7
    -	@Getter(onMethod_={@Id, @Column(name="unique-id")}) //JDK8
    -	@Setter(onParam_=@Max(10000)) //JDK8
    +	@Getter(onMethod_={@Id, @Column(name="unique-id")})
    +	@Setter(onParam_=@Max(10000))
     	private long unid;
     }