diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java index a9972f1e672..8c434c84edd 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java @@ -18,7 +18,6 @@ import com.sun.source.tree.*; import com.sun.source.util.TreePathScanner; -import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.DCTree; @@ -27,6 +26,7 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.util.Context; +import lombok.Generated; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; @@ -1519,9 +1519,11 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm // this is a lambda parameter with an inferred type expression typeExpr = null; } else { - boolean lombokVal = isLombokVal(node); + Space space = whitespace(); + boolean lombokVal = source.startsWith("val", cursor); + cursor += 3; // skip `val` or `var` typeExpr = new J.Identifier(randomId(), - sourceBefore(lombokVal ? "val" : "var"), + space, Markers.build(singletonList(JavaVarKeyword.build())), emptyList(), lombokVal ? "val" : "var", @@ -1684,8 +1686,8 @@ public J visitWildcard(WildcardTree node, Space fmt) { } private static int getActualStartPosition(JCTree t) { - // not sure if this is a bug in Lombok, but the variable's start position is after the `val` annotation - if (t instanceof JCVariableDecl && isLombokVal((JCVariableDecl) t)) { + // The variable's start position in the source is wrongly after lombok's `@val` annotation + if (t instanceof JCVariableDecl && isLombokGenerated(t)) { return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); } return t.getStartPosition(); @@ -1861,7 +1863,7 @@ private List> convertStatements(@Nullable List> treesGroupedByStartPosition = new LinkedHashMap<>(); for (Tree t : trees) { - if (isLombokGenerated(t)) { + if (!(t instanceof JCVariableDecl) && isLombokGenerated(t)) { continue; } treesGroupedByStartPosition.computeIfAbsent(((JCTree) t).getStartPosition(), k -> new ArrayList<>(1)).add(t); @@ -1893,51 +1895,23 @@ private List> convertStatements(@Nullable List "lombok.val".equals(a.type.toString())); } - return isLombokGenerated(sym); - } - private static boolean isLombokGenerated(@Nullable Symbol sym) { - if (sym == null) { - return false; - } - // Lombok val is represented as a @lombok.val on a "final" modifier, neither which appear in source - if ("lombok.val".equals(sym.getQualifiedName().toString())) { - return true; - } - if (sym.getMetadata() == null) { - return false; - } - for (Attribute.Compound a : sym.getDeclarationAttributes()) { - if ("lombok.Generated".equals(a.type.toString())) { - return true; - } - } - return false; + //noinspection ConstantConditions + return sym != null && ("lombok.val".equals(sym.getQualifiedName().toString()) || sym.getAnnotation(Generated.class) != null); } /** @@ -2117,7 +2091,6 @@ private ReloadableJava11ModifierResults sortedModifiersAndAnnotations(ModifiersT for (int i = cursor; i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { JCAnnotation jcAnnotation = annotationPosTable.get(i); - // Skip over lombok's "@val" annotation which does not actually appear in source if (isLombokGenerated(jcAnnotation.getAnnotationType())) { continue; } diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java index 738a3fe7c23..90ba684cfd1 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java @@ -19,7 +19,6 @@ import com.sun.source.doctree.DocCommentTree; import com.sun.source.tree.*; import com.sun.source.util.TreePathScanner; -import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.DocCommentTable; @@ -27,6 +26,7 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.util.Context; +import lombok.Generated; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; @@ -1597,9 +1597,11 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm // this is a lambda parameter with an inferred type expression typeExpr = null; } else { - boolean lombokVal = isLombokVal(node); + Space space = whitespace(); + boolean lombokVal = source.startsWith("val", cursor); + cursor += 3; // skip `val` or `var` typeExpr = new J.Identifier(randomId(), - sourceBefore(lombokVal ? "val" : "var"), + space, Markers.build(singletonList(JavaVarKeyword.build())), emptyList(), lombokVal ? "val" : "var", @@ -1747,6 +1749,14 @@ public J visitWildcard(WildcardTree node, Space fmt) { } } + private static int getActualStartPosition(JCTree t) { + // The variable's start position in the source is wrongly after lombok's `@val` annotation + if (t instanceof JCVariableDecl && isLombokGenerated(t)) { + return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); + } + return t.getStartPosition(); + } + private void reportJavaParsingException(Throwable ex) { // this SHOULD never happen, but is here simply as a diagnostic measure in the event of unexpected exceptions StringBuilder message = new StringBuilder("Failed to convert for the following cursor stack:"); @@ -1771,14 +1781,6 @@ private void reportJavaParsingException(Throwable ex) { ctx.getOnError().accept(new JavaParsingException(message.toString(), ex)); } - private static int getActualStartPosition(JCTree t) { - // not sure if this is a bug in Lombok, but the variable's start position is after the `val` annotation - if (t instanceof JCVariableDecl && isLombokVal((JCVariableDecl) t)) { - return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); - } - return t.getStartPosition(); - } - private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix) { return convert(t, suffix, j -> Markers.EMPTY); } @@ -1942,7 +1944,7 @@ private List> convertStatements(@Nullable List> treesGroupedByStartPosition = new LinkedHashMap<>(); for (Tree t : trees) { - if (isLombokGenerated(t)) { + if (!(t instanceof JCVariableDecl) && isLombokGenerated(t)) { continue; } treesGroupedByStartPosition.computeIfAbsent(((JCTree) t).getStartPosition(), k -> new ArrayList<>(1)).add(t); @@ -1974,51 +1976,23 @@ private List> convertStatements(@Nullable List "lombok.val".equals(a.type.toString())); } - return isLombokGenerated(sym); - } - private static boolean isLombokGenerated(@Nullable Symbol sym) { - if (sym == null) { - return false; - } - // Lombok val is represented as a @lombok.val on a "final" modifier, neither which appear in source - if ("lombok.val".equals(sym.getQualifiedName().toString())) { - return true; - } - if (sym.getMetadata() == null) { - return false; - } - for (Attribute.Compound a : sym.getDeclarationAttributes()) { - if ("lombok.Generated".equals(a.type.toString())) { - return true; - } - } - return false; + //noinspection ConstantConditions + return sym != null && ("lombok.val".equals(sym.getQualifiedName().toString()) || sym.getAnnotation(Generated.class) != null); } /** @@ -2199,7 +2173,6 @@ private ReloadableJava17ModifierResults sortedModifiersAndAnnotations(ModifiersT for (int i = cursor; i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { JCAnnotation jcAnnotation = annotationPosTable.get(i); - // Skip over lombok's "@val" annotation which does not actually appear in source if (isLombokGenerated(jcAnnotation.getAnnotationType())) { continue; } diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java index b49c1f1563a..da4ad8b8acd 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java @@ -19,7 +19,6 @@ import com.sun.source.doctree.DocCommentTree; import com.sun.source.tree.*; import com.sun.source.util.TreePathScanner; -import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.tree.DocCommentTable; @@ -27,6 +26,7 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.util.Context; +import lombok.Generated; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; @@ -1597,9 +1597,11 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm // this is a lambda parameter with an inferred type expression typeExpr = null; } else { - boolean lombokVal = isLombokVal(node); + Space space = whitespace(); + boolean lombokVal = source.startsWith("val", cursor); + cursor += 3; // skip `val` or `var` typeExpr = new J.Identifier(randomId(), - sourceBefore(lombokVal ? "val" : "var"), + space, Markers.build(singletonList(JavaVarKeyword.build())), emptyList(), lombokVal ? "val" : "var", @@ -1747,6 +1749,14 @@ public J visitWildcard(WildcardTree node, Space fmt) { } } + private static int getActualStartPosition(JCTree t) { + // The variable's start position in the source is wrongly after lombok's `@val` annotation + if (t instanceof JCVariableDecl && isLombokGenerated(t)) { + return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); + } + return t.getStartPosition(); + } + private void reportJavaParsingException(Throwable ex) { // this SHOULD never happen, but is here simply as a diagnostic measure in the event of unexpected exceptions StringBuilder message = new StringBuilder("Failed to convert for the following cursor stack:"); @@ -1771,14 +1781,6 @@ private void reportJavaParsingException(Throwable ex) { ctx.getOnError().accept(new JavaParsingException(message.toString(), ex)); } - private static int getActualStartPosition(JCTree t) { - // not sure if this is a bug in Lombok, but the variable's start position is after the `val` annotation - if (t instanceof JCVariableDecl && isLombokVal((JCVariableDecl) t)) { - return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); - } - return t.getStartPosition(); - } - private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix) { return convert(t, suffix, j -> Markers.EMPTY); } @@ -1942,7 +1944,7 @@ private List> convertStatements(@Nullable List> treesGroupedByStartPosition = new LinkedHashMap<>(); for (Tree t : trees) { - if (isLombokGenerated(t)) { + if (!(t instanceof JCVariableDecl) && isLombokGenerated(t)) { continue; } treesGroupedByStartPosition.computeIfAbsent(((JCTree) t).getStartPosition(), k -> new ArrayList<>(1)).add(t); @@ -1974,51 +1976,23 @@ private List> convertStatements(@Nullable List "lombok.val".equals(a.type.toString())); } - return isLombokGenerated(sym); - } - private static boolean isLombokGenerated(@Nullable Symbol sym) { - if (sym == null) { - return false; - } - // Lombok val is represented as a @lombok.val on a "final" modifier, neither which appear in source - if ("lombok.val".equals(sym.getQualifiedName().toString())) { - return true; - } - if (sym.getMetadata() == null) { - return false; - } - for (Attribute.Compound a : sym.getDeclarationAttributes()) { - if ("lombok.Generated".equals(a.type.toString())) { - return true; - } - } - return false; + //noinspection ConstantConditions + return sym != null && ("lombok.val".equals(sym.getQualifiedName().toString()) || sym.getAnnotation(Generated.class) != null); } /** @@ -2199,7 +2173,6 @@ private ReloadableJava21ModifierResults sortedModifiersAndAnnotations(ModifiersT for (int i = cursor; i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { JCAnnotation jcAnnotation = annotationPosTable.get(i); - // Skip over lombok's "@val" annotation which does not actually appear in source if (isLombokGenerated(jcAnnotation.getAnnotationType())) { continue; } diff --git a/rewrite-java-8/build.gradle.kts b/rewrite-java-8/build.gradle.kts index 5f92a5f85dd..e07e976892e 100644 --- a/rewrite-java-8/build.gradle.kts +++ b/rewrite-java-8/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { compileOnly("org.slf4j:slf4j-api:1.7.+") implementation(project(":rewrite-java")) + runtimeOnly(project(":rewrite-java-lombok")) implementation("org.ow2.asm:asm:latest.release") implementation("io.micrometer:micrometer-core:1.9.+") diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java index de46c937d0a..bad6fc19a4d 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8Parser.java @@ -18,12 +18,12 @@ import com.sun.tools.javac.comp.*; import com.sun.tools.javac.file.JavacFileManager; import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.main.Option; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Options; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Timer; +import lombok.Getter; import org.jspecify.annotations.Nullable; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -31,7 +31,6 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.SourceFile; -import org.openrewrite.internal.MetricsHelper; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.tree.J; @@ -40,19 +39,25 @@ import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; +import org.slf4j.LoggerFactory; +import javax.annotation.processing.Processor; import javax.tools.*; import java.io.*; +import java.lang.reflect.Constructor; import java.net.URI; +import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static com.sun.tools.javac.util.List.nil; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; class ReloadableJava8Parser implements JavaParser { @@ -70,6 +75,7 @@ class ReloadableJava8Parser implements JavaParser { private final JavaCompiler compiler; private final ResettableLog compilerLog; private final Collection styles; + private final List annotationProcessors; ReloadableJava8Parser(@Nullable Collection classpath, Collection classBytesClasspath, @@ -100,6 +106,70 @@ class ReloadableJava8Parser implements JavaParser { Options.instance(context).put("-g", "-g"); Options.instance(context).put("-proc", "none"); + // Ensure type attribution continues despite errors in individual files or nodes. + // If an error occurs in a single file or node, type attribution should still proceed + // for all other source files and unaffected nodes within the same file. + Options.instance(context).put("should-stop.ifError", "GENERATE"); + + LOMBOK: + if (System.getenv().getOrDefault("REWRITE_LOMBOK", System.getProperty("rewrite.lombok")) != null && + classpath != null && classpath.stream().anyMatch(it -> it.toString().contains("lombok"))) { + Processor lombokProcessor = null; + try { + // https://projectlombok.org/contributing/lombok-execution-path + List overrideClasspath = new ArrayList<>(); + for (Path part : classpath) { + if (part.toString().contains("lombok")) { + overrideClasspath.add(part.toString()); + } + } + // make sure the rewrite-java-lombok dependency comes first + boolean found = false; + for (int i = 0; i < overrideClasspath.size(); i++) { + if (overrideClasspath.get(i).contains("rewrite-java-lombok")) { + overrideClasspath.add(0, overrideClasspath.remove(i)); + found = true; + } + } + if (!found) { + // try to find `rewrite-java-lombok` using class loader + URL resource = getClass().getClassLoader().getResource("org/openrewrite/java/lombok/OpenRewriteConfigurationKeysLoader.class"); + if (resource != null && resource.getProtocol().equals("jar") && resource.getPath().startsWith("file:")) { + String path = Paths.get(URI.create(resource.getPath().substring(0, resource.getPath().indexOf("!")))).toString(); + overrideClasspath.add(0, path); + } else { + break LOMBOK; + } + } + System.setProperty("shadow.override.lombok", String.join(File.pathSeparator, overrideClasspath)); + + Class shadowLoaderClass = Class.forName("lombok.launch.ShadowClassLoader", true, getClass().getClassLoader()); + Constructor shadowLoaderConstructor = shadowLoaderClass.getDeclaredConstructor( + Class.forName("java.lang.ClassLoader"), + Class.forName("java.lang.String"), + Class.forName("java.lang.String"), + Class.forName("java.util.List"), + Class.forName("java.util.List")); + shadowLoaderConstructor.setAccessible(true); + + ClassLoader lombokShadowLoader = (ClassLoader) shadowLoaderConstructor.newInstance( + getClass().getClassLoader(), + "lombok", + null, + emptyList(), + singletonList("lombok.patcher.Symbols") + ); + lombokProcessor = (Processor) lombokShadowLoader.loadClass("lombok.core.AnnotationProcessor").getDeclaredConstructor().newInstance(); + Options.instance(context).put(Option.PROCESSOR, "lombok.launch.AnnotationProcessorHider$AnnotationProcessor"); + } catch (ReflectiveOperationException ignore) { + // Lombok was not found or could not be initialized + } finally { + annotationProcessors = lombokProcessor != null ? singletonList(lombokProcessor) : emptyList(); + } + } else { + annotationProcessors = emptyList(); + } + // MUST be created (registered with the context) after pfm and compilerLog compiler = new JavaCompiler(context); @@ -118,7 +188,7 @@ public void write(char[] cbuf, int off, int len) { if (logCompilationWarningsAndErrors) { String log = new String(Arrays.copyOfRange(cbuf, off, len)); if (!StringUtils.isBlank(log)) { - org.slf4j.LoggerFactory.getLogger(ReloadableJava8Parser.class).warn(log); + LoggerFactory.getLogger(ReloadableJava8Parser.class).warn(log); } } } @@ -138,45 +208,7 @@ public void close() { @Override public Stream parseInputs(Iterable sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - if (classpath != null) { // override classpath - if (context.get(JavaFileManager.class) != pfm) { - throw new IllegalStateException("JavaFileManager has been forked unexpectedly"); - } - - try { - pfm.setLocation(StandardLocation.CLASS_PATH, classpath.stream().map(Path::toFile).collect(toList())); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @SuppressWarnings("ConstantConditions") LinkedHashMap cus = acceptedInputs(sourceFiles) - .collect(Collectors.toMap( - Function.identity(), - input -> { - try { - return compiler.parse(new Java8ParserInputFileObject(input, ctx)); - } catch (IllegalStateException e) { - if ("endPosTable already set".equals(e.getMessage())) { - throw new IllegalStateException( - "Call reset() on JavaParser before parsing another set of source files that " + - "have some of the same fully qualified names. Source file [" + - input.getPath() + "]\n[\n" + StringUtils.readFully(input.getSource(ctx), getCharset(ctx)) + "\n]", e); - } - throw e; - } - }, - (e2, e1) -> e1, LinkedHashMap::new)); - - try { - enterAll(cus.values()); - compiler.attribute(new TimedTodo(compiler.todo)); - } catch (Throwable t) { - // when symbol entering fails on problems like missing types, attribution can often times proceed - // unhindered, but it sometimes cannot (so attribution is always a BEST EFFORT in the presence of errors) - ctx.getOnError().accept(new JavaParsingException("Failed symbol entering or attribution", t)); - } - + LinkedHashMap cus = parseInputsToCompilerAst(sourceFiles, ctx); return cus.entrySet().stream().map(cuByPath -> { Input input = cuByPath.getKey(); parsingListener.startedParsing(input); @@ -190,6 +222,7 @@ public Stream parseInputs(Iterable sourceFiles, @Nullable Pat ctx, context); J.CompilationUnit cu = (J.CompilationUnit) parser.scan(cuByPath.getValue(), Space.EMPTY); + //noinspection DataFlowIssue cuByPath.setValue(null); // allow memory used by this JCCompilationUnit to be released parsingListener.parsed(input, cu); return requirePrintEqualsInput(cu, input, relativeTo, ctx); @@ -200,6 +233,53 @@ public Stream parseInputs(Iterable sourceFiles, @Nullable Pat }); } + LinkedHashMap parseInputsToCompilerAst(Iterable sourceFiles, ExecutionContext ctx) { + if (classpath != null) { // override classpath + if (context.get(JavaFileManager.class) != pfm) { + throw new IllegalStateException("JavaFileManager has been forked unexpectedly"); + } + + try { + pfm.setLocation(StandardLocation.CLASS_PATH, classpath.stream().map(Path::toFile).collect(toList())); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + LinkedHashMap cus = new LinkedHashMap<>(); + List inputFileObjects = acceptedInputs(sourceFiles) + .map(input -> new Java8ParserInputFileObject(input, ctx)) + .collect(toList()); + if (!annotationProcessors.isEmpty()) { + compiler.initProcessAnnotations(annotationProcessors); + } + try { + //noinspection unchecked + com.sun.tools.javac.util.List jcCompilationUnits = compiler.parseFiles((List) (List) inputFileObjects); + for (int i = 0; i < inputFileObjects.size(); i++) { + cus.put(inputFileObjects.get(i).getInput(), jcCompilationUnits.get(i)); + } + try { + enterAll(cus.values()); + JavaCompiler delegate = annotationProcessors.isEmpty() ? compiler : compiler.processAnnotations(jcCompilationUnits, nil()); + delegate.attribute(delegate.todo); + } catch (Throwable t) { + // when symbol entering fails on problems like missing types, attribution can often times proceed + // unhindered, but it sometimes cannot (so attribution is always best-effort in the presence of errors) + ctx.getOnError().accept(new JavaParsingException("Failed symbol entering or attribution", t)); + } + } catch (IllegalStateException e) { + if ("endPosTable already set".equals(e.getMessage())) { + throw new IllegalStateException( + "Call reset() on JavaParser before parsing another set of source files that " + + "have some of the same fully qualified names.", e); + } + throw e; + } + + return cus; + } + @Override public ReloadableJava8Parser reset() { typeCache.clear(); @@ -261,35 +341,6 @@ public void reset(Collection uris) { } } - private static class TimedTodo extends Todo { - private final Todo todo; - private Timer.@Nullable Sample sample; - - private TimedTodo(Todo todo) { - super(new Context()); - this.todo = todo; - } - - @Override - public boolean isEmpty() { - if (sample != null) { - sample.stop(MetricsHelper.successTags( - Timer.builder("rewrite.parse") - .description("The time spent by the JDK in type attributing the source file") - .tag("file.type", "Java") - .tag("step", "(2) Type attribution")) - .register(Metrics.globalRegistry)); - } - return todo.isEmpty(); - } - - @Override - public Env remove() { - this.sample = Timer.start(); - return todo.remove(); - } - } - private static class ByteArrayCapableJavacFileManager extends JavacFileManager { private final List classByteClasspath; @@ -332,6 +383,7 @@ public Iterable list(Location location, String packageName, Set< private static class PackageAwareJavaFileObject extends SimpleJavaFileObject { private final String pkg; + @Getter private final String className; private final byte[] classBytes; @@ -365,10 +417,6 @@ public String getPackage() { return pkg; } - public String getClassName() { - return className; - } - @Override public InputStream openInputStream() { return new ByteArrayInputStream(classBytes); diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java index 6b944cff74a..0d6cf8309fc 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java @@ -25,6 +25,7 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.util.Context; +import lombok.Generated; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; @@ -33,7 +34,6 @@ import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.internal.JavaTypeCache; -import org.openrewrite.java.JavaPrinter; import org.openrewrite.java.marker.OmitParentheses; import org.openrewrite.java.marker.TrailingComma; import org.openrewrite.java.tree.*; @@ -339,7 +339,7 @@ public J visitCase(CaseTree node, Space fmt) { node.getExpression() == null ? EMPTY : sourceBefore("case"), singletonList(node.getExpression() == null ? JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null)) : - JRightPadded.build(convertOrNull(node.getExpression())) + JRightPadded.build(convert(node.getExpression())) ), Markers.EMPTY ), @@ -391,7 +391,7 @@ public J visitClass(ClassTree node, Space fmt) { Markers.EMPTY); JLeftPadded extendings = node.getExtendsClause() == null ? null : - padLeft(sourceBefore("extends"), convertOrNull(node.getExtendsClause())); + padLeft(sourceBefore("extends"), convert(node.getExtendsClause())); JContainer implementings = null; if (node.getImplementsClause() != null && !node.getImplementsClause().isEmpty()) { @@ -659,7 +659,7 @@ public J visitForLoop(ForLoopTree node, Space fmt) { commaDelim.apply(t) ); - JRightPadded condition = convertOrNull(node.getCondition(), semiDelim); + JRightPadded condition = convert(node.getCondition(), semiDelim); if (condition == null) { condition = padRight(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), EMPTY); } @@ -927,7 +927,7 @@ public J visitMethod(MethodTree node, Space fmt) { } List returnTypeAnnotations = collectAnnotations(annotationPosTable); - TypeTree returnType = convertOrNull(node.getReturnType()); + TypeTree returnType = convert(node.getReturnType()); if (returnType != null && !returnTypeAnnotations.isEmpty()) { returnType = new J.AnnotatedType(randomId(), Space.EMPTY, Markers.EMPTY, returnTypeAnnotations, returnType); @@ -970,7 +970,7 @@ public J visitMethod(MethodTree node, Space fmt) { JContainer.build(sourceBefore("throws"), convertAll(node.getThrows(), commaDelim, noDelim), Markers.EMPTY); - J.Block body = convertOrNull(node.getBody()); + J.Block body = convert(node.getBody()); JLeftPadded defaultValue = node.getDefaultValue() == null ? null : padLeft(sourceBefore("default"), convert(node.getDefaultValue())); @@ -994,9 +994,9 @@ public J visitNewArray(NewArrayTree node, Space fmt) { while (elementType instanceof JCArrayTypeTree) { elementType = ((JCArrayTypeTree) elementType).elemtype; } - typeExpr = convertOrNull(elementType); + typeExpr = convert(elementType); } else { - typeExpr = convertOrNull(jcVarType); + typeExpr = convert(jcVarType); } List nodeDimensions = node.getDimensions(); @@ -1053,7 +1053,7 @@ public J visitNewClass(NewClassTree node, Space fmt) { } // for enum definitions with anonymous class initializers, endPos of node identifier will be -1 - TypeTree clazz = endPos(node.getIdentifier()) >= 0 ? convertOrNull(node.getIdentifier()) : null; + TypeTree clazz = endPos(node.getIdentifier()) >= 0 ? convert(node.getIdentifier()) : null; JContainer args; if (positionOfNext("(", '{') > -1) { @@ -1161,7 +1161,8 @@ public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { @Override public J visitReturn(ReturnTree node, Space fmt) { skip("return"); - return new J.Return(randomId(), fmt, Markers.EMPTY, convertOrNull(node.getExpression())); + Expression expression = convert(node.getExpression()); + return new J.Return(randomId(), fmt, Markers.EMPTY, expression); } @Override @@ -1506,6 +1507,17 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm TypeTree typeExpr; if (vartype == null || endPos(vartype) < 0 || vartype instanceof JCErroneous) { typeExpr = null; // this is a lambda parameter with an inferred type expression + } else if (isLombokGenerated(node)) { + Space space = whitespace(); + boolean lombokVal = source.startsWith("val", cursor); + cursor += 3; // skip `val` or `var` + typeExpr = new J.Identifier(randomId(), + space, + Markers.build(singletonList(JavaVarKeyword.build())), + emptyList(), + lombokVal ? "val" : "var", + typeMapping.type(vartype), + null); } else if (vartype instanceof JCArrayTypeTree) { JCExpression elementType = vartype; while (elementType instanceof JCArrayTypeTree || elementType instanceof JCAnnotatedType) { @@ -1542,10 +1554,9 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm List> vars = new ArrayList<>(nodes.size()); for (int i = 0; i < nodes.size(); i++) { - VariableTree n = nodes.get(i); + JCVariableDecl n = (JCVariableDecl) nodes.get(i); Space namedVarPrefix = sourceBefore(n.getName().toString()); - JCVariableDecl vd = (JCVariableDecl) n; JavaType type = typeMapping.type(n); J.Identifier name = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), n.getName().toString(), @@ -1558,7 +1569,7 @@ private J.VariableDeclarations visitVariables(List nodes, Space fm new J.VariableDeclarations.NamedVariable(randomId(), namedVarPrefix, Markers.EMPTY, name, dimensionsAfterName, - vd.init != null ? padLeft(sourceBefore("="), convertOrNull(vd.init)) : null, + n.init != null ? padLeft(sourceBefore("="), convert(n.init)) : null, (JavaType.Variable) typeMapping.type(n) ), i == nodes.size() - 1 ? EMPTY : sourceBefore(",") @@ -1615,7 +1626,7 @@ public J visitWildcard(WildcardTree node, Space fmt) { bound = null; } - return new J.Wildcard(randomId(), fmt, Markers.EMPTY, bound, convertOrNull(wildcard.inner)); + return new J.Wildcard(randomId(), fmt, Markers.EMPTY, bound, convert(wildcard.inner)); } /** @@ -1623,9 +1634,12 @@ public J visitWildcard(WildcardTree node, Space fmt) { * Conversion utilities * -------------- */ - private J2 convert(Tree t) { + private @Nullable J2 convert(@Nullable Tree t) { + if (t == null) { + return null; + } try { - String prefix = source.substring(cursor, max(((JCTree) t).getStartPosition(), cursor)); + String prefix = source.substring(cursor, Math.max(cursor, getActualStartPosition((JCTree) t))); cursor += prefix.length(); @SuppressWarnings("unchecked") J2 j = (J2) scan(t, formatWithCommentTree(prefix, (JCTree) t, docCommentTable.getCommentTree((JCTree) t))); return j; @@ -1655,11 +1669,25 @@ private J2 convert(Tree t) { } } - private JRightPadded convert(Tree t, Function suffix) { + private static int getActualStartPosition(JCTree t) { + // not sure if this is a bug in Lombok, but the variable's start position is after the `val` annotation + if (t instanceof JCVariableDecl && isLombokGenerated(t)) { + return ((JCVariableDecl) t).mods.annotations.get(0).getStartPosition(); + } + return t.getStartPosition(); + } + + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix) { + if (t == null) { + return null; + } return convert(t, suffix, j -> Markers.EMPTY); } - private JRightPadded convert(Tree t, Function suffix, Function markers) { + private @Nullable JRightPadded convert(@Nullable Tree t, Function suffix, Function markers) { + if (t == null) { + return null; + } J2 j = convert(t); @SuppressWarnings("ConstantConditions") JRightPadded rightPadded = j == null ? null : new JRightPadded<>(j, suffix.apply(t), markers.apply(t)); @@ -1710,14 +1738,6 @@ private long lineNumber(Tree tree) { return lineNumber; } - private @Nullable T convertOrNull(@Nullable Tree t) { - return t == null ? null : convert(t); - } - - private @Nullable JRightPadded convertOrNull(@Nullable Tree t, Function suffix) { - return t == null ? null : convert(t, suffix); - } - private List convertAll(List trees) { List converted = new ArrayList<>(trees.size()); for (Tree tree : trees) { @@ -1836,6 +1856,9 @@ private List> convertStatements(@Nullable List> treesGroupedByStartPosition = new LinkedHashMap<>(); for (Tree t : trees) { + if (!(t instanceof JCVariableDecl) && isLombokGenerated(t)) { + continue; + } treesGroupedByStartPosition.computeIfAbsent(((JCTree) t).getStartPosition(), k -> new ArrayList<>(1)).add(t); } @@ -1865,6 +1888,32 @@ private List> convertStatements(@Nullable List "@lombok.Generated()".equals(a.toString())); + } + } else if (tree instanceof JCTree.JCClassDecl) { + sym = ((JCClassDecl) tree).sym; + } else if (tree instanceof JCTree.JCVariableDecl) { + sym = ((JCVariableDecl) tree).sym; + return sym != null && sym.getDeclarationAttributes().stream().anyMatch(a -> "lombok.val".equals(a.type.toString()) || "lombok.var".equals(a.type.toString())); + } + + //noinspection ConstantConditions + return sym != null && ( + "lombok.val".equals(sym.getQualifiedName().toString()) || "lombok.var".equals(sym.getQualifiedName().toString()) || + sym.getAnnotation(Generated.class) != null + ); + } + /** * -------------- * Other convenience utilities @@ -2040,13 +2089,17 @@ private Java8ModifierResults sortedModifiersAndAnnotations(ModifiersTree modifie int lastAnnotationPosition = cursor; for (int i = cursor; i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { - J.Annotation annotation = convert(annotationPosTable.get(i)); + JCAnnotation jcAnnotation = annotationPosTable.get(i); + if (isLombokGenerated(jcAnnotation.getAnnotationType())) { + continue; + } + J.Annotation annotation = convert(jcAnnotation); if (afterFirstModifier) { currentAnnotations.add(annotation); } else { leadingAnnotations.add(annotation); } - i = cursor -1; + i = cursor - 1; lastAnnotationPosition = cursor; continue; } @@ -2160,7 +2213,11 @@ private List collectAnnotations(Map annotat boolean inMultilineComment = false; for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) { if (annotationPosTable.containsKey(i)) { - annotations.add(convert(annotationPosTable.get(i))); + JCAnnotation jcAnnotation = annotationPosTable.get(i); + if (isLombokGenerated(jcAnnotation)) { + continue; + } + annotations.add(convert(jcAnnotation)); i = cursor; continue; } diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/package-info.java b/rewrite-java-8/src/main/java/org/openrewrite/java/package-info.java new file mode 100644 index 00000000000..e697a3e66c4 --- /dev/null +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2020 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NullMarked +@NonNullFields +package org.openrewrite.java; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/AllArgsConstructorHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/AllArgsConstructorHandler.java index b629bee724a..b4b04d39c1d 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/AllArgsConstructorHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/AllArgsConstructorHandler.java @@ -40,7 +40,6 @@ public void handle(AnnotationValues annotationValues, JCTree JCTree.JCIdent ident = (JCTree.JCIdent) assign.getVariable(); String name = ident.getName().toString(); if (name.equals("onConstructor") || name.equals("onConstructor_")) { - // In Java 1.8+ the parameter is `onConstructor_` continue; } } diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodNoOpHandler.java similarity index 91% rename from rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodHandler.java rename to rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodNoOpHandler.java index 68b5543da96..dbc6be1dcf4 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/ExtensionMethodNoOpHandler.java @@ -21,7 +21,7 @@ import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; -public class ExtensionMethodHandler extends JavacAnnotationHandler { +public class ExtensionMethodNoOpHandler extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotationValues, JCTree.JCAnnotation jcAnnotation, JavacNode javacNode) { } diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperNoOpHandler.java similarity index 93% rename from rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperHandler.java rename to rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperNoOpHandler.java index d1ef2b9fa59..6b7a1be1085 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/HelperNoOpHandler.java @@ -21,7 +21,7 @@ import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; -public class HelperHandler extends JavacAnnotationHandler { +public class HelperNoOpHandler extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCTree.JCAnnotation ast, JavacNode annotationNode) { } diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedNoOpHandler.java similarity index 93% rename from rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedHandler.java rename to rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedNoOpHandler.java index a9ce02cd671..bd66517d6ff 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/JacksonizedNoOpHandler.java @@ -24,7 +24,7 @@ @SuppressWarnings("SpellCheckingInspection") @HandlerPriority(-512) -public class JacksonizedHandler extends JavacAnnotationHandler { +public class JacksonizedNoOpHandler extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCTree.JCAnnotation ast, JavacNode annotationNode) { } diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/NoArgsConstructorHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/NoArgsConstructorHandler.java index b5942688f46..1acfcfb96a1 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/NoArgsConstructorHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/NoArgsConstructorHandler.java @@ -38,7 +38,8 @@ public void handle(AnnotationValues annotationValues, JCTree. if (originalArg instanceof JCTree.JCAssign && ((JCTree.JCAssign) originalArg).getVariable() instanceof JCTree.JCIdent) { JCTree.JCAssign assign = (JCTree.JCAssign) originalArg; JCTree.JCIdent ident = (JCTree.JCIdent) assign.getVariable(); - if ("onConstructor".equals(ident.getName().toString())) { + String name = ident.getName().toString(); + if (name.equals("onConstructor") || name.equals("onConstructor_")) { continue; } } diff --git a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/RequiredArgsConstructorHandler.java b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/RequiredArgsConstructorHandler.java index e20877acf3d..c376de6bf22 100644 --- a/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/RequiredArgsConstructorHandler.java +++ b/rewrite-java-lombok/src/main/java/org/openrewrite/java/lombok/RequiredArgsConstructorHandler.java @@ -38,7 +38,8 @@ public void handle(AnnotationValues annotationValues, J if (originalArg instanceof JCTree.JCAssign && ((JCTree.JCAssign) originalArg).getVariable() instanceof JCTree.JCIdent) { JCTree.JCAssign assign = (JCTree.JCAssign) originalArg; JCTree.JCIdent ident = (JCTree.JCIdent) assign.getVariable(); - if ("onConstructor".equals(ident.getName().toString())) { + String name = ident.getName().toString(); + if (name.equals("onConstructor") || name.equals("onConstructor_")) { continue; } } diff --git a/rewrite-java-lombok/src/main/resources/META-INF/services/lombok.javac.JavacAnnotationHandler b/rewrite-java-lombok/src/main/resources/META-INF/services/lombok.javac.JavacAnnotationHandler index 322964dfb1b..b556f06f95e 100644 --- a/rewrite-java-lombok/src/main/resources/META-INF/services/lombok.javac.JavacAnnotationHandler +++ b/rewrite-java-lombok/src/main/resources/META-INF/services/lombok.javac.JavacAnnotationHandler @@ -17,10 +17,10 @@ org.openrewrite.java.lombok.AllArgsConstructorHandler org.openrewrite.java.lombok.BuilderHandler org.openrewrite.java.lombok.BuilderDefaultNoOpHandler org.openrewrite.java.lombok.CleanupNoOpHandler -org.openrewrite.java.lombok.ExtensionMethodHandler +org.openrewrite.java.lombok.ExtensionMethodNoOpHandler org.openrewrite.java.lombok.GetterHandler -org.openrewrite.java.lombok.HelperHandler -org.openrewrite.java.lombok.JacksonizedHandler +org.openrewrite.java.lombok.HelperNoOpHandler +org.openrewrite.java.lombok.JacksonizedNoOpHandler org.openrewrite.java.lombok.LockedNoOpHandler org.openrewrite.java.lombok.LockedReadNoOpHandler org.openrewrite.java.lombok.LockedWriteNoOpHandler diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LombokTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LombokTest.java index 6a355210012..aab176f0a7f 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LombokTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LombokTest.java @@ -19,9 +19,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnJre; -import org.junit.jupiter.api.condition.JRE; import org.openrewrite.java.JavaParser; +import org.openrewrite.java.MinimumJava11; import org.openrewrite.java.search.FindMissingTypes; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -34,7 +33,6 @@ import static org.openrewrite.java.Assertions.java; @SuppressWarnings({"CaughtExceptionImmediatelyRethrown", "LombokGetterMayBeUsed", "LombokSetterMayBeUsed", "DefaultAnnotationParam", "NotNullFieldNotInitialized", "ProtectedMemberInFinalClass", "WriteOnlyObject", "ConcatenationWithEmptyString"}) -@EnabledOnJre({JRE.JAVA_11, JRE.JAVA_17, JRE.JAVA_21}) class LombokTest implements RewriteTest { @BeforeAll @@ -248,6 +246,12 @@ public class ConstructorExample { public static class NoArgsExample { @NonNull private String field; } + + public void test() { + ConstructorExample x = ConstructorExample.of("desc"); + ConstructorExample y = new ConstructorExample<>("1L"); + ConstructorExample.NoArgsExample z = new ConstructorExample.NoArgsExample(); + } } """ ) @@ -437,7 +441,7 @@ public class SingularExample { } @Test - void jul() { + void log() { rewriteRun( java( """ @@ -459,6 +463,23 @@ void m() { ); } + @Test + void var() { + rewriteRun( + java( + """ + import lombok.var; + + class Test { + void test() { + var s = "foo"; + } + } + """ + ) + ); + } + @Test void val() { rewriteRun( @@ -696,6 +717,7 @@ public void both(String s) { } @Test + @MinimumJava11 void jacksonized() { rewriteRun( spec -> spec.parser(JavaParser.fromJavaVersion().classpath("jackson-annotations", "lombok")), @@ -725,6 +747,11 @@ void standardException() { @StandardException public class ExampleException extends Exception { + public void test() { + new ExampleException("message"); + new ExampleException(new RuntimeException("message")); + new ExampleException("message", new RuntimeException("message")); + } } """ ) @@ -732,29 +759,27 @@ public class ExampleException extends Exception { } @Test + @MinimumJava11 void onConstructor() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.builder().allowMissingType(o -> { - assert o instanceof FindMissingTypes.MissingTypeResult; - FindMissingTypes.MissingTypeResult result = (FindMissingTypes.MissingTypeResult) o; - // type attribution is missing for annotation args, as it was intentionally removed for processing. - return result.getPath().startsWith("Identifier->Annotation->"); - }).build()), + java( + """ + public @interface Inject {} + public @interface Id {} + public @interface Column { String name(); } + public @interface Max { long value(); } + """ + ), java( """ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; - import javax.inject.Inject; - 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")}) //JDK8 - @Setter(onParam_=@Max(10000)) //JDK8 + @Getter(onMethod_={@Id, @Column(name="unique-id")}) + @Setter(onParam_=@Max(10000)) private long unid; public void test() { @@ -769,17 +794,14 @@ public void test() { } @Test + @MinimumJava11 void onConstructorNoArgs() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.builder().allowMissingType(o -> { - assert o instanceof FindMissingTypes.MissingTypeResult; - FindMissingTypes.MissingTypeResult result = (FindMissingTypes.MissingTypeResult) o; - if (result.getJ() instanceof J.Identifier identifier) { - // type attribution is missing for annotation args, as it was intentionally removed for processing. - return identifier.getSimpleName().equals("__") || identifier.getSimpleName().equals("Inject"); - } - return false; - }).build()), + java( + """ + public @interface Inject {} + """ + ), java( """ import lombok.NoArgsConstructor; @@ -788,8 +810,8 @@ void onConstructorNoArgs() { import javax.inject.Inject; - @NoArgsConstructor(onConstructor = @__(@Inject)) - @RequiredArgsConstructor(onConstructor = @__(@Inject)) + @NoArgsConstructor(onConstructor_ = @Inject) + @RequiredArgsConstructor(onConstructor_ = @Inject) public class OnXExample { @NonNull private Long unid; @@ -810,6 +832,127 @@ public void test() { @SuppressWarnings("MismatchedReadAndWriteOfArray") @Nested class LessSupported { + /* + java 8 cannot figure out all type checking: + - When the @AllArgsConstructorHandler, @NoArgsConstructorHandler and @NoArgsConstructorHandler annotations are + used with the `onConstructor_` param, Lombok does not call the JavacAnnotationHandlers. + - The @Jacksonized annotation does somehow turns into `ClassDeclaration->CompilationUni` error + */ + + @Test + // TODO: Find solution and remove this test + void jacksonizedForJava8() { + rewriteRun( + spec -> spec + .parser(JavaParser.fromJavaVersion().classpath("jackson-annotations", "lombok")) + .typeValidationOptions(TypeValidation.builder().allowMissingType(o -> { + assert o instanceof FindMissingTypes.MissingTypeResult; + FindMissingTypes.MissingTypeResult result = (FindMissingTypes.MissingTypeResult) o; + // Using the @Jacksonized annotation in java 8 just breaks it all + return result.getPath().startsWith("ClassDeclaration->CompilationUnit") || + result.getPath().startsWith("Identifier->Annotation")|| + result.getPath().startsWith("Identifier->ParameterizedType"); + }).build()), + java( + """ + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + import lombok.Builder; + import lombok.extern.jackson.Jacksonized; + + @Jacksonized + @Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public class JacksonExample { + private List strings; + } + """ + ) + ); + } + + @Test + // TODO: Find solution and remove this test + void onConstructorForJava8() { + rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().allowMissingType(o -> { + assert o instanceof FindMissingTypes.MissingTypeResult; + FindMissingTypes.MissingTypeResult result = (FindMissingTypes.MissingTypeResult) o; + // The AllArgsConstructorHandler, GetterHandler and SetterHandler do not run at all for java 8, + // so no generated constructors and methods, thus no types. + return result.getPath().startsWith("NewClass->") || result.getPath().startsWith("MethodInvocation->"); + }).build()), + java( + """ + public @interface Inject {} + public @interface Id {} + public @interface Column { String name(); } + public @interface Max { long value(); } + """ + ), + java( + """ + import lombok.AllArgsConstructor; + import lombok.Getter; + import lombok.Setter; + + @AllArgsConstructor(onConstructor_=@Inject) + public class OnXExample { + @Getter(onMethod_={@Id, @Column(name="unique-id")}) + @Setter(onParam_=@Max(10000)) + private long unid; + + public void test() { + OnXExample x = new OnXExample(1L); + x.setUnid(2L); + System.out.println(x.getUnid()); + } + } + """ + ) + ); + } + + @Test + // TODO: Find solution and remove this test + void onConstructorNoArgsForJava8() { + rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().allowMissingType(o -> { + assert o instanceof FindMissingTypes.MissingTypeResult; + FindMissingTypes.MissingTypeResult result = (FindMissingTypes.MissingTypeResult) o; + // The NoArgsConstructor and RequiredArgsConstructor do not run at all for java 8, + // so no generated constructors, thus no types. + return result.getPath().startsWith("NewClass->"); + }).build()), + java( + """ + public @interface Inject {} + public @interface Ignore {} // somehow we need this, to prevent `ClassDeclaration->CompilationUnit` errors + """ + ), + java( + """ + import lombok.NoArgsConstructor; + import lombok.NonNull; + import lombok.RequiredArgsConstructor; + + import javax.inject.Inject; + + @NoArgsConstructor(onConstructor_=@Inject) + @RequiredArgsConstructor(onConstructor_=@Inject) + public class OnXExample { + @NonNull private Long unid; + + public void test() { + new OnXExample(); + new OnXExample(1L); + } + } + """ + ) + ); + } + + @Test void extensionMethod() { rewriteRun(