From 2fa7b0a195183a1282bb69a9cd216fd6c7f0b000 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 30 Jun 2023 12:24:30 +0200 Subject: [PATCH 001/447] Annotate `Tree` and `JavaType` with `@JsonIgnoreProperties` (#3380) During deserialization the `@c` property appears to be treated as an unknown property, which adds some extra overhead. --- rewrite-core/src/main/java/org/openrewrite/Tree.java | 2 ++ .../src/main/java/org/openrewrite/java/tree/JavaType.java | 1 + 2 files changed, 3 insertions(+) diff --git a/rewrite-core/src/main/java/org/openrewrite/Tree.java b/rewrite-core/src/main/java/org/openrewrite/Tree.java index 308c4ad34e4..c2a2e933aa5 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Tree.java +++ b/rewrite-core/src/main/java/org/openrewrite/Tree.java @@ -15,6 +15,7 @@ */ package org.openrewrite; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonTypeInfo; @@ -27,6 +28,7 @@ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@c") @JsonPropertyOrder({"@c"}) // serialize type info first +@JsonIgnoreProperties(ignoreUnknown = true) public interface Tree { @SuppressWarnings("unused") @JsonProperty("@c") diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java index 873d0d6cf85..21ce2e436de 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java @@ -36,6 +36,7 @@ @SuppressWarnings("unused") @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@ref") @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@c") +@JsonIgnoreProperties(ignoreUnknown = true) public interface JavaType { FullyQualified[] EMPTY_FULLY_QUALIFIED_ARRAY = new FullyQualified[0]; From e315f935c007212ecb85b39b9a310b0a5dd0fea8 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 30 Jun 2023 13:20:14 +0200 Subject: [PATCH 002/447] Better handling of statements in context-free `JavaTemplate`s Statements should typically not be inserted into the class body but inside an initializer body (or method body). The class body is for now reserved for method declarations, but this can still be improved in the future. The template used by `SimplifyBooleanReturn` is now declared as context-free. Fixes: #3381 --- .../org/openrewrite/java/cleanup/SimplifyBooleanReturn.java | 1 - .../internal/template/BlockStatementTemplateGenerator.java | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanReturn.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanReturn.java index 4aa8dfb1f7c..2c57b5b117b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanReturn.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanReturn.java @@ -61,7 +61,6 @@ public Duration getEstimatedEffortPerOccurrence() { public TreeVisitor getVisitor() { return new JavaVisitor() { private final JavaTemplate notIfConditionReturn = JavaTemplate.builder("return !(#{any(boolean)});") - .contextSensitive() .build(); @Override diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java index a984e61beaa..f5993304c60 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java @@ -241,9 +241,12 @@ private void contextFreeTemplate(J j, StringBuilder before, StringBuilder after) before.append("Object o = "); after.append(";"); after.append("\n}}"); - } else if (!(j instanceof J.Import) && !(j instanceof J.Package)) { + } else if (j instanceof J.MethodDeclaration) { before.insert(0, "class Template {\n"); after.append("\n}"); + } else if (!(j instanceof J.Import) && !(j instanceof J.Package)) { + before.insert(0, "class Template {{\n"); + after.append("\n}}"); } before.insert(0, EXPR_STATEMENT_PARAM + METHOD_INVOCATION_STUBS); for (String anImport : imports) { From 9dce7435fb48b37f4f61c556f13e5ad30c27f5a4 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 30 Jun 2023 14:14:06 +0200 Subject: [PATCH 003/447] Improve performance of `TypeUtils#isAssignableTo()` This method is used by the `UsesType` visitor and therefore used a lot. Removing use of `Stream` to improve performance. --- .../java/org/openrewrite/java/tree/TypeUtils.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index bc0f026f25b..9e770937743 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -264,9 +264,16 @@ public static boolean isAssignableTo(Pattern to, @Nullable JavaType from) { if (from instanceof JavaType.FullyQualified) { JavaType.FullyQualified classFrom = (JavaType.FullyQualified) from; - return to.matcher(classFrom.getFullyQualifiedName()).matches() || - isAssignableTo(to, classFrom.getSupertype()) || - classFrom.getInterfaces().stream().anyMatch(i -> isAssignableTo(to, i)); + if (to.matcher(classFrom.getFullyQualifiedName()).matches() || + isAssignableTo(to, classFrom.getSupertype())) { + return true; + } + for (JavaType.FullyQualified anInterface : classFrom.getInterfaces()) { + if (isAssignableTo(to, anInterface)) { + return true; + } + } + return false; } else if (from instanceof JavaType.GenericTypeVariable) { JavaType.GenericTypeVariable genericFrom = (JavaType.GenericTypeVariable) from; for (JavaType bound : genericFrom.getBounds()) { From a5bbc9fee363762a29a7f074ccc3c7b35781b6b4 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 30 Jun 2023 14:21:08 +0200 Subject: [PATCH 004/447] Improve performance of `StringUtils#aspectjNameToPattern()` Used internally by `MethodMatcher` and thus performance critical. --- .../java/org/openrewrite/internal/StringUtils.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java index 076fef4ac38..dc7fd5cc668 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java @@ -30,6 +30,7 @@ import java.util.TreeMap; import java.util.function.Predicate; import java.util.regex.Matcher; +import java.util.regex.Pattern; public class StringUtils { private StringUtils() { @@ -637,6 +638,9 @@ public static boolean isNumeric(@Nullable String str) { return true; } + private static final Pattern SINGLE_DOT_OR_DOLLAR = Pattern.compile("(?:([^.]+)|^)\\.(?:([^.]+)|$)"); + private static final String SINGLE_DOT_OR_DOLLAR_REPLACEMENT = "$1" + Matcher.quoteReplacement("[.$]") + "$2"; + /** * See https://eclipse.org/aspectj/doc/next/progguide/semantics-pointcuts.html#type-patterns *

@@ -648,11 +652,11 @@ public static boolean isNumeric(@Nullable String str) { * the code is in any declaration of a type whose name begins with "com.xerox.". */ public static String aspectjNameToPattern(String name) { - return name + String replaced = name .replace("$", "\\$") .replace("[", "\\[") - .replace("]", "\\]") - .replaceAll("(?:([^.]+)|^)\\.(?:([^.]+)|$)", "$1" + Matcher.quoteReplacement("[.$]") + "$2") + .replace("]", "\\]"); + return SINGLE_DOT_OR_DOLLAR.matcher(replaced).replaceAll(SINGLE_DOT_OR_DOLLAR_REPLACEMENT) .replace("*", "[^.]*") .replace("..", "\\.(.+\\.)?"); } From 9c10ea3ff6975f2a109d4c1f9a4ae189b8a81def Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 30 Jun 2023 14:52:45 +0200 Subject: [PATCH 005/447] Allow context-free templates to replace fields --- .../java/JavaTemplateContextFreeTest.java | 38 +++++++++++++++++++ .../BlockStatementTemplateGenerator.java | 7 ++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateContextFreeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateContextFreeTest.java index 9563435aa41..b2930eaa655 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateContextFreeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateContextFreeTest.java @@ -62,6 +62,44 @@ void m() { )); } + @Test + void replaceField() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new JavaVisitor<>() { + @Override + public J visitVariableDeclarations(J.VariableDeclarations vd, ExecutionContext ctx) { + if (vd.getVariables().size() == 1 && vd.getVariables().get(0).getSimpleName().equals("i")) { + return JavaTemplate.apply("Integer i = 2;", getCursor(), vd.getCoordinates().replace()); + } + return super.visitVariableDeclarations(vd, ctx); + } + })), + java( + """ + class Test { + private Integer i = 1; + void m() { + Integer i = 1; + Object o = new Object() { + private final Integer i = 1; + }; + } + } + """, + """ + class Test { + Integer i = 2; + void m() { + Integer i = 2; + Object o = new Object() { + Integer i = 2; + }; + } + } + """ + )); + } + @SuppressWarnings("UnusedAssignment") @Test void genericsAndAnyParameters() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java index f5993304c60..7b14e25aa22 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java @@ -214,11 +214,12 @@ private void template(Cursor cursor, J prior, StringBuilder before, StringBuilde if (contextSensitive) { contextTemplate(cursor, prior, before, after, insertionPoint, mode); } else { - contextFreeTemplate(prior, before, after); + contextFreeTemplate(cursor, prior, before, after); } } - private void contextFreeTemplate(J j, StringBuilder before, StringBuilder after) { + @SuppressWarnings("DataFlowIssue") + private void contextFreeTemplate(Cursor cursor, J j, StringBuilder before, StringBuilder after) { if (j instanceof J.ClassDeclaration) { // While not impossible to handle, reaching this point is likely to be a mistake. // Without context a class declaration can include no imports, package, or outer class. @@ -241,7 +242,7 @@ private void contextFreeTemplate(J j, StringBuilder before, StringBuilder after) before.append("Object o = "); after.append(";"); after.append("\n}}"); - } else if (j instanceof J.MethodDeclaration) { + } else if (j instanceof J.MethodDeclaration || j instanceof J.VariableDeclarations && cursor.getValue() instanceof J.Block && cursor.getParent().getValue() instanceof J.ClassDeclaration) { before.insert(0, "class Template {\n"); after.append("\n}"); } else if (!(j instanceof J.Import) && !(j instanceof J.Package)) { From 35d936dbc071b029284c9346dbaf1fcfdf0341a5 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 30 Jun 2023 16:29:28 +0200 Subject: [PATCH 006/447] Add array-based `unsafeSet()` methods to `JavaType` implementations (#3379) This allows to cut down on the allocations of lists and arrays, as the internal representation is now array-based. Using this new overloads in `UnsafeJavaTypeVisitor`. --- .../org/openrewrite/internal/ListUtils.java | 5 +- .../openrewrite/groovy/GroovyTypeMapping.java | 2 +- .../java/UnsafeJavaTypeVisitor.java | 36 +++++++----- .../org/openrewrite/java/tree/JavaType.java | 55 +++++++++++++++++++ 4 files changed, 81 insertions(+), 17 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/ListUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/ListUtils.java index 59fde69459a..9e9f9543816 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/ListUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/ListUtils.java @@ -324,15 +324,14 @@ public static List nullIfEmpty(@Nullable List ls) { return ls == null || ls.isEmpty() ? null : ls; } - public static T[] arrayOrNullIfEmpty(@Nullable List list, T[] array) { + public static T @Nullable [] arrayOrNullIfEmpty(@Nullable List list, T[] array) { if (list == null || list.isEmpty()) { return null; } return list.toArray(array); } - @Nullable - public static T[] nullIfEmpty(@Nullable T[] list) { + public static T @Nullable [] nullIfEmpty(T @Nullable [] list) { return list == null || list.length == 0 ? null : list; } diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyTypeMapping.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyTypeMapping.java index b5cd8ae09e3..391315eb025 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyTypeMapping.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyTypeMapping.java @@ -319,7 +319,7 @@ public JavaType.Variable variableType(String name, @Nullable JavaType type) { typeCache.put(signature, variable); - variable.unsafeSet(JavaType.Unknown.getInstance(), type, null); + variable.unsafeSet(JavaType.Unknown.getInstance(), type, (JavaType.FullyQualified[]) null); return variable; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java index a5038a5cfac..24df461784e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java @@ -15,21 +15,24 @@ */ package org.openrewrite.java; -import org.openrewrite.internal.ListUtils; import org.openrewrite.java.tree.JavaType; +import java.util.function.UnaryOperator; + +import static org.openrewrite.java.tree.JavaType.*; + public class UnsafeJavaTypeVisitor

extends JavaTypeVisitor

{ @Override public JavaType visitClass(JavaType.Class aClass, P p) { return aClass.unsafeSet( - ListUtils.map(aClass.getTypeParameters(), t -> visit(t, p)), + mapInPlace(aClass.getTypeParameters().toArray(EMPTY_JAVA_TYPE_ARRAY), t -> visit(t, p)), (JavaType.FullyQualified) visit(aClass.getSupertype(), p), (JavaType.FullyQualified) visit(aClass.getOwningClass(), p), - ListUtils.map(aClass.getAnnotations(), a -> (JavaType.FullyQualified) visit(a, p)), - ListUtils.map(aClass.getInterfaces(), i -> (JavaType.FullyQualified) visit(i, p)), - ListUtils.map(aClass.getMembers(), m -> (JavaType.Variable) visit(m, p)), - ListUtils.map(aClass.getMethods(), m -> (JavaType.Method) visit(m, p)) + mapInPlace(aClass.getAnnotations().toArray(EMPTY_FULLY_QUALIFIED_ARRAY), a -> (JavaType.FullyQualified) visit(a, p)), + mapInPlace(aClass.getInterfaces().toArray(EMPTY_FULLY_QUALIFIED_ARRAY), i -> (JavaType.FullyQualified) visit(i, p)), + mapInPlace(aClass.getMembers().toArray(EMPTY_VARIABLE_ARRAY), m -> (JavaType.Variable) visit(m, p)), + mapInPlace(aClass.getMethods().toArray(EMPTY_METHOD_ARRAY), m -> (JavaType.Method) visit(m, p)) ); } @@ -42,7 +45,7 @@ public JavaType visitArray(JavaType.Array array, P p) { public JavaType visitParameterized(JavaType.Parameterized parameterized, P p) { return parameterized.unsafeSet( (JavaType.FullyQualified) visit(parameterized.getType(), p), - ListUtils.map(parameterized.getTypeParameters(), t -> visit(t, p)) + mapInPlace(parameterized.getTypeParameters().toArray(EMPTY_JAVA_TYPE_ARRAY), t -> visit(t, p)) ); } @@ -51,7 +54,7 @@ public JavaType visitGenericTypeVariable(JavaType.GenericTypeVariable generic, P return generic.unsafeSet( generic.getName(), generic.getVariance(), - ListUtils.map(generic.getBounds(), b -> visit(b, p)) + mapInPlace(generic.getBounds().toArray(EMPTY_JAVA_TYPE_ARRAY), b -> visit(b, p)) ); } @@ -60,9 +63,9 @@ public JavaType visitMethod(JavaType.Method method, P p) { return method.unsafeSet( (JavaType.FullyQualified) visit(method.getDeclaringType(), p), visit(method.getReturnType(), p), - ListUtils.map(method.getParameterTypes(), pt -> visit(pt, p)), - ListUtils.map(method.getThrownExceptions(), t -> (JavaType.FullyQualified) visit(t, p)), - ListUtils.map(method.getAnnotations(), a -> (JavaType.FullyQualified) visit(a, p)) + mapInPlace(method.getParameterTypes().toArray(EMPTY_JAVA_TYPE_ARRAY), pt -> visit(pt, p)), + mapInPlace(method.getThrownExceptions().toArray(EMPTY_FULLY_QUALIFIED_ARRAY), t -> (JavaType.FullyQualified) visit(t, p)), + mapInPlace(method.getAnnotations().toArray(EMPTY_FULLY_QUALIFIED_ARRAY), a -> (JavaType.FullyQualified) visit(a, p)) ); } @@ -71,12 +74,19 @@ public JavaType visitVariable(JavaType.Variable variable, P p) { return variable.unsafeSet( visit(variable.getOwner(), p), visit(variable.getType(), p), - ListUtils.map(variable.getAnnotations(), a -> (JavaType.FullyQualified) visit(a, p)) + mapInPlace(variable.getAnnotations().toArray(EMPTY_FULLY_QUALIFIED_ARRAY), a -> (JavaType.FullyQualified) visit(a, p)) ); } @Override public JavaType visitMultiCatch(JavaType.MultiCatch multiCatch, P p) { - return multiCatch.unsafeSet(ListUtils.map(multiCatch.getThrowableTypes(), t -> visit(t, p))); + return multiCatch.unsafeSet(mapInPlace(multiCatch.getThrowableTypes().toArray(EMPTY_JAVA_TYPE_ARRAY), t -> visit(t, p))); + } + + private static T[] mapInPlace(T[] ls, UnaryOperator map) { + for (int i = 0; i < ls.length; i++) { + ls[i] = map.apply(ls[i]); + } + return ls; } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java index 21ce2e436de..44c2e15c189 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java @@ -22,6 +22,7 @@ import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; import org.openrewrite.Incubating; +import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.DefaultJavaTypeSignatureBuilder; @@ -132,6 +133,11 @@ public MultiCatch unsafeSet(List throwableTypes) { this.throwableTypes = arrayOrNullIfEmpty(throwableTypes, EMPTY_JAVA_TYPE_ARRAY); return this; } + + public MultiCatch unsafeSet(JavaType[] throwableTypes) { + this.throwableTypes = ListUtils.nullIfEmpty(throwableTypes); + return this; + } } abstract class FullyQualified implements JavaType { @@ -501,6 +507,20 @@ public Class unsafeSet(@Nullable List typeParameters, @Nullable FullyQ return this; } + public Class unsafeSet(@Nullable JavaType[] typeParameters, @Nullable FullyQualified supertype, @Nullable FullyQualified owningClass, + @Nullable FullyQualified[] annotations, @Nullable FullyQualified[] interfaces, + @Nullable Variable[] members, @Nullable Method[] methods) { + //noinspection DuplicatedCode + this.typeParameters = ListUtils.nullIfEmpty(typeParameters); + this.supertype = supertype; + this.owningClass = owningClass; + this.annotations = ListUtils.nullIfEmpty(annotations); + this.interfaces = ListUtils.nullIfEmpty(interfaces); + this.members = ListUtils.nullIfEmpty(members); + this.methods = ListUtils.nullIfEmpty(methods); + return this; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -619,6 +639,13 @@ public Parameterized unsafeSet(@Nullable FullyQualified type, @Nullable List".equals(name); } @@ -1284,6 +1331,14 @@ public Variable unsafeSet(JavaType owner, @Nullable JavaType type, return this; } + public Variable unsafeSet(JavaType owner, @Nullable JavaType type, + FullyQualified @Nullable [] annotations) { + this.owner = owner; + this.type = unknownIfNull(type); + this.annotations = ListUtils.nullIfEmpty(annotations); + return this; + } + @Override public boolean equals(Object o) { if (this == o) return true; From 19d21322bf844051b945fdd68263751dd6677a2f Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Fri, 30 Jun 2023 10:32:08 -0400 Subject: [PATCH 007/447] Refactor `MethodMatcher#matchesTargetType` (#3372) Extracts `MethodMatcher#matchesTargetType` into `TypeUtils#isOfTypeWithName`. This makes is easier for external actors to implement cusotm `MethodMatcher` implementations without copying and pasting logic out of `MethodMatcher`. Signed-off-by: Jonathan Leitschuh --- .../org/openrewrite/java/MethodMatcher.java | 30 +++------------ .../org/openrewrite/java/tree/TypeUtils.java | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java b/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java index ea05fdf2e5b..3e08465fe9d 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java @@ -148,31 +148,11 @@ private boolean matchesTargetTypeName(String fullyQualifiedTypeName) { } boolean matchesTargetType(@Nullable JavaType.FullyQualified type) { - if (type == null || type instanceof JavaType.Unknown) { - return false; - } - - if (matchesTargetTypeName(type.getFullyQualifiedName())) { - return true; - } - - if (matchOverrides) { - if (!"java.lang.Object".equals(type.getFullyQualifiedName()) && matchesTargetType(OBJECT_CLASS)) { - return true; - } - - if (matchesTargetType(type.getSupertype())) { - return true; - } - - for (JavaType.FullyQualified anInterface : type.getInterfaces()) { - if (matchesTargetType(anInterface)) { - return true; - } - } - } - - return false; + return TypeUtils.isOfTypeWithName( + type, + matchOverrides, + this::matchesTargetTypeName + ); } @SuppressWarnings("BooleanMethodIsAlwaysInverted") diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 9e770937743..b19c0fdc23a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -15,11 +15,13 @@ */ package org.openrewrite.java.tree; +import org.openrewrite.Incubating; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaTypeSignatureBuilder; import org.openrewrite.java.internal.DefaultJavaTypeSignatureBuilder; import java.util.*; +import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.IntStream; @@ -137,6 +139,42 @@ public static boolean isOfClassType(@Nullable JavaType type, String fqn) { return false; } + /** + * @param type The declaring type of the method invocation or constructor. + * @param matchOverride Whether to match the {@code Object} type. + * @return True if the declaring type matches the criteria of this matcher. + */ + @Incubating(since = "8.1.4") + public static boolean isOfTypeWithName( + @Nullable JavaType.FullyQualified type, + boolean matchOverride, + Predicate matcher + ) { + if (type == null || type instanceof JavaType.Unknown) { + return false; + } + if (matcher.test(type.getFullyQualifiedName())) { + return true; + } + if (matchOverride) { + if (!"java.lang.Object".equals(type.getFullyQualifiedName()) && + isOfTypeWithName(TYPE_OBJECT, true, matcher)) { + return true; + } + + if (isOfTypeWithName(type.getSupertype(), true, matcher)) { + return true; + } + + for (JavaType.FullyQualified anInterface : type.getInterfaces()) { + if (isOfTypeWithName(anInterface, true, matcher)) { + return true; + } + } + } + return false; + } + public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType from) { try { if (to instanceof JavaType.Unknown || from instanceof JavaType.Unknown) { From b97248f21996b988d4c5417d3f149b1ca4aacdc4 Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Fri, 30 Jun 2023 13:10:44 -0700 Subject: [PATCH 008/447] Fix dependenciesFromResources sometimes picking the wrong jar if there are multiple jars whose suffix match the artifactName --- rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index 77689901d75..1d96f386336 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -117,7 +117,7 @@ static List dependenciesFromResources(ExecutionContext ctx, String... arti nextArtifact: for (String artifactName : artifactNamesWithVersions) { - Pattern jarPattern = Pattern.compile(artifactName + "-?.*\\.jar$"); + Pattern jarPattern = Pattern.compile("[/\\\\]" + artifactName + "-?.*\\.jar$"); File[] extracted = resourceTarget.listFiles(); if (extracted != null) { for (File file : extracted) { From 03bfcaad6253b56e989f8ada8fdfeadccc9f047b Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Fri, 30 Jun 2023 16:14:23 -0700 Subject: [PATCH 009/447] My proposal for ensuring recipe validation happens exactly once per run: Make it the responsibility of whatever is going to run the recipe, not RecipeScheduler (#3389) --- .../src/main/java/org/openrewrite/Recipe.java | 7 +------ .../java/org/openrewrite/RecipeScheduler.java | 6 +++--- .../gradle/UpdateGradleWrapperTest.java | 16 +++++++--------- .../java/org/openrewrite/test/RewriteTest.java | 9 +++++---- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/Recipe.java b/rewrite-core/src/main/java/org/openrewrite/Recipe.java index b7fe11f1cb9..b8eb5a64526 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Recipe.java +++ b/rewrite-core/src/main/java/org/openrewrite/Recipe.java @@ -84,11 +84,6 @@ public String getDisplayName() { public String getDescription() { return "Default no-op test, does nothing."; } - - @Override - public TreeVisitor getVisitor() { - return TreeVisitor.noop(); - } } /** @@ -320,7 +315,7 @@ public final Collection> validateAll() { return validateAll(new InMemoryExecutionContext(), new ArrayList<>()); } - private Collection> validateAll(ExecutionContext ctx, Collection> acc) { + public Collection> validateAll(ExecutionContext ctx, Collection> acc) { acc.add(validate(ctx)); for (Recipe recipe : getRecipeList()) { recipe.validateAll(ctx, acc); diff --git a/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java b/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java index 647dd6f897b..57b9b90e565 100644 --- a/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java +++ b/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java @@ -141,7 +141,7 @@ static class RecipeRunCycle { public LargeSourceSet scanSources(LargeSourceSet sourceSet, int cycle) { return mapForRecipeRecursively(sourceSet, (recipeStack, sourceFile) -> { Recipe recipe = recipeStack.peek(); - if (recipe.maxCycles() < cycle || !recipe.validate(ctx).isValid()) { + if (recipe.maxCycles() < cycle) { return sourceFile; } @@ -175,7 +175,7 @@ public LargeSourceSet generateSources(LargeSourceSet sourceSet, int cycle) { while (!allRecipesStack.isEmpty()) { Stack recipeStack = allRecipesStack.pop(); Recipe recipe = recipeStack.peek(); - if (recipe.maxCycles() < cycle || !recipe.validate(ctx).isValid()) { + if (recipe.maxCycles() < cycle) { continue; } @@ -200,7 +200,7 @@ public LargeSourceSet generateSources(LargeSourceSet sourceSet, int cycle) { public LargeSourceSet editSources(LargeSourceSet sourceSet, int cycle) { return mapForRecipeRecursively(sourceSet, (recipeStack, sourceFile) -> { Recipe recipe = recipeStack.peek(); - if (recipe.maxCycles() < cycle || !recipe.validate(ctx).isValid()) { + if (recipe.maxCycles() < cycle) { return sourceFile; } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpdateGradleWrapperTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpdateGradleWrapperTest.java index a2eb7ee2d5e..6c859c1f1db 100755 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpdateGradleWrapperTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpdateGradleWrapperTest.java @@ -132,12 +132,11 @@ void updateWrapper() { distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda """, spec -> spec.path("gradle/wrapper/gradle-wrapper.properties") - .afterRecipe(gradleWrapperProperties -> { - assertThat(gradleWrapperProperties.getMarkers().findFirst(BuildTool.class)).hasValueSatisfying(buildTool -> { - assertThat(buildTool.getType()).isEqualTo(BuildTool.Type.Gradle); - assertThat(buildTool.getVersion()).isEqualTo("7.4.2"); - }); - }) + .afterRecipe(gradleWrapperProperties -> + assertThat(gradleWrapperProperties.getMarkers().findFirst(BuildTool.class)).hasValueSatisfying(buildTool -> { + assertThat(buildTool.getType()).isEqualTo(BuildTool.Type.Gradle); + assertThat(buildTool.getVersion()).isEqualTo("7.4.2"); + })) ), gradlew, gradlewBat, @@ -222,9 +221,7 @@ void dontAddMissingWrapper() { rewriteRun( spec -> spec.recipe(new UpdateGradleWrapper("7.x", null, null, Boolean.FALSE)) .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Gradle, "7.4"))) - .afterRecipe(run -> { - assertThat(run.getChangeset().getAllResults()).isEmpty(); - }) + .afterRecipe(run -> assertThat(run.getChangeset().getAllResults()).isEmpty()) ); } @@ -403,6 +400,7 @@ void defaultsToLatestRelease() { var gradleWrapperJar = result(run, Remote.class, "gradle-wrapper.jar"); assertThat(gradleWrapperJar.getSourcePath()).isEqualTo(WRAPPER_JAR_LOCATION); + //noinspection OptionalGetWithoutIsPresent BuildTool buildTool = gradleWrapperJar.getMarkers().findFirst(BuildTool.class).get(); assertThat(buildTool.getVersion()).isNotEqualTo("7.4"); assertThat(gradleWrapperJar.getUri()).isEqualTo(URI.create("https://services.gradle.org/distributions/gradle-" + buildTool.getVersion() + "-bin.zip")); diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java index 3e1ee659076..7cac878e578 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java @@ -156,10 +156,6 @@ default void rewriteRun(Consumer spec, SourceSpec... sourceSpecs) .as("A recipe must be specified") .isNotNull(); - assertThat(recipe.validate().failures()) - .as("Recipe validation must have no failures") - .isEmpty(); - if (!(recipe instanceof AdHocRecipe) && !(recipe instanceof CompositeRecipe) && testClassSpec.serializationValidation && testMethodSpec.serializationValidation) { @@ -200,6 +196,11 @@ default void rewriteRun(Consumer spec, SourceSpec... sourceSpecs) for (SourceSpec s : sourceSpecs) { s.customizeExecutionContext.accept(executionContext); } + List> validations = new ArrayList<>(); + recipe.validateAll(executionContext, validations); + assertThat(validations) + .as("Recipe validation must have no failures") + .noneMatch(Validated::isInvalid); Map>> sourceSpecsByParser = new HashMap<>(); List methodSpecParsers = testMethodSpec.parsers; From 6a5861cd8f4b042338356a9682bc9886d3cfab2c Mon Sep 17 00:00:00 2001 From: Sam Snyder Date: Fri, 30 Jun 2023 19:46:13 -0700 Subject: [PATCH 010/447] Fix #3385 by getting rid of LazyValidated, which causes ClassCastExceptions whenever it is not valid --- .../main/java/org/openrewrite/Validated.java | 69 +------------------ .../openrewrite/semver/DependencyMatcher.java | 2 +- .../org/openrewrite/gradle/Assertions.java | 10 ++- .../gradle/UpdateGradleWrapper.java | 48 ++++++------- .../gradle/util/GradleWrapper.java | 29 ++------ .../gradle/util/GradleWrapperTest.java | 27 +------- 6 files changed, 40 insertions(+), 145 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/Validated.java b/rewrite-core/src/main/java/org/openrewrite/Validated.java index 37bcc13984b..03ebd177197 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Validated.java +++ b/rewrite-core/src/main/java/org/openrewrite/Validated.java @@ -15,17 +15,14 @@ */ package org.openrewrite; -import org.jetbrains.annotations.NotNull; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import java.util.*; import java.util.function.Predicate; -import java.util.function.Supplier; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import static java.util.Objects.requireNonNull; import static java.util.stream.StreamSupport.stream; /** @@ -43,15 +40,6 @@ default boolean isInvalid() { return !isValid(); } - default Validated asInvalid() { - if (this.isInvalid()) { - //noinspection unchecked - return (Validated) this; - } else { - throw new IllegalStateException("Not an invalid value"); - } - } - default List> failures() { List> list = new ArrayList<>(); for (Validated v : this) { @@ -62,6 +50,7 @@ default List> failures() { return list; } + @SuppressWarnings("unused") static Secret validSecret(String property, String value) { return new Secret(property, value); } @@ -74,9 +63,6 @@ static Valid valid(String property, @Nullable T value) { return new Valid<>(property, value); } - static Validated lazy(String property, Supplier supplier) { - return new LazyValidated<>(property, supplier); - } /** * Validate that the Predicate will evaluate to 'true' on the supplied value. @@ -257,59 +243,6 @@ public String toString() { } } - class LazyValidated implements Validated { - private final String property; - private final Supplier valueSupplier; - - private T value; - private RuntimeException exception; - - public LazyValidated(String property, Supplier valueSupplier) { - this.property = requireNonNull(property); - this.valueSupplier = requireNonNull(valueSupplier); - } - - private void evaluate() { - try { - if (value == null && exception == null) { - value = valueSupplier.get(); - } - } catch (RuntimeException e) { - exception = e; - } - } - - @Override - public boolean isValid() { - evaluate(); - return value != null; - } - - @Override - public @Nullable T getValue() { - evaluate(); - if (value == null) { - throw new IllegalStateException("Value does not exist", exception); - } - return value; - } - - @NotNull - @Override - public Iterator> iterator() { - return Stream.of((Validated) this).iterator(); - } - - @Override - public String toString() { - return "LazyValidated{" + - "property='" + property + '\'' + - ", value='" + (value == null ? (exception == null ? "[UNEVALUATED]" : "[EXCEPTION]") : value) + '\'' + - (exception == null ? "" : ", exception=" + exception) + - '}'; - } - } - class Invalid implements Validated { private final String property; diff --git a/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java b/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java index 7aec9f5e2b0..470f6999d4c 100755 --- a/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java +++ b/rewrite-core/src/main/java/org/openrewrite/semver/DependencyMatcher.java @@ -69,7 +69,7 @@ public static Validated build(String pattern) { validatedVersion = Semver.validate(patternPieces[2], null); } if(validatedVersion.isInvalid()) { - return validatedVersion.asInvalid(); + return Validated.invalid("pattern", null, "Unable to parse version"); } return Validated.valid("pattern", new DependencyMatcher(patternPieces[0], patternPieces[1], validatedVersion.getValue())); } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/Assertions.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/Assertions.java index a38a88444be..332a9d0e031 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/Assertions.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/Assertions.java @@ -16,6 +16,7 @@ package org.openrewrite.gradle; import org.intellij.lang.annotations.Language; +import org.openrewrite.HttpSenderExecutionContextView; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Parser; import org.openrewrite.SourceFile; @@ -27,6 +28,7 @@ import org.openrewrite.groovy.GroovyParser; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.ipc.http.HttpSender; import org.openrewrite.marker.OperatingSystemProvenance; import org.openrewrite.properties.tree.Properties; import org.openrewrite.test.SourceSpec; @@ -88,7 +90,8 @@ public static UncheckedConsumer> withToolingApi(@Nullable Strin } if (version != null) { - GradleWrapper gradleWrapper = requireNonNull(GradleWrapper.validate(new InMemoryExecutionContext(), version, distribution, null).getValue()); + HttpSender httpSender = HttpSenderExecutionContextView.view(new InMemoryExecutionContext()).getHttpSender(); + GradleWrapper gradleWrapper = GradleWrapper.create(distribution, version, null,httpSender); Files.createDirectories(projectDir.resolve("gradle/wrapper/")); Files.write(projectDir.resolve(GradleWrapper.WRAPPER_PROPERTIES_LOCATION), ("distributionBase=GRADLE_USER_HOME\n" + "distributionPath=wrapper/dists\n" + @@ -113,8 +116,9 @@ public static UncheckedConsumer> withToolingApi(@Nullable Strin if (sourceFile.getSourcePath().toString().endsWith(".gradle")) { if (sourceFile.getSourcePath().endsWith("settings.gradle")) { OpenRewriteModel model = OpenRewriteModelBuilder.forProjectDirectory(tempDirectory.resolve(sourceFile.getSourcePath()).getParent().toFile(), null); - if (model.gradleSettings() != null) { - GradleSettings gradleSettings = GradleSettings.fromToolingModel(model.gradleSettings()); + org.openrewrite.gradle.toolingapi.GradleSettings rawSettings = model.gradleSettings(); + if (rawSettings != null) { + GradleSettings gradleSettings = GradleSettings.fromToolingModel(rawSettings); sourceFiles.set(i, sourceFile.withMarkers(sourceFile.getMarkers().add(gradleSettings))); } } else { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java index 544dc41cbff..104e4f1f40c 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java @@ -26,6 +26,7 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.ipc.http.HttpSender; import org.openrewrite.marker.BuildTool; import org.openrewrite.marker.Markers; import org.openrewrite.properties.PropertiesParser; @@ -63,7 +64,8 @@ public String getDescription() { @Getter @Option(displayName = "New version", - description = "An exact version number or node-style semver selector used to select the version number.", + description = "An exact version number or node-style semver selector used to select the version number. " + + "Defaults to latest release if not specified.", example = "7.x", required = false) @Nullable @@ -97,22 +99,24 @@ public String getDescription() { @Nullable final Boolean addIfMissing; - @NonFinal - transient Validated gradleWrapperValidation; - - Validated createValidatedGradleWrapperValidation(ExecutionContext ctx) { - return GradleWrapper.validate( - ctx, - isBlank(version) ? "latest.release" : version, - distribution, - repositoryUrl - ); + @Override + public Validated validate() { + Validated validated = super.validate(); + if (version != null) { + validated = validated.and(Semver.validate(version, null)); + } + return validated; } - @Override - public Validated validate(ExecutionContext ctx) { - gradleWrapperValidation = createValidatedGradleWrapperValidation(ctx); - return super.validate(ctx).and(gradleWrapperValidation); + @NonFinal + transient GradleWrapper gradleWrapper; + private GradleWrapper getGradleWrapper(ExecutionContext ctx) { + if(gradleWrapper == null) { + HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); + gradleWrapper = GradleWrapper.create( + distribution, version, repositoryUrl,httpSender); + } + return gradleWrapper; } static class GradleWrapperState { @@ -154,19 +158,16 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { return false; } - GradleWrapper gradleWrapper = requireNonNull(createValidatedGradleWrapperValidation(ctx).getValue()); + GradleWrapper gradleWrapper = getGradleWrapper(ctx); VersionComparator versionComparator = requireNonNull(Semver.validate(isBlank(version) ? "latest.release" : version, null).getValue()); int compare = versionComparator.compare(null, buildTool.getVersion(), gradleWrapper.getVersion()); + // maybe we want to update the distribution type or url if (compare < 0) { acc.needsWrapperUpdate = true; acc.updatedMarker = buildTool.withVersion(gradleWrapper.getVersion()); return true; - } else if (compare == 0) { - // maybe we want to update the distribution type or url - return true; - } - return false; + } else return compare == 0; } @Override @@ -175,7 +176,7 @@ public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { return entry; } - GradleWrapper gradleWrapper = requireNonNull(createValidatedGradleWrapperValidation(ctx).getValue()); + GradleWrapper gradleWrapper = getGradleWrapper(ctx); // Typical example: https://services.gradle.org/distributions/gradle-7.4-all.zip String currentDistributionUrl = entry.getValue().getText(); @@ -227,7 +228,7 @@ public Collection generate(GradleWrapperState acc, ExecutionContext List gradleWrapperFiles = new ArrayList<>(); ZonedDateTime now = ZonedDateTime.now(); - GradleWrapper gradleWrapper = requireNonNull(gradleWrapperValidation.getValue()); + GradleWrapper gradleWrapper = getGradleWrapper(ctx); if (acc.addGradleWrapperProperties) { //noinspection UnusedProperty @@ -279,7 +280,6 @@ public TreeVisitor getVisitor(GradleWrapperState acc) { } return new TreeVisitor() { - final GradleWrapper gradleWrapper = requireNonNull(gradleWrapperValidation.getValue()); @Override public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java index fef710d57c8..7e9196b9890 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java @@ -24,6 +24,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.ipc.http.HttpSender; import org.openrewrite.remote.Remote; +import org.openrewrite.semver.LatestRelease; import org.openrewrite.semver.Semver; import org.openrewrite.semver.VersionComparator; @@ -52,32 +53,14 @@ public class GradleWrapper { String version; DistributionInfos distributionInfos; - public static Validated validate( - ExecutionContext ctx, - String version, - @Nullable String distribution, - @Nullable String repositoryUrl - ) { - String distributionTypeName = distribution != null && !distribution.isEmpty() ? distribution : DistributionType.Bin.name().toLowerCase(); - Validated validated = - Validated - .testNone("distributionType", "must be a valid distribution type", distributionTypeName, - dt -> Arrays.stream(DistributionType.values()) - .anyMatch(type -> type.name().equalsIgnoreCase(dt))) - .and(Semver.validate(version, null)); - if (validated.isInvalid()) { - return validated.asInvalid(); - } - HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); - return Validated.lazy("", () -> create(distributionTypeName, version, repositoryUrl, httpSender)); - } - - private static GradleWrapper create(String distributionTypeName, String version, @Nullable String repositoryUrl, HttpSender httpSender) { + public static GradleWrapper create(@Nullable String distributionTypeName, @Nullable String version, @Nullable String repositoryUrl, HttpSender httpSender) { DistributionType distributionType = Arrays.stream(DistributionType.values()) .filter(dt -> dt.name().equalsIgnoreCase(distributionTypeName)) .findAny() - .orElseThrow(() -> new IllegalArgumentException("Unknown distribution type " + distributionTypeName)); - VersionComparator versionComparator = requireNonNull(Semver.validate(version, null).getValue()); + .orElse(DistributionType.Bin); + VersionComparator versionComparator = (version == null) ? + new LatestRelease(null) : + requireNonNull(Semver.validate(version, null).getValue()); String gradleVersionsUrl = (repositoryUrl == null) ? "https://services.gradle.org/versions/all" : repositoryUrl; try (HttpSender.Response resp = httpSender.send(httpSender.get(gradleVersionsUrl).build())) { diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/util/GradleWrapperTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/util/GradleWrapperTest.java index 3d13b55cd06..1487503fbae 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/util/GradleWrapperTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/util/GradleWrapperTest.java @@ -21,9 +21,6 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import org.junit.jupiter.api.Test; -import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.Validated; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.NonNull; @@ -71,7 +68,7 @@ public MockResponse dispatch(RecordedRequest recordedRequest) { String extension = path.substring(path.lastIndexOf("-bin.") + "-bin.".length()); String resourcePath = "gradle-%s-bin.%s".formatted(version, extension); try ( - InputStream is = getClass().getClassLoader().getResourceAsStream(resourcePath); + InputStream is = getClass().getClassLoader().getResourceAsStream(resourcePath) ) { String body = StringUtils.readFully(is); return new MockResponse().setResponseCode(200).setBody(body); @@ -92,26 +89,4 @@ public MockResponse dispatch(RecordedRequest recordedRequest) { throw new RuntimeException(e); } } - - @Test - void validateDistributionTypeBin() { - mockGradleServices(mockWebServer -> { - Validated validated = GradleWrapper.validate(new InMemoryExecutionContext(), "7.x", "bin", "http://%s:%d/versions/all".formatted(mockWebServer.getHostName(), mockWebServer.getPort())); - GradleWrapper gw = validated.getValue(); - assertThat(validated.isValid()).isTrue(); - assertThat(gw).isNotNull(); - assertThat(gw.getVersion()).isEqualTo("7.6"); - }); - } - - @Test - void validateWithDistributionNull() { - mockGradleServices(mockWebServer -> { - Validated validated = GradleWrapper.validate(new InMemoryExecutionContext(), "latest.release", null, "http://%s:%d/versions/all".formatted(mockWebServer.getHostName(), mockWebServer.getPort())); - GradleWrapper gw = validated.getValue(); - assertThat(validated.isValid()).isTrue(); - assertThat(gw).isNotNull(); - assertThat(gw.getVersion()).isEqualTo("7.6"); - }); - } } From 6d78b3c8699afbab6c0e2cac959d843e61f95a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Schn=C3=A9ider?= Date: Fri, 30 Jun 2023 21:26:50 -0700 Subject: [PATCH 011/447] Cache the GradleWrapper validation and any initial remote archive download by uri (#3391) Co-authored-by: Shannon Pamperl Co-authored-by: Sam Snyder --- .../remote/LocalRemoteArtifactCache.java | 81 +++++++++++++++++++ .../java/org/openrewrite/remote/Remote.java | 13 ++- .../org/openrewrite/remote/RemoteArchive.java | 34 ++++++-- .../remote/RemoteArtifactCache.java | 49 +++++++++++ .../remote/RemoteExecutionContextView.java | 48 +++++++++++ .../org/openrewrite/remote/RemoteFile.java | 31 ++++--- .../openrewrite/remote/RemoteArchiveTest.java | 13 +-- .../gradle/UpdateGradleWrapper.java | 7 +- .../gradle/UpdateGradleWrapperTest.java | 4 +- 9 files changed, 246 insertions(+), 34 deletions(-) create mode 100644 rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java create mode 100644 rewrite-core/src/main/java/org/openrewrite/remote/RemoteArtifactCache.java create mode 100644 rewrite-core/src/main/java/org/openrewrite/remote/RemoteExecutionContextView.java diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java b/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java new file mode 100644 index 00000000000..90a91e4117d --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java @@ -0,0 +1,81 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.remote; + +import org.openrewrite.internal.lang.Nullable; + +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.function.Consumer; + +public class LocalRemoteArtifactCache implements RemoteArtifactCache { + private final Path cacheDir; + + public LocalRemoteArtifactCache(Path cacheDir) { + if (!cacheDir.toFile().exists() && !cacheDir.toFile().mkdirs()) { + throw new IllegalStateException("Unable to find or create remote archive cache at " + cacheDir); + } + this.cacheDir = cacheDir; + } + + @Override + @Nullable + public Path get(URI uri) { + Path resolved = cacheDir.resolve(hashUri(uri)); + return Files.exists(resolved) ? resolved : null; + } + + @Override + @Nullable + public Path put(URI uri, InputStream artifactInputStream, Consumer onError) { + try { + Path artifact = cacheDir.resolve(hashUri(uri)); + try (InputStream is = artifactInputStream) { + Files.copy(is, artifact, StandardCopyOption.REPLACE_EXISTING); + } + return artifact; + } catch (Exception e) { + onError.accept(e); + return null; + } + } + + public static String hashUri(URI uri) { + // hash the string using SHA-256 + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashBytes = digest.digest(uri.toString().getBytes(StandardCharsets.UTF_8)); + + StringBuilder hashStringBuilder = new StringBuilder(); + for (byte hashByte : hashBytes) { + String hex = Integer.toHexString(0xff & hashByte); + if (hex.length() == 1) { + hashStringBuilder.append('0'); + } + hashStringBuilder.append(hex); + } + return hashStringBuilder.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java b/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java index 5e8bcdbb347..3399a7bd87a 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java @@ -19,8 +19,6 @@ import org.openrewrite.*; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.ipc.http.HttpSender; -import org.openrewrite.ipc.http.HttpUrlConnectionSender; import org.openrewrite.marker.Markers; import java.io.InputStream; @@ -71,13 +69,15 @@ default T withChecksum(@Nullable Checksum checksum) { /** * Download the remote file * - * @param httpSender used to download the file represented by this Remote + * @param ctx used to download the file represented by this Remote */ - InputStream getInputStream(HttpSender httpSender); + InputStream getInputStream(ExecutionContext ctx); @Override default

String printAll(P p) { - return StringUtils.readFully(getInputStream(new HttpUrlConnectionSender()), StandardCharsets.UTF_8); + ExecutionContext ctx = p instanceof ExecutionContext ? (ExecutionContext) p : + new InMemoryExecutionContext(); + return StringUtils.readFully(getInputStream(ctx), StandardCharsets.UTF_8); } @Override @@ -116,8 +116,7 @@ public Tree visit(@Nullable Tree tree, PrintOutputCapture

p) { SourceFile sourceFile = (SourceFile) requireNonNull(tree); ExecutionContext ctx = p.getContext() instanceof ExecutionContext ? (ExecutionContext) p.getContext() : new InMemoryExecutionContext(); - HttpSender sender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); - p.append(StringUtils.readFully(getInputStream(sender), StandardCharsets.UTF_8)); + p.append(StringUtils.readFully(getInputStream(ctx), StandardCharsets.UTF_8)); return sourceFile; } }; diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java index 6d73b4dcfee..56ca127d21e 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java @@ -19,15 +19,19 @@ import lombok.Value; import lombok.With; import org.intellij.lang.annotations.Language; +import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; +import org.openrewrite.HttpSenderExecutionContextView; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.ipc.http.HttpSender; import org.openrewrite.marker.Markers; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.net.URI; import java.nio.charset.Charset; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.UUID; @@ -78,15 +82,29 @@ public class RemoteArchive implements Remote { List paths; @Override - public InputStream getInputStream(HttpSender httpSender) { - //noinspection resource - HttpSender.Response response = httpSender.send(httpSender.get(uri.toString()).build()); - InputStream body = response.getBody(); - InputStream inner = readIntoArchive(body, paths, 0); - if (inner == null) { - throw new IllegalArgumentException("Unable to find path " + paths + " in zip file " + uri); + public InputStream getInputStream(ExecutionContext ctx) { + HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); + RemoteArtifactCache cache = RemoteExecutionContextView.view(ctx).getArtifactCache(); + try { + Path localArchive = cache.compute(uri, () -> { + //noinspection resource + HttpSender.Response response = httpSender.send(httpSender.get(uri.toString()).build()); + return response.getBody(); + }, ctx.getOnError()); + + if (localArchive == null) { + throw new IllegalStateException("Failed to download " + uri + " to artifact cache"); + } + + InputStream body = Files.newInputStream(localArchive); + InputStream inner = readIntoArchive(body, paths, 0); + if (inner == null) { + throw new IllegalArgumentException("Unable to find path " + paths + " in zip file " + uri); + } + return inner; + } catch (IOException e) { + throw new UncheckedIOException("Unable to download " + uri + " to temporary file", e); } - return inner; } private @Nullable InputStream readIntoArchive(InputStream body, List paths, int index) { diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArtifactCache.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArtifactCache.java new file mode 100644 index 00000000000..c25affdd867 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArtifactCache.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.remote; + +import org.openrewrite.internal.lang.Nullable; + +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Path; +import java.util.concurrent.Callable; +import java.util.function.Consumer; + +public interface RemoteArtifactCache { + + @Nullable + Path get(URI uri); + + @Nullable + Path put(URI uri, InputStream is, Consumer onError); + + @Nullable + default Path compute(URI uri, Callable<@Nullable InputStream> artifactStream, Consumer onError) { + Path artifact = get(uri); + if (artifact == null) { + try { + InputStream is = artifactStream.call(); + if (is != null) { + artifact = put(uri, is, onError); + } + } catch (Exception e) { + onError.accept(e); + } + } + return artifact; + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteExecutionContextView.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteExecutionContextView.java new file mode 100644 index 00000000000..56dd8b6ed62 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteExecutionContextView.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.remote; + +import org.openrewrite.DelegatingExecutionContext; +import org.openrewrite.ExecutionContext; + +import java.nio.file.Paths; + +public class RemoteExecutionContextView extends DelegatingExecutionContext { + private static final RemoteArtifactCache DEFAULT_ARTIFACT_CACHE = new LocalRemoteArtifactCache( + Paths.get(System.getProperty("user.home") + "/.rewrite/remote")); + + private static final String REMOTE_ARTIFACT_CACHE = "org.openrewrite.remote.artifactCache"; + + private RemoteExecutionContextView(ExecutionContext delegate) { + super(delegate); + } + + public static RemoteExecutionContextView view(ExecutionContext ctx) { + if (ctx instanceof RemoteExecutionContextView) { + return (RemoteExecutionContextView) ctx; + } + return new RemoteExecutionContextView(ctx); + } + + public RemoteExecutionContextView setArtifactCache(RemoteArtifactCache artifactCache) { + putMessage(REMOTE_ARTIFACT_CACHE, artifactCache); + return this; + } + + public RemoteArtifactCache getArtifactCache() { + return getMessage(REMOTE_ARTIFACT_CACHE, DEFAULT_ARTIFACT_CACHE); + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java index a7d0d4dc36d..edd2798e790 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java @@ -19,15 +19,19 @@ import lombok.Value; import lombok.With; import org.intellij.lang.annotations.Language; +import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; +import org.openrewrite.HttpSenderExecutionContextView; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.ipc.http.HttpSender; -import org.openrewrite.ipc.http.HttpUrlConnectionSender; import org.openrewrite.marker.Markers; +import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.net.URI; import java.nio.charset.Charset; +import java.nio.file.Files; import java.nio.file.Path; import java.util.UUID; @@ -54,14 +58,23 @@ public class RemoteFile implements Remote { String description; @Override - public

byte[] printAllAsBytes(P p) { - //noinspection resource - return new HttpUrlConnectionSender().get(uri.toString()).send().getBodyAsBytes(); - } + public InputStream getInputStream(ExecutionContext ctx) { + HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); + RemoteArtifactCache cache = RemoteExecutionContextView.view(ctx).getArtifactCache(); + try { + Path localFile = cache.compute(uri, () -> { + //noinspection resource + HttpSender.Response response = httpSender.get(uri.toString()).send(); + return response.getBody(); + }, ctx.getOnError()); - @Override - public InputStream getInputStream(HttpSender httpSender) { - //noinspection resource - return httpSender.get(uri.toString()).send().getBody(); + if (localFile == null) { + throw new IllegalStateException("Failed to download " + uri + " to artifact cache"); + } + + return Files.newInputStream(localFile); + } catch (IOException e) { + throw new UncheckedIOException("Unable to download " + uri + " to temporary file", e); + } } } diff --git a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java index 99e5d551fbb..4d45023213d 100644 --- a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java @@ -17,6 +17,9 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.ExecutionContext; +import org.openrewrite.HttpSenderExecutionContextView; +import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.test.MockHttpSender; import java.io.ByteArrayOutputStream; @@ -34,6 +37,8 @@ class RemoteArchiveTest { @ValueSource(strings = {"7.4.2", "7.5-rc-1", "7.6"}) void gradleWrapper(String version) throws Exception { URL distributionUrl = requireNonNull(RemoteArchiveTest.class.getClassLoader().getResource("gradle-" + version + "-bin.zip")); + ExecutionContext ctx = new InMemoryExecutionContext(); + HttpSenderExecutionContextView.view(ctx).setHttpSender(new MockHttpSender(distributionUrl::openStream)); RemoteArchive remoteArchive = Remote .builder( @@ -42,18 +47,14 @@ void gradleWrapper(String version) throws Exception { ) .build("gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar"); - byte[] actual = readAll(remoteArchive.getInputStream(new MockHttpSender(distributionUrl::openStream))); + byte[] actual = readAll(remoteArchive.getInputStream(ctx)); assertThat(actual).hasSizeGreaterThan(50_000); } private byte[] readAll(InputStream is) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int count; - byte[] buf = new byte[4096]; - while ((count = is.read(buf)) != -1) { - baos.write(buf, 0, count); - } + is.transferTo(baos); return baos.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java index 104e4f1f40c..50c16b9dfd5 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java @@ -65,7 +65,7 @@ public String getDescription() { @Getter @Option(displayName = "New version", description = "An exact version number or node-style semver selector used to select the version number. " + - "Defaults to latest release if not specified.", + "Defaults to the latest release if not specified.", example = "7.x", required = false) @Nullable @@ -110,6 +110,7 @@ public Validated validate() { @NonFinal transient GradleWrapper gradleWrapper; + private GradleWrapper getGradleWrapper(ExecutionContext ctx) { if(gradleWrapper == null) { HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); @@ -338,7 +339,7 @@ private String unixScript(GradleWrapper gradleWrapper, ExecutionContext ctx) { binding.put("defaultJvmOpts", StringUtils.isNotEmpty(defaultJvmOpts) ? "'" + defaultJvmOpts + "'" : ""); binding.put("classpath", "$APP_HOME/gradle/wrapper/gradle-wrapper.jar"); - String gradlewTemplate = StringUtils.readFully(gradleWrapper.gradlew().getInputStream(HttpSenderExecutionContextView.view(ctx).getHttpSender())); + String gradlewTemplate = StringUtils.readFully(gradleWrapper.gradlew().getInputStream(ctx)); return renderTemplate(gradlewTemplate, binding, "\n"); } @@ -347,7 +348,7 @@ private String batchScript(GradleWrapper gradleWrapper, ExecutionContext ctx) { binding.put("defaultJvmOpts", defaultJvmOpts(gradleWrapper)); binding.put("classpath", "%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar"); - String gradlewBatTemplate = StringUtils.readFully(gradleWrapper.gradlewBat().getInputStream(HttpSenderExecutionContextView.view(ctx).getHttpSender())); + String gradlewBatTemplate = StringUtils.readFully(gradleWrapper.gradlewBat().getInputStream(ctx)); return renderTemplate(gradlewBatTemplate, binding, "\r\n"); } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpdateGradleWrapperTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpdateGradleWrapperTest.java index 6c859c1f1db..e03b8af4060 100755 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpdateGradleWrapperTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpdateGradleWrapperTest.java @@ -455,7 +455,9 @@ private S result(RecipeRun run, Class clazz, String en private boolean isValidWrapperJar(Remote gradleWrapperJar) { try { Path testWrapperJar = Files.createTempFile("gradle-wrapper", "jar"); - try (InputStream is = gradleWrapperJar.getInputStream(new HttpUrlConnectionSender(Duration.ofSeconds(5), Duration.ofSeconds(5)))) { + ExecutionContext ctx = new InMemoryExecutionContext(); + HttpSenderExecutionContextView.view(ctx).setHttpSender(new HttpUrlConnectionSender(Duration.ofSeconds(5), Duration.ofSeconds(5))); + try (InputStream is = gradleWrapperJar.getInputStream(ctx)) { Files.copy(is, testWrapperJar, StandardCopyOption.REPLACE_EXISTING); try (FileSystem fs = FileSystems.newFileSystem(testWrapperJar)) { return Files.exists(fs.getPath("org/gradle/cli/CommandLineParser.class")); From f43465bd6666a26b2dcf2d4a0bf12d536399a839 Mon Sep 17 00:00:00 2001 From: Michael Keppler Date: Sat, 1 Jul 2023 06:38:50 +0200 Subject: [PATCH 012/447] Fix NPE (#3300) getMethodType() is nullable and must be checked at every invocation. java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.JavaType$Method.hasFlags(org.openrewrite.java.tree.Flag[])" because the return value of "org.openrewrite.java.tree.J$MethodInvocation.getMethodType()" is null at org.openrewrite.java.search.SemanticallyEqual$SemanticallyEqualVisitor.visitMethodInvocation (SemanticallyEqual.java:874) Co-authored-by: Tim te Beek --- .../java/org/openrewrite/java/search/SemanticallyEqual.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java b/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java index 754a3bd553e..2b513f0fe88 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java @@ -871,7 +871,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, J j) return method; } - boolean static_ = method.getMethodType().hasFlags(Flag.Static); + boolean static_ = method.getMethodType() != null && method.getMethodType().hasFlags(Flag.Static); J.MethodInvocation compareTo = (J.MethodInvocation) j; if (!method.getSimpleName().equals(compareTo.getSimpleName()) || !TypeUtils.isOfType(method.getMethodType(), compareTo.getMethodType()) || From 05d7df412474a13ed12c282547331a809054af11 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Sat, 1 Jul 2023 10:06:28 -0500 Subject: [PATCH 013/447] Add maven resolution time recording --- .../openrewrite/maven/MavenExecutionContextView.java | 11 +++++++++++ .../maven/internal/MavenPomDownloader.java | 6 +++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenExecutionContextView.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenExecutionContextView.java index fcc64cc8380..b3652bd8b6d 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenExecutionContextView.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenExecutionContextView.java @@ -23,6 +23,7 @@ import org.openrewrite.maven.internal.MavenParsingException; import org.openrewrite.maven.tree.*; +import java.time.Duration; import java.util.*; import java.util.stream.Collectors; @@ -42,6 +43,7 @@ public class MavenExecutionContextView extends DelegatingExecutionContext { private static final String MAVEN_PINNED_SNAPSHOT_VERSIONS = "org.openrewrite.maven.pinnedSnapshotVersions"; private static final String MAVEN_POM_CACHE = "org.openrewrite.maven.pomCache"; private static final String MAVEN_RESOLUTION_LISTENER = "org.openrewrite.maven.resolutionListener"; + private static final String MAVEN_RESOLUTION_TIME = "org.openrewrite.maven.resolutionTime"; public MavenExecutionContextView(ExecutionContext delegate) { super(delegate); @@ -54,6 +56,15 @@ public static MavenExecutionContextView view(ExecutionContext ctx) { return new MavenExecutionContextView(ctx); } + public MavenExecutionContextView recordResolutionTime(Duration time) { + this.computeMessage(MAVEN_RESOLUTION_TIME, time.toMillis(), () -> 0L, Long::sum); + return this; + } + + public Duration getResolutionTime() { + return Duration.ofMillis(getMessage(MAVEN_RESOLUTION_TIME, 0L)); + } + public MavenExecutionContextView setResolutionListener(ResolutionEventListener listener) { putMessage(MAVEN_RESOLUTION_LISTENER, listener); return this; diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index 733d5b95ab7..ea3c592060e 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -45,6 +45,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Duration; import java.util.*; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; @@ -123,10 +124,12 @@ public MavenPomDownloader(Map projectPoms, HttpSender httpSender, Exe this.projectPoms = projectPoms; this.projectPomsByGav = projectPomsByGav(projectPoms); this.httpSender = httpSender; + this.ctx = MavenExecutionContextView.view(ctx); this.sendRequest = Retry.decorateCheckedFunction( mavenDownloaderRetry, request -> { int responseCode; + long start = System.nanoTime(); try (HttpSender.Response response = httpSender.send(request)) { if (response.isSuccessful()) { return response.getBodyAsBytes(); @@ -134,10 +137,11 @@ public MavenPomDownloader(Map projectPoms, HttpSender httpSender, Exe responseCode = response.getCode(); } catch (Throwable t) { throw new HttpSenderResponseException(t, null); + } finally { + this.ctx.recordResolutionTime(Duration.ofNanos(System.nanoTime() - start)); } throw new HttpSenderResponseException(null, responseCode); }); - this.ctx = MavenExecutionContextView.view(ctx); this.mavenCache = this.ctx.getPomCache(); } From 7f765d0f8340e4434abaf6a669ff8d397ccabc9d Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Sat, 1 Jul 2023 13:51:15 -0500 Subject: [PATCH 014/447] Guard against NPE in MavenResolutionResult#unsafeSetModules --- .../org/openrewrite/maven/tree/MavenResolutionResult.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenResolutionResult.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenResolutionResult.java index 986ade7fd1f..6a710b90e93 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenResolutionResult.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenResolutionResult.java @@ -148,8 +148,8 @@ public void unsafeSetParent(MavenResolutionResult parent) { this.parent = parent; } - public void unsafeSetModules(List modules) { - this.modules = new ArrayList<>(modules); + public void unsafeSetModules(@Nullable List modules) { + this.modules = modules == null ? emptyList() : new ArrayList<>(modules); } @Nullable From cfb22826ba2330b7b2c9fd22eebac12f5f036f3b Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Sat, 1 Jul 2023 22:02:07 +0200 Subject: [PATCH 015/447] `ShortenFullyQualifiedTypeReferences` should handle equal types --- ...ortenFullyQualifiedTypeReferencesTest.java | 61 ++++++++++++++++++- .../ShortenFullyQualifiedTypeReferences.java | 10 +-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java index 8c9f4882951..d55638b5189 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java @@ -19,6 +19,7 @@ import org.openrewrite.DocumentExample; import org.openrewrite.ExecutionContext; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -67,7 +68,7 @@ class T { """, """ import java.util.Map; - + class T { Map.Entry mapEntry; } @@ -146,6 +147,35 @@ class T { ); } + @Test + void equalType() { + rewriteRun( + java( + //language=java + """ + import java.util.List; + + class T { + java.util.List list; + } + """, + """ + import java.util.List; + + class T { + List list; + } + """, + spec -> spec.mapBeforeRecipe(cu -> (J.CompilationUnit) new JavaIsoVisitor<>() { + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, Object o) { + return multiVariable.withType(JavaType.ShallowClass.build("java.util.List")); + } + }.visit(cu, 0)) + ) + ); + } + @Test void noImport() { rewriteRun( @@ -362,4 +392,33 @@ void m(java.lang.String s, List list) { ) ); } + + @Test + void qualifiedMethodReference() { + rewriteRun( + java( + //language=java + """ + import java.util.Collection; + import java.util.function.Function; + + class T { + Function, Integer> m() { + return java.util.Collection::size; + } + } + """, + """ + import java.util.Collection; + import java.util.function.Function; + + class T { + Function, Integer> m() { + return Collection::size; + } + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java b/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java index 98ebe253d8c..d5b49a1f8a5 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java @@ -20,10 +20,8 @@ import org.openrewrite.SourceFile; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaSourceFile; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.Space; +import org.openrewrite.java.internal.DefaultJavaTypeSignatureBuilder; +import org.openrewrite.java.tree.*; import java.time.Duration; import java.util.HashMap; @@ -54,6 +52,7 @@ public String getDescription() { public TreeVisitor getVisitor() { return new JavaVisitor() { final Map usedTypes = new HashMap<>(); + final JavaTypeSignatureBuilder signatureBuilder = new DefaultJavaTypeSignatureBuilder(); private void ensureInitialized() { if (!usedTypes.isEmpty()) { @@ -114,7 +113,8 @@ public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) { ensureInitialized(); String simpleName = fieldAccess.getSimpleName(); - if (type.equals(usedTypes.get(simpleName))) { + JavaType usedType = usedTypes.get(simpleName); + if (type == usedType || signatureBuilder.signature(type).equals(signatureBuilder.signature(usedType))) { return fieldAccess.getName().withPrefix(fieldAccess.getPrefix()); } else if (!usedTypes.containsKey(simpleName)) { String fullyQualifiedName = ((JavaType.FullyQualified) type).getFullyQualifiedName(); From 74465055806410212dd58f13dc8e155ef84db2c7 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Mon, 3 Jul 2023 09:24:43 +0200 Subject: [PATCH 016/447] Allow templates to replace `J.NewClass` with expression `J.NewClass` implements both the `Expression` and `Statement` interfaces. But since there is no overload of `visitNewClass()` in the visitor of `JavaTemplateJavaExtension`, what is expected will always be another statement. --- .../internal/template/JavaTemplateJavaExtension.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateJavaExtension.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateJavaExtension.java index fb3df7b7716..df4aed2742b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateJavaExtension.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateJavaExtension.java @@ -433,6 +433,15 @@ public J visitMethodInvocation(J.MethodInvocation method, Integer integer) { return maybeReplaceStatement(method, J.class, 0); } + @Override + public J visitNewClass(J.NewClass newClass, Integer p) { + if (newClass.isScope(insertionPoint)) { + // allow a `J.NewClass` to also be replaced by an expression + return maybeReplaceStatement(newClass, J.class, p); + } + return super.visitNewClass(newClass, p); + } + @Override public J visitPackage(J.Package pkg, Integer integer) { if (loc.equals(PACKAGE_PREFIX) && pkg.isScope(insertionPoint)) { From bfd882fde80e9154d6b183aa35eac9a446fe1270 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Mon, 3 Jul 2023 13:29:52 +0200 Subject: [PATCH 017/447] Fix `@Nullable` annotation on `ClasspathScanningLoader` Was incorrectly referring to `javax.annotation.Nullable`. --- .../openrewrite/config/ClasspathScanningLoader.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java index 25b230d1b53..c6d34bda70d 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java @@ -22,11 +22,11 @@ import org.openrewrite.Recipe; import org.openrewrite.ScanningRecipe; import org.openrewrite.internal.RecipeIntrospectionUtils; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.style.NamedStyles; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.nio.file.Path; @@ -50,7 +50,7 @@ public class ClasspathScanningLoader implements ResourceLoader { /** * Construct a ClasspathScanningLoader scans the runtime classpath of the current java process for recipes * - * @param properties Yaml placeholder properties + * @param properties Yaml placeholder properties * @param acceptPackages Limit scan to specified packages */ public ClasspathScanningLoader(Properties properties, String[] acceptPackages) { @@ -64,7 +64,7 @@ public ClasspathScanningLoader(Properties properties, String[] acceptPackages) { /** * Construct a ClasspathScanningLoader scans the provided classload for recipes * - * @param properties Yaml placeholder properties + * @param properties Yaml placeholder properties * @param classLoader Limit scan to classes loadable by this classloader */ public ClasspathScanningLoader(Properties properties, ClassLoader classLoader) { @@ -110,14 +110,14 @@ private void scanYaml(ClassGraph classGraph, Properties properties, Collection styleClass = classInfo.loadClass(); try { Constructor constructor = RecipeIntrospectionUtils.getZeroArgsConstructor(styleClass); - if(constructor != null) { + if (constructor != null) { constructor.setAccessible(true); styles.add((NamedStyles) constructor.newInstance()); } From 54a57a6d0c96eb6e90f3d3445828c6caa9af2526 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 3 Jul 2023 16:34:03 +0200 Subject: [PATCH 018/447] Allow AddProperty reuse (#3384) * Allow AddProperty reuse - Use PropertiesVisitor for the returned visitor - Have PropertiesVisitor.visitFile return Properties.File - Remove now excess casts * Add test to demonstrate former issue & guard against changes * Revert "Allow AddProperty reuse" This reverts commit 8e10e2c854c145a08d667aa36c9a4dee5ae1c8f0. * Switch to & return PropertiesIsoVisitor in AddProperty * Revert "Add test to demonstrate former issue & guard against changes" This reverts commit f9647fca5257cdaea6a30a7bb88e060ef890fdba. --- .../main/java/org/openrewrite/properties/AddProperty.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java b/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java index 66d698c5720..fb1921fecbd 100644 --- a/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java +++ b/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java @@ -59,11 +59,11 @@ public String getDescription() { } @Override - public TreeVisitor getVisitor() { - return new PropertiesVisitor() { + public PropertiesIsoVisitor getVisitor() { + return new PropertiesIsoVisitor() { @Override - public Properties visitFile(Properties.File file, ExecutionContext executionContext) { - Properties.File p = (Properties.File) super.visitFile(file, executionContext); + public Properties.File visitFile(Properties.File file, ExecutionContext executionContext) { + Properties.File p = super.visitFile(file, executionContext); if (!StringUtils.isBlank(property) && !StringUtils.isBlank(value)) { Set properties = FindProperties.find(p, property, false); if (properties.isEmpty()) { From ef24f5aece6629f53fb653f224700d21f0bbea36 Mon Sep 17 00:00:00 2001 From: Nick McKinney Date: Mon, 3 Jul 2023 10:36:27 -0400 Subject: [PATCH 019/447] added QualifyThisVisitor (#3303) * added QualifyThisVisitor * whitespace * whitespace * Verify already qualified identity check noops --------- Co-authored-by: Tim te Beek --- .../openrewrite/java/QualifyThisVisitor.java | 62 +++++++++++++ .../java/QualifyThisVisitorTest.java | 86 +++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/QualifyThisVisitor.java create mode 100644 rewrite-java/src/test/java/org/openrewrite/java/QualifyThisVisitorTest.java diff --git a/rewrite-java/src/main/java/org/openrewrite/java/QualifyThisVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/QualifyThisVisitor.java new file mode 100644 index 00000000000..1749ad8ae2e --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/QualifyThisVisitor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.java; + +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Tree; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JLeftPadded; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; +import org.openrewrite.marker.Markers; + +public class QualifyThisVisitor extends JavaVisitor { + @Override + public J visitIdentifier(J.Identifier ident, ExecutionContext executionContext) { + if (ident.getSimpleName().equals("this") + && !isAlreadyQualified(ident) + && ident.getType() instanceof JavaType.Class) { + JavaType.Class type = (JavaType.Class) ident.getType(); + return new J.FieldAccess( + Tree.randomId(), + Space.EMPTY, + Markers.EMPTY, + new J.Identifier( + Tree.randomId(), + Space.EMPTY, + Markers.EMPTY, + type.getClassName(), + type, + null + ), + JLeftPadded.build(ident), + type + ); + } else { + return ident; + } + } + + private boolean isAlreadyQualified(J.Identifier ident) { + Cursor parentCursor = getCursor().getParentTreeCursor(); + if (!(parentCursor.getValue() instanceof J.FieldAccess)) { + return false; + } + J.FieldAccess parent = parentCursor.getValue(); + return parent.getName() == ident; + } +} diff --git a/rewrite-java/src/test/java/org/openrewrite/java/QualifyThisVisitorTest.java b/rewrite-java/src/test/java/org/openrewrite/java/QualifyThisVisitorTest.java new file mode 100644 index 00000000000..8d2d26b7220 --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/QualifyThisVisitorTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.java; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.RewriteTest.toRecipe; + +class QualifyThisVisitorTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(toRecipe(QualifyThisVisitor::new)); + } + + @Test + void qualifyThis() { + rewriteRun( + java(""" + public class Foo { + private String str = "str"; + public String getStr() { + return this.str; + } + } + """, """ + public class Foo { + private String str = "str"; + public String getStr() { + return Foo.this.str; + } + } + """) + ); + } + + @Test + void alreadyQualifiedNoop() { + rewriteRun( + java(""" + public class Foo { + private String str = "str"; + public String getStr() { + return Foo.this.str; + } + } + """) + ); + } + + @Test + void qualifyThisMethodInvocation() { + rewriteRun( + java(""" + public class Foo { + private String str = "str"; + public int getLength() { + return this.str.length(); + } + } + """, """ + public class Foo { + private String str = "str"; + public int getLength() { + return Foo.this.str.length(); + } + } + """) + ); + } +} From bdc9dc4c4c73a5254c1a8402a2bd7e933965df2a Mon Sep 17 00:00:00 2001 From: Shannon Pamperl Date: Mon, 3 Jul 2023 09:37:44 -0500 Subject: [PATCH 020/447] Add `UpdateMavenWrapper` recipe including checksum verification (#3392) * Add `UpdateMavenWrapper` recipe including checksum verification Fixes gh-1565 and gh-2996 * Add missing license headers * Ensure cmd files are using crlf line endings --- .gitattributes | 1 + .../main/java/org/openrewrite/Checksum.java | 15 +- .../java/org/openrewrite/remote/Remote.java | 11 +- .../org/openrewrite/remote/RemoteArchive.java | 4 + .../org/openrewrite/remote/RemoteFile.java | 4 + rewrite-maven/build.gradle.kts | 2 +- .../openrewrite/maven/UpdateMavenWrapper.java | 482 ++++++++++++++++++ .../maven/utilities/MavenWrapper.java | 216 ++++++++ .../maven/UpdateMavenWrapperTest.java | 418 +++++++++++++++ rewrite-maven/src/test/resources/mvnw | 287 +++++++++++ rewrite-maven/src/test/resources/mvnw.cmd | 187 +++++++ 11 files changed, 1619 insertions(+), 8 deletions(-) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java create mode 100644 rewrite-maven/src/test/resources/mvnw create mode 100644 rewrite-maven/src/test/resources/mvnw.cmd diff --git a/.gitattributes b/.gitattributes index d21dc057c39..a7e78915851 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,6 +7,7 @@ # These are explicitly windows files and should use crlf *.bat text eol=crlf +*.cmd text eol=crlf # These files are text and should be normalized (Convert crlf => lf) *.bash text eol=lf diff --git a/rewrite-core/src/main/java/org/openrewrite/Checksum.java b/rewrite-core/src/main/java/org/openrewrite/Checksum.java index 5a3c4a87270..918beb9c7cf 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Checksum.java +++ b/rewrite-core/src/main/java/org/openrewrite/Checksum.java @@ -18,6 +18,7 @@ import lombok.Value; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.ipc.http.HttpSender; +import org.openrewrite.remote.Remote; import java.io.IOException; import java.io.InputStream; @@ -99,14 +100,20 @@ public static SourceFile checksum(SourceFile sourceFile, @Nullable String algori try { MessageDigest md = MessageDigest.getInstance(algorithm); - try (InputStream is = Files.newInputStream(sourceFile.getSourcePath()); - DigestInputStream dis = new DigestInputStream(is, md)) { + InputStream is; + if (sourceFile instanceof Remote) { + is = ((Remote) sourceFile).getInputStream(new InMemoryExecutionContext()); + } else { + is = Files.newInputStream(sourceFile.getSourcePath()); + } + + try (DigestInputStream dis = new DigestInputStream(is, md)) { //noinspection StatementWithEmptyBody while (dis.read() != -1) { - // read decorated stream to EOF + // read stream to EOF } + return sourceFile.withChecksum(new Checksum(algorithm, md.digest())); } - return sourceFile.withChecksum(new Checksum(algorithm, md.digest())); } catch (NoSuchAlgorithmException | IOException e) { throw new IllegalArgumentException(e); } diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java b/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java index 3399a7bd87a..daed280455d 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/Remote.java @@ -170,19 +170,24 @@ public Builder fileAttributes(FileAttributes fileAttributes) { return this; } + public Builder checksum(Checksum checksum) { + this.checksum = checksum; + return this; + } + public RemoteFile build() { - return new RemoteFile(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description); + return new RemoteFile(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description, checksum); } public RemoteArchive build(Path path) { return new RemoteArchive(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description, Arrays.asList(path.toString().replace("/", "\\/").replace(".", "\\.") - .split("!"))); + .split("!")), checksum); } public RemoteArchive build(String... paths) { return new RemoteArchive(id, sourcePath, markers, uri, charset, charsetBomMarked, fileAttributes, description, - Arrays.asList(paths)); + Arrays.asList(paths), checksum); } } } diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java index 56ca127d21e..bb736eba396 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java @@ -19,6 +19,7 @@ import lombok.Value; import lombok.With; import org.intellij.lang.annotations.Language; +import org.openrewrite.Checksum; import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; import org.openrewrite.HttpSenderExecutionContextView; @@ -81,6 +82,9 @@ public class RemoteArchive implements Remote { */ List paths; + @Nullable + Checksum checksum; + @Override public InputStream getInputStream(ExecutionContext ctx) { HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java index edd2798e790..b4a0e91b910 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java @@ -19,6 +19,7 @@ import lombok.Value; import lombok.With; import org.intellij.lang.annotations.Language; +import org.openrewrite.Checksum; import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; import org.openrewrite.HttpSenderExecutionContextView; @@ -57,6 +58,9 @@ public class RemoteFile implements Remote { @Language("markdown") String description; + @Nullable + Checksum checksum; + @Override public InputStream getInputStream(ExecutionContext ctx) { HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); diff --git a/rewrite-maven/build.gradle.kts b/rewrite-maven/build.gradle.kts index 237ce066086..2a9c6f06417 100755 --- a/rewrite-maven/build.gradle.kts +++ b/rewrite-maven/build.gradle.kts @@ -31,7 +31,7 @@ dependencies { compileOnly("org.rocksdb:rocksdbjni:latest.release") compileOnly(project(":rewrite-yaml")) - compileOnly(project(":rewrite-properties")) + implementation(project(":rewrite-properties")) implementation("io.micrometer:micrometer-core:1.9.+") diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java new file mode 100644 index 00000000000..f98856588f8 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java @@ -0,0 +1,482 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.maven; + +import lombok.*; +import lombok.experimental.FieldDefaults; +import lombok.experimental.NonFinal; +import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.BuildTool; +import org.openrewrite.marker.Markers; +import org.openrewrite.maven.utilities.MavenWrapper; +import org.openrewrite.properties.PropertiesIsoVisitor; +import org.openrewrite.properties.PropertiesParser; +import org.openrewrite.properties.PropertiesVisitor; +import org.openrewrite.properties.search.FindProperties; +import org.openrewrite.properties.tree.Properties; +import org.openrewrite.quark.Quark; +import org.openrewrite.remote.Remote; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; +import org.openrewrite.text.PlainText; + +import java.time.ZonedDateTime; +import java.util.*; + +import static java.util.Objects.requireNonNull; +import static org.openrewrite.PathUtils.equalIgnoringSeparators; +import static org.openrewrite.internal.StringUtils.isBlank; +import static org.openrewrite.maven.utilities.MavenWrapper.*; + +/** + * This recipe expects for the specified repository to be a Maven layout with `maven-metadata.xml` files containing all + * the following REQUIRED publications: + * + * org.apache.maven.wrapper:maven-wrapper:{wrapperVersion} + * org.apache.maven.wrapper:maven-wrapper-distribution:{wrapperVersion} + * org.apache.maven:apache-maven:{distributionVersion} + */ +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE) +@EqualsAndHashCode(callSuper = true) +public class UpdateMavenWrapper extends ScanningRecipe { + private static final String DISTRIBUTION_URL_KEY = "distributionUrl"; + private static final String DISTRIBUTION_SHA_256_SUM_KEY = "distributionSha256Sum"; + private static final String WRAPPER_URL_KEY = "wrapperUrl"; + private static final String WRAPPER_SHA_256_SUM_KEY = "wrapperSha256Sum"; + + @Getter + @Option(displayName = "New wrapper version", + description = "An exact version number or node-style semver selector used to select the wrapper version number.", + example = "3.x", + required = false) + @Nullable + final String wrapperVersion; + + @Getter + @Option(displayName = "Wrapper Distribution type", + description = "The distribution of the Maven wrapper to use.\n\n" + + "* \"bin\" uses a `maven-wrapper.jar` compiled binary.\n" + + "* \"only-script\" uses a lite version of `mvnw`/`mvnw.cmd` using wget/curl or powershell. (required wrapper 3.2.0 or newer)\n" + + "* \"script\" downloads `maven-wrapper.jar` or `MavenWrapperDownloader.java` to then download a full distribution.\n" + + "* \"source\" uses `MavenWrapperDownloader.java` source file.\n\n" + + "Defaults to \"bin\".", + valid = {"bin", "only-script", "script", "source"}, + required = false) + @Nullable + final String wrapperDistribution; + + @Getter + @Option(displayName = "New distribution version", + description = "An exact version number or node-style semver selector used to select the Maven version number.", + example = "3.x", + required = false) + @Nullable + final String distributionVersion; + + @Getter + @Option(displayName = "Repository URL", + description = "The URL of the repository to download the Maven wrapper and distribution from. Supports repositories " + + "with a Maven layout. Defaults to `https://repo.maven.apache.org/maven2`.", + example = "https://repo.maven.apache.org/maven2", + required = false) + @Nullable + final String repositoryUrl; + + @Getter + @Option(displayName = "Add if missing", + description = "Add a Maven wrapper, if it's missing. Defaults to `true`.", + required = false) + @Nullable + final Boolean addIfMissing; + + @Override + public String getDisplayName() { + return "Update Maven wrapper"; + } + + @Override + public String getDescription() { + return "Update the version of Maven used in an existing Maven wrapper."; + } + + @Override + public Validated validate() { + Validated validated = super.validate(); + if (wrapperVersion != null) { + validated = validated.and(Semver.validate(wrapperVersion, null)); + } + if (distributionVersion != null) { + validated = validated.and(Semver.validate(distributionVersion, null)); + } + return validated; + } + + @NonFinal + @Nullable + transient MavenWrapper mavenWrapper; + + private MavenWrapper getMavenWrapper(ExecutionContext ctx) { + if (mavenWrapper == null) { + mavenWrapper = MavenWrapper.create(wrapperVersion, wrapperDistribution, distributionVersion, repositoryUrl, ctx); + } + return mavenWrapper; + } + + static class MavenWrapperState { + boolean needsWrapperUpdate = false; + @Nullable BuildTool updatedMarker; + boolean addMavenWrapperProperties = true; + boolean addMavenWrapperDownloader = true; + boolean addMavenWrapperJar = true; + boolean addMavenShellScript = true; + boolean addMavenBatchScript = true; + } + + @Override + public MavenWrapperState getInitialValue(ExecutionContext ctx) { + return new MavenWrapperState(); + } + + @Override + public TreeVisitor getScanner(MavenWrapperState acc) { + return Preconditions.or( + new PropertiesVisitor() { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + if (!super.isAcceptable(sourceFile, ctx)) { + return false; + } + + if (equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_PROPERTIES_LOCATION)) { + acc.addMavenWrapperProperties = false; + } else if (!PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_PROPERTIES_LOCATION_RELATIVE_PATH)) { + return false; + } + + Optional maybeBuildTool = sourceFile.getMarkers().findFirst(BuildTool.class); + if (!maybeBuildTool.isPresent()) { + return false; + } + BuildTool buildTool = maybeBuildTool.get(); + if (buildTool.getType() != BuildTool.Type.Maven) { + return false; + } + + MavenWrapper mavenWrapper = getMavenWrapper(ctx); + + VersionComparator versionComparator = requireNonNull(Semver.validate(isBlank(distributionVersion) ? "latest.release" : distributionVersion, null).getValue()); + int compare = versionComparator.compare(null, buildTool.getVersion(), mavenWrapper.getDistributionVersion()); + // maybe we want to update the distribution url + if (compare < 0) { + acc.needsWrapperUpdate = true; + acc.updatedMarker = buildTool.withVersion(mavenWrapper.getDistributionVersion()); + return true; + } else return compare == 0; + } + + @Override + public Properties visitFile(Properties.File file, ExecutionContext executionContext) { + Properties p = super.visitFile(file, executionContext); + if (FindProperties.find(p, DISTRIBUTION_SHA_256_SUM_KEY, null).isEmpty() || + FindProperties.find(p, WRAPPER_SHA_256_SUM_KEY, null).isEmpty()) { + acc.needsWrapperUpdate = true; + } + return p; + } + + @Override + public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { + MavenWrapper mavenWrapper = getMavenWrapper(ctx); + if ("distributionUrl".equals(entry.getKey())) { + // Typical example: https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + String currentDistributionUrl = entry.getValue().getText(); + if (!mavenWrapper.getPropertiesFormattedDistributionUrl().equals(currentDistributionUrl)) { + acc.needsWrapperUpdate = true; + } + } else if ("wrapperUrl".equals(entry.getKey())) { + // Typical example: https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + String currentWrapperUrl = entry.getValue().getText(); + if (!mavenWrapper.getPropertiesFormattedWrapperUrl().equals(currentWrapperUrl)) { + acc.needsWrapperUpdate = true; + } + } + return entry; + } + }, + new TreeVisitor() { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + if (!super.isAcceptable(sourceFile, ctx)) { + return false; + } + + MavenWrapper mavenWrapper = getMavenWrapper(ctx); + + if (sourceFile instanceof Quark || sourceFile instanceof Remote) { + if (equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_JAR_LOCATION)) { + acc.addMavenWrapperJar = false; + if (mavenWrapper.getWrapperDistributionType() != DistributionType.Bin) { + acc.needsWrapperUpdate = true; + } + return true; + } else if (equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_DOWNLOADER_LOCATION)) { + acc.addMavenWrapperDownloader = false; + if (mavenWrapper.getWrapperDistributionType() != DistributionType.Source) { + acc.needsWrapperUpdate = true; + } + return true; + } + } + + if (sourceFile instanceof PlainText) { + if (equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_BATCH_LOCATION)) { + acc.addMavenBatchScript = false; + return true; + } else if (equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_SCRIPT_LOCATION)) { + acc.addMavenShellScript = false; + return true; + } + } + + return false; + } + } + ); + } + + @Override + public Collection generate(MavenWrapperState acc, ExecutionContext ctx) { + if (Boolean.FALSE.equals(addIfMissing)) { + return Collections.emptyList(); + } + + MavenWrapper mavenWrapper = getMavenWrapper(ctx); + if (mavenWrapper.getWrapperDistributionType() == DistributionType.Bin) { + if (!(acc.addMavenWrapperJar || acc.addMavenWrapperProperties || acc.addMavenBatchScript || acc.addMavenShellScript)) { + return Collections.emptyList(); + } + } else if (mavenWrapper.getWrapperDistributionType() == DistributionType.OnlyScript) { + if (!(acc.addMavenWrapperProperties || acc.addMavenBatchScript || acc.addMavenShellScript)) { + return Collections.emptyList(); + } + } else { + if (!(acc.addMavenWrapperDownloader || acc.addMavenWrapperProperties || acc.addMavenBatchScript || acc.addMavenShellScript)) { + return Collections.emptyList(); + } + } + + List mavenWrapperFiles = new ArrayList<>(); + ZonedDateTime now = ZonedDateTime.now(); + + if (acc.addMavenWrapperProperties) { + String mavenWrapperPropertiesText = ASF_LICENSE_HEADER + + DISTRIBUTION_URL_KEY + "=" + mavenWrapper.getPropertiesFormattedDistributionUrl() + "\n" + + DISTRIBUTION_SHA_256_SUM_KEY + "=" + mavenWrapper.getDistributionChecksum().getHexValue(); + if (mavenWrapper.getWrapperDistributionType() != DistributionType.OnlyScript) { + mavenWrapperPropertiesText += "\n" + + WRAPPER_URL_KEY + "=" + mavenWrapper.getPropertiesFormattedWrapperUrl() + "\n" + + WRAPPER_SHA_256_SUM_KEY + "=" + mavenWrapper.getWrapperChecksum().getHexValue(); + } + //noinspection UnusedProperty + Properties.File mavenWrapperProperties = new PropertiesParser().parse(mavenWrapperPropertiesText) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Could not parse as properties")) + .withSourcePath(WRAPPER_PROPERTIES_LOCATION); + mavenWrapperFiles.add(mavenWrapperProperties); + } + + FileAttributes wrapperScriptAttributes = new FileAttributes(now, now, now, true, true, true, 1L); + if (acc.addMavenShellScript) { + String mvnwText = unixScript(mavenWrapper, ctx); + PlainText mvnw = PlainText.builder() + .text(mvnwText) + .sourcePath(WRAPPER_SCRIPT_LOCATION) + .fileAttributes(wrapperScriptAttributes) + .build(); + mavenWrapperFiles.add(mvnw); + } + + if (acc.addMavenBatchScript) { + String mvnwCmdText = batchScript(mavenWrapper, ctx); + PlainText mvnwCmd = PlainText.builder() + .text(mvnwCmdText) + .sourcePath(WRAPPER_BATCH_LOCATION) + .fileAttributes(wrapperScriptAttributes) + .build(); + mavenWrapperFiles.add(mvnwCmd); + } + + if (mavenWrapper.getWrapperDistributionType() == DistributionType.Bin && acc.addMavenWrapperJar) { + mavenWrapperFiles.add(mavenWrapper.wrapperJar()); + } else if (mavenWrapper.getWrapperDistributionType() == DistributionType.Source && acc.addMavenWrapperDownloader) { + mavenWrapperFiles.add(mavenWrapper.wrapperDownloader()); + } + + return mavenWrapperFiles; + } + + @Override + public TreeVisitor getVisitor(MavenWrapperState acc) { + if (!acc.needsWrapperUpdate) { + return TreeVisitor.noop(); + } + + return new TreeVisitor() { + @Override + public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!(tree instanceof SourceFile)) { + return tree; + } + + SourceFile sourceFile = (SourceFile) tree; + if (acc.updatedMarker != null) { + sourceFile = sourceFile.getMarkers().findFirst(BuildTool.class) + .map(buildTool -> (SourceFile) tree.withMarkers(tree.getMarkers().setByType(acc.updatedMarker))) + .orElse(sourceFile); + } + + MavenWrapper mavenWrapper = getMavenWrapper(ctx); + + if (sourceFile instanceof PlainText && PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_SCRIPT_LOCATION_RELATIVE_PATH)) { + String mvnwText = unixScript(mavenWrapper, ctx); + PlainText mvnw = (PlainText) setExecutable(sourceFile); + if (!mvnwText.equals(mvnw.getText())) { + mvnw = mvnw.withText(mvnwText); + } + return mvnw; + } + if (sourceFile instanceof PlainText && PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_BATCH_LOCATION_RELATIVE_PATH)) { + String mvnwCmdText = batchScript(mavenWrapper, ctx); + PlainText mvnwCmd = (PlainText) setExecutable(sourceFile); + if (!mvnwCmdText.equals(mvnwCmd.getText())) { + mvnwCmd = mvnwCmd.withText(mvnwCmdText); + } + return mvnwCmd; + } + + if (sourceFile instanceof Properties.File && PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_PROPERTIES_LOCATION_RELATIVE_PATH)) { + return new WrapperPropertiesVisitor(mavenWrapper).visitNonNull(sourceFile, ctx); + } + if (mavenWrapper.getWrapperDistributionType() == DistributionType.Bin) { + if ((sourceFile instanceof Quark || sourceFile instanceof Remote) && PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_JAR_LOCATION_RELATIVE_PATH)) { + return mavenWrapper.wrapperJar().withId(sourceFile.getId()).withMarkers(sourceFile.getMarkers()); + } + + if (PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH)) { + return null; + } + } else if (mavenWrapper.getWrapperDistributionType() == DistributionType.Source) { + if ((sourceFile instanceof Quark || sourceFile instanceof Remote) && PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH)) { + return mavenWrapper.wrapperDownloader().withId(sourceFile.getId()).withMarkers(sourceFile.getMarkers()); + } + + if (PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_JAR_LOCATION_RELATIVE_PATH)) { + return null; + } + } else if (PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_JAR_LOCATION_RELATIVE_PATH) || + PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH)) { + return null; + } + return sourceFile; + } + }; + } + + private static T setExecutable(T sourceFile) { + FileAttributes attributes = sourceFile.getFileAttributes(); + if (attributes == null) { + ZonedDateTime now = ZonedDateTime.now(); + return sourceFile.withFileAttributes(new FileAttributes(now, now, now, true, true, true, 1L)); + } else if (!attributes.isExecutable()) { + return sourceFile.withFileAttributes(attributes.withExecutable(true)); + } + return sourceFile; + } + + private String unixScript(MavenWrapper mavenWrapper, ExecutionContext ctx) { + return StringUtils.readFully(mavenWrapper.mvnw().getInputStream(ctx)); + } + + private String batchScript(MavenWrapper mavenWrapper, ExecutionContext ctx) { + return StringUtils.readFully(mavenWrapper.mvnwCmd().getInputStream(ctx)); + } + + @AllArgsConstructor + private static class WrapperPropertiesVisitor extends PropertiesIsoVisitor { + MavenWrapper mavenWrapper; + + @Override + public Properties.File visitFile(Properties.File file, ExecutionContext executionContext) { + Properties.File p = super.visitFile(file, executionContext); + Checksum mavenDistributionChecksum = mavenWrapper.getDistributionChecksum(); + if (FindProperties.find(p, DISTRIBUTION_SHA_256_SUM_KEY, null).isEmpty() && mavenDistributionChecksum != null) { + Properties.Value propertyValue = new Properties.Value(Tree.randomId(), "", Markers.EMPTY, mavenDistributionChecksum.getHexValue()); + Properties.Entry entry = new Properties.Entry(Tree.randomId(), "\n", Markers.EMPTY, DISTRIBUTION_SHA_256_SUM_KEY, "", Properties.Entry.Delimiter.EQUALS, propertyValue); + p = p.withContent(ListUtils.concat(p.getContent(), entry)); + } + if (mavenWrapper.getWrapperDistributionType() != DistributionType.OnlyScript) { + Checksum wrapperJarChecksum = mavenWrapper.getWrapperChecksum(); + if (FindProperties.find(p, WRAPPER_SHA_256_SUM_KEY, null).isEmpty() && wrapperJarChecksum != null) { + Properties.Value propertyValue = new Properties.Value(Tree.randomId(), "", Markers.EMPTY, wrapperJarChecksum.getHexValue()); + Properties.Entry entry = new Properties.Entry(Tree.randomId(), "\n", Markers.EMPTY, WRAPPER_SHA_256_SUM_KEY, "", Properties.Entry.Delimiter.EQUALS, propertyValue); + p = p.withContent(ListUtils.concat(p.getContent(), entry)); + } + } + return p; + } + + @Override + @Nullable + public Properties.Entry visitEntry(Properties.Entry entry, ExecutionContext executionContext) { + if (DISTRIBUTION_URL_KEY.equals(entry.getKey())) { + Properties.Value value = entry.getValue(); + if (!mavenWrapper.getPropertiesFormattedDistributionUrl().equals(value.getText())) { + return entry.withValue(value.withText(mavenWrapper.getPropertiesFormattedDistributionUrl())); + } + } else if (DISTRIBUTION_SHA_256_SUM_KEY.equals(entry.getKey())) { + Properties.Value value = entry.getValue(); + Checksum mavenDistributionChecksum = mavenWrapper.getDistributionChecksum(); + if (mavenDistributionChecksum != null && !mavenDistributionChecksum.getHexValue().equals(value.getText())) { + return entry.withValue(value.withText(mavenDistributionChecksum.getHexValue())); + } + } else if (WRAPPER_URL_KEY.equals(entry.getKey())) { + if (mavenWrapper.getWrapperDistributionType() != DistributionType.OnlyScript) { + Properties.Value value = entry.getValue(); + if (!mavenWrapper.getPropertiesFormattedWrapperUrl().equals(value.getText())) { + return entry.withValue(value.withText(mavenWrapper.getPropertiesFormattedWrapperUrl())); + } + } else { + return null; + } + } else if (WRAPPER_SHA_256_SUM_KEY.equals(entry.getKey())) { + if (mavenWrapper.getWrapperDistributionType() != DistributionType.OnlyScript) { + Properties.Value value = entry.getValue(); + Checksum wrapperJarChecksum = mavenWrapper.getWrapperChecksum(); + if (wrapperJarChecksum != null && !wrapperJarChecksum.getHexValue().equals(value.getText())) { + return entry.withValue(value.withText(wrapperJarChecksum.getHexValue())); + } + } else { + return null; + } + } + return entry; + } + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java new file mode 100644 index 00000000000..555f6e1d50d --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java @@ -0,0 +1,216 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.maven.utilities; + +import lombok.Value; +import org.openrewrite.Checksum; +import org.openrewrite.ExecutionContext; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.maven.MavenDownloadingException; +import org.openrewrite.maven.internal.MavenPomDownloader; +import org.openrewrite.maven.tree.GroupArtifact; +import org.openrewrite.maven.tree.MavenMetadata; +import org.openrewrite.maven.tree.MavenRepository; +import org.openrewrite.remote.Remote; +import org.openrewrite.semver.LatestRelease; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static java.util.Objects.requireNonNull; + +@Value +public class MavenWrapper { + public static final String ASF_LICENSE_HEADER = "# Licensed to the Apache Software Foundation (ASF) under one\n" + + "# or more contributor license agreements. See the NOTICE file\n" + + "# distributed with this work for additional information\n" + + "# regarding copyright ownership. The ASF licenses this file\n" + + "# to you under the Apache License, Version 2.0 (the\n" + + "# \"License\"); you may not use this file except in compliance\n" + + "# with the License. You may obtain a copy of the License at\n" + + "# \n" + + "# http://www.apache.org/licenses/LICENSE-2.0\n" + + "# \n" + + "# Unless required by applicable law or agreed to in writing,\n" + + "# software distributed under the License is distributed on an\n" + + "# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n" + + "# KIND, either express or implied. See the License for the\n" + + "# specific language governing permissions and limitations\n" + + "# under the License.\n"; + public static final String WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH = ".mvn/wrapper/MavenWrapperDownloader.java"; + public static final String WRAPPER_JAR_LOCATION_RELATIVE_PATH = ".mvn/wrapper/maven-wrapper.jar"; + public static final String WRAPPER_PROPERTIES_LOCATION_RELATIVE_PATH = ".mvn/wrapper/maven-wrapper.properties"; + public static final String WRAPPER_SCRIPT_LOCATION_RELATIVE_PATH = "mvnw"; + public static final String WRAPPER_BATCH_LOCATION_RELATIVE_PATH = "mvnw.cmd"; + + public static final Path WRAPPER_DOWNLOADER_LOCATION = Paths.get(WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH); + public static final Path WRAPPER_JAR_LOCATION = Paths.get(WRAPPER_JAR_LOCATION_RELATIVE_PATH); + public static final Path WRAPPER_PROPERTIES_LOCATION = Paths.get(WRAPPER_PROPERTIES_LOCATION_RELATIVE_PATH); + public static final Path WRAPPER_SCRIPT_LOCATION = Paths.get(WRAPPER_SCRIPT_LOCATION_RELATIVE_PATH); + public static final Path WRAPPER_BATCH_LOCATION = Paths.get(WRAPPER_BATCH_LOCATION_RELATIVE_PATH); + + String wrapperVersion; + String wrapperUri; + Checksum wrapperChecksum; + String wrapperDistributionUri; + DistributionType wrapperDistributionType; + String distributionVersion; + String distributionUri; + Checksum distributionChecksum; + + public static MavenWrapper create( + @Nullable String wrapperVersion, + @Nullable String wrapperDistributionTypeName, + @Nullable String distributionVersion, + @Nullable String repositoryUrl, + ExecutionContext ctx + ) { + DistributionType wrapperDistributionType = Arrays.stream(DistributionType.values()) + .filter(dt -> dt.classifier.equalsIgnoreCase(wrapperDistributionTypeName)) + .findAny() + .orElse(DistributionType.Bin); + + MavenPomDownloader pomDownloader = new MavenPomDownloader(Collections.emptyMap(), ctx, null, null); + + VersionComparator wrapperVersionComparator = (wrapperVersion == null) ? + new LatestRelease(null) : + requireNonNull(Semver.validate(wrapperVersion, null).getValue()); + VersionComparator distributionVersionComparator = (distributionVersion == null) ? + new LatestRelease(null) : + requireNonNull(Semver.validate(distributionVersion, null).getValue()); + + MavenRepository repository = repositoryUrl == null ? + MavenRepository.MAVEN_CENTRAL : + MavenRepository.builder() + .uri(repositoryUrl) + .releases(true) + .snapshots(true) + .build(); + List repositories = Collections.singletonList(repository); + try { + GroupArtifact wrapperDistributionGroupArtifact = new GroupArtifact("org.apache.maven.wrapper", "maven-wrapper-distribution"); + MavenMetadata wrapperMetadata = pomDownloader.downloadMetadata(wrapperDistributionGroupArtifact, null, repositories); + String resolvedWrapperVersion = wrapperMetadata.getVersioning() + .getVersions() + .stream() + .filter(v -> wrapperVersionComparator.isValid(null, v)) + .max((v1, v2) -> wrapperVersionComparator.compare(null, v1, v2)) + .orElseThrow(() -> new IllegalStateException("Expected to find at least one Maven wrapper version to select from.")); + String resolvedWrapperUri = getDownloadUriFor(repository, new GroupArtifact("org.apache.maven.wrapper", "maven-wrapper"), resolvedWrapperVersion, null, "jar"); + String resolvedWrapperDistributionUri = getDownloadUriFor(repository, wrapperDistributionGroupArtifact, resolvedWrapperVersion, wrapperDistributionType.classifier, "zip"); + + GroupArtifact distributionGroupArtifact = new GroupArtifact("org.apache.maven", "apache-maven"); + MavenMetadata distributionMetadata = pomDownloader.downloadMetadata(distributionGroupArtifact, null, repositories); + String resolvedDistributionVersion = distributionMetadata.getVersioning() + .getVersions() + .stream() + .filter(v -> distributionVersionComparator.isValid(null, v)) + .max((v1, v2) -> distributionVersionComparator.compare(null, v1, v2)) + .orElseThrow(() -> new IllegalStateException("Expected to find at least one Maven distribution version to select from.")); + String resolvedDistributionUri = getDownloadUriFor(repository, distributionGroupArtifact, resolvedDistributionVersion, "bin", "zip"); + + Remote wrapperJar = (Remote) Checksum.sha256(Remote.builder( + WRAPPER_JAR_LOCATION, + URI.create(resolvedWrapperUri) + ).build()); + + Remote mavenDistribution = (Remote) Checksum.sha256(Remote.builder( + Paths.get(""), + URI.create(resolvedDistributionUri) + ).build()); + + return new MavenWrapper( + resolvedWrapperVersion, + resolvedWrapperUri, + wrapperJar.getChecksum(), + resolvedWrapperDistributionUri, + wrapperDistributionType, + resolvedDistributionVersion, + resolvedDistributionUri, + mavenDistribution.getChecksum() + ); + } catch (MavenDownloadingException e) { + throw new RuntimeException("Could not get Maven versions at: " + repository.getUri(), e); + } + } + + public String getPropertiesFormattedWrapperUrl() { + return wrapperUri.replaceAll("(? + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

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

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven; + +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; +import org.openrewrite.*; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.ipc.http.HttpUrlConnectionSender; +import org.openrewrite.marker.BuildTool; +import org.openrewrite.maven.utilities.MavenWrapper; +import org.openrewrite.remote.Remote; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpecs; +import org.openrewrite.text.PlainText; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.*; +import java.time.Duration; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.UnaryOperator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.openrewrite.maven.utilities.MavenWrapper.*; +import static org.openrewrite.properties.Assertions.properties; +import static org.openrewrite.test.SourceSpecs.*; + +class UpdateMavenWrapperTest implements RewriteTest { + private final UnaryOperator<@Nullable String> notEmpty = actual -> { + assertThat(actual).isNotNull(); + return actual + "\n"; + }; + + // Maven wrapper script text for 3.1.1 + private static final String MVNW_TEXT = StringUtils.readFully(UpdateMavenWrapperTest.class.getResourceAsStream("/mvnw")); + private static final String MVNW_CMD_TEXT = StringUtils.readFully(UpdateMavenWrapperTest.class.getResourceAsStream("/mvnw.cmd")); + + private final SourceSpecs mvnw = text("", spec -> spec.path(WRAPPER_SCRIPT_LOCATION).after(notEmpty)); + private final SourceSpecs mvnwCmd = text("", spec -> spec.path(WRAPPER_BATCH_LOCATION).after(notEmpty)); + private final SourceSpecs mvnWrapperJarQuark = other("", spec -> spec.path(WRAPPER_JAR_LOCATION).after(notEmpty)); + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UpdateMavenWrapper("3.1.x", null, "3.8.x", null, null)); + } + + @Test + @DocumentExample("Add a new Maven wrapper") + void addMavenWrapper() { + rewriteRun( + spec -> spec.afterRecipe(run -> { + assertThat(run.getChangeset().getAllResults()).hasSize(4); + + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getSourcePath()).isEqualTo(WRAPPER_SCRIPT_LOCATION); + assertThat(mvnw.getText()).isEqualTo(MVNW_TEXT); + assertThat(mvnw.getFileAttributes()).isNotNull(); + assertThat(mvnw.getFileAttributes().isReadable()).isTrue(); + assertThat(mvnw.getFileAttributes().isWritable()).isTrue(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getSourcePath()).isEqualTo(WRAPPER_BATCH_LOCATION); + assertThat(mvnwCmd.getText()).isEqualTo(MVNW_CMD_TEXT); + + var mavenWrapperJar = result(run, Remote.class, "maven-wrapper.jar"); + assertThat(mavenWrapperJar.getSourcePath()).isEqualTo(WRAPPER_JAR_LOCATION); + assertThat(mavenWrapperJar.getUri()).isEqualTo(URI.create("https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar")); + assertThat(isValidWrapperJar(mavenWrapperJar)).as("Wrapper jar is not valid").isTrue(); + }), + properties( + null, + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ) + ); + } + + @Test + @DocumentExample("Update existing Maven wrapper") + void updateWrapper() { + rewriteRun( + spec -> spec.allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getSourcePath()).isEqualTo(WRAPPER_SCRIPT_LOCATION); + assertThat(mvnw.getText()).isEqualTo(MVNW_TEXT); + assertThat(mvnw.getFileAttributes()).isNotNull(); + assertThat(mvnw.getFileAttributes().isReadable()).isTrue(); + assertThat(mvnw.getFileAttributes().isWritable()).isTrue(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getSourcePath()).isEqualTo(WRAPPER_BATCH_LOCATION); + assertThat(mvnwCmd.getText()).isEqualTo(MVNW_CMD_TEXT); + + var mavenWrapperJar = result(run, Remote.class, "maven-wrapper.jar"); + assertThat(mavenWrapperJar.getSourcePath()).isEqualTo(WRAPPER_JAR_LOCATION); + assertThat(mavenWrapperJar.getUri()).isEqualTo(URI.create("https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar")); + assertThat(isValidWrapperJar(mavenWrapperJar)).as("Wrapper jar is not valid").isTrue(); + }), + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + .afterRecipe(mavenWrapperProperties -> + assertThat(mavenWrapperProperties.getMarkers().findFirst(BuildTool.class)).hasValueSatisfying(buildTool -> { + assertThat(buildTool.getType()).isEqualTo(BuildTool.Type.Maven); + assertThat(buildTool.getVersion()).isEqualTo("3.8.8"); + })) + ), + mvnw, + mvnwCmd, + mvnWrapperJarQuark + ); + } + + @Test + void updateVersionUsingSourceDistribution() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper("3.1.x", "source", "3.8.x", null, null)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getText()).isNotBlank(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getText()).isNotBlank(); + + assertThatThrownBy(() -> result(run, SourceFile.class, "maven-wrapper.jar")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + + var mvnwDownloaderJava = result(run, Remote.class, "MavenWrapperDownloader.java"); + assertThat(mvnwDownloaderJava.getSourcePath()).isEqualTo(WRAPPER_DOWNLOADER_LOCATION); + assertThat(mvnwDownloaderJava.getUri()).isEqualTo(URI.create("https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper-distribution/3.1.1/maven-wrapper-distribution-3.1.1-source.zip")); + }), + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + other( + "", + null, + spec -> spec.path(".mvn/wrapper/maven-wrapper.jar") + ) + ); + } + + @Test + void updateVersionUsingScriptDistribution() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper("3.1.x", "script", "3.8.x", null, null)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getText()).isNotBlank(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getText()).isNotBlank(); + + assertThatThrownBy(() -> result(run, SourceFile.class, "maven-wrapper.jar")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + + assertThatThrownBy(() -> result(run, Remote.class, "MavenWrapperDownloader.java")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + }), + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + other( + "", + null, + spec -> spec.path(".mvn/wrapper/maven-wrapper.jar") + ) + ); + } + + @Test + void updateVersionUsingOnlyScriptDistribution() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper(null, "only-script", "3.8.x", null, null)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getText()).isNotBlank(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getText()).isNotBlank(); + + assertThatThrownBy(() -> result(run, Remote.class, "maven-wrapper.jar")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + + assertThatThrownBy(() -> result(run, Remote.class, "MavenWrapperDownloader.java")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + }), + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + other( + "", + null, + spec -> spec.path(".mvn/wrapper/maven-wrapper.jar") + ) + ); + } + + @Test + void dontAddMissingWrapper() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper("3.1.x", null, "3.8.x", null, Boolean.FALSE)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> assertThat(run.getChangeset().getAllResults()).isEmpty()) + ); + } + + @Test + void updateMultipleWrappers() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper("3.1.x", null, "3.8.x", null, Boolean.FALSE)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))), + dir("example1", + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + mvnWrapperJarQuark + ), + dir("example2", + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar + """), + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + mvnWrapperJarQuark + ) + ); + } + + @Test + void doNotDowngrade() { + rewriteRun( + spec -> spec.allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.9.0"))), + properties( + withLicenseHeader(""" + distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip + wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + text("", spec -> spec.path("mvnw")), + text("", spec -> spec.path("mvnw.cmd")), + other("", spec -> spec.path(".mvn/wrapper/maven-wrapper.jar")) + ); + } + + @Test + void allowUpdatingDistributionTypeWhenSameVersion() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper("3.1.x", "script", "3.8.x", null, null)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.8"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getText()).isNotBlank(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getText()).isNotBlank(); + + assertThatThrownBy(() -> result(run, SourceFile.class, "maven-wrapper.jar")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + + assertThatThrownBy(() -> result(run, SourceFile.class, "MavenWrapperDownloader.java")) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("No value present"); + }), + properties( + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + ), + mvnw, + mvnwCmd, + other( + "", + null, + spec -> spec.path(".mvn/wrapper/maven-wrapper.jar") + ), + other( + "", + null, + spec -> spec.path(".mvn/wrapper/MavenWrapperDownloader.java") + ) + ); + } + + private String withLicenseHeader(@Language("properties") String original) { + return MavenWrapper.ASF_LICENSE_HEADER + original; + } + + private S result(RecipeRun run, Class clazz, String endsWith) { + return run.getChangeset().getAllResults().stream() + .map(Result::getAfter) + .filter(Objects::nonNull) + .filter(r -> r.getSourcePath().endsWith(endsWith)) + .findFirst() + .map(clazz::cast) + .orElseThrow(); + } + + private boolean isValidWrapperJar(Remote gradleWrapperJar) { + try { + Path testWrapperJar = Files.createTempFile("maven-wrapper", "jar"); + ExecutionContext ctx = new InMemoryExecutionContext(); + HttpSenderExecutionContextView.view(ctx).setHttpSender(new HttpUrlConnectionSender(Duration.ofSeconds(5), Duration.ofSeconds(5))); + try (InputStream is = gradleWrapperJar.getInputStream(ctx)) { + Files.copy(is, testWrapperJar, StandardCopyOption.REPLACE_EXISTING); + try (FileSystem fs = FileSystems.newFileSystem(testWrapperJar)) { + return Files.exists(fs.getPath("org/apache/maven/wrapper/MavenWrapperMain.class")); + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/rewrite-maven/src/test/resources/mvnw b/rewrite-maven/src/test/resources/mvnw new file mode 100644 index 00000000000..b7f064624f8 --- /dev/null +++ b/rewrite-maven/src/test/resources/mvnw @@ -0,0 +1,287 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.1.1 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + printf '%s' "$(cd "$basedir"; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname $0)") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $wrapperUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + QUIET="--quiet" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + elif command -v curl > /dev/null; then + QUIET="--silent" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + QUIET="" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L + fi + [ $? -eq 0 ] || rm -f "$wrapperJarPath" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=`cygpath --path --windows "$javaSource"` + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/rewrite-maven/src/test/resources/mvnw.cmd b/rewrite-maven/src/test/resources/mvnw.cmd new file mode 100644 index 00000000000..474c9d6b74c --- /dev/null +++ b/rewrite-maven/src/test/resources/mvnw.cmd @@ -0,0 +1,187 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.1.1 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% From 96a60de43d130d426dec6b0eaa0364b909d72bc9 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Tue, 4 Jul 2023 15:13:37 -0400 Subject: [PATCH 021/447] Gradle and maven wrapper repositoryUrl can be blank --- .../remote/LocalRemoteArtifactCache.java | 4 +- .../gradle/util/GradleWrapper.java | 3 +- .../maven/utilities/MavenWrapper.java | 49 ++++++++++--------- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java b/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java index 90a91e4117d..e745b77de13 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java @@ -48,10 +48,12 @@ public Path get(URI uri) { @Nullable public Path put(URI uri, InputStream artifactInputStream, Consumer onError) { try { - Path artifact = cacheDir.resolve(hashUri(uri)); + Path artifact = cacheDir.resolve(hashUri(uri) + ".tmp"); try (InputStream is = artifactInputStream) { Files.copy(is, artifact, StandardCopyOption.REPLACE_EXISTING); } + Files.move(artifact, cacheDir.resolve(hashUri(uri)), StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); return artifact; } catch (Exception e) { onError.accept(e); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java index 7e9196b9890..47adbb547cd 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import lombok.Value; import org.openrewrite.*; +import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.ipc.http.HttpSender; import org.openrewrite.remote.Remote; @@ -62,7 +63,7 @@ public static GradleWrapper create(@Nullable String distributionTypeName, @Nulla new LatestRelease(null) : requireNonNull(Semver.validate(version, null).getValue()); - String gradleVersionsUrl = (repositoryUrl == null) ? "https://services.gradle.org/versions/all" : repositoryUrl; + String gradleVersionsUrl = (StringUtils.isBlank(repositoryUrl)) ? "https://services.gradle.org/versions/all" : repositoryUrl; try (HttpSender.Response resp = httpSender.send(httpSender.get(gradleVersionsUrl).build())) { if (resp.isSuccessful()) { List allVersions = new ObjectMapper() diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java index 555f6e1d50d..57b32e9582b 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java @@ -18,6 +18,7 @@ import lombok.Value; import org.openrewrite.Checksum; import org.openrewrite.ExecutionContext; +import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.internal.MavenPomDownloader; @@ -41,21 +42,21 @@ @Value public class MavenWrapper { public static final String ASF_LICENSE_HEADER = "# Licensed to the Apache Software Foundation (ASF) under one\n" + - "# or more contributor license agreements. See the NOTICE file\n" + - "# distributed with this work for additional information\n" + - "# regarding copyright ownership. The ASF licenses this file\n" + - "# to you under the Apache License, Version 2.0 (the\n" + - "# \"License\"); you may not use this file except in compliance\n" + - "# with the License. You may obtain a copy of the License at\n" + - "# \n" + - "# http://www.apache.org/licenses/LICENSE-2.0\n" + - "# \n" + - "# Unless required by applicable law or agreed to in writing,\n" + - "# software distributed under the License is distributed on an\n" + - "# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n" + - "# KIND, either express or implied. See the License for the\n" + - "# specific language governing permissions and limitations\n" + - "# under the License.\n"; + "# or more contributor license agreements. See the NOTICE file\n" + + "# distributed with this work for additional information\n" + + "# regarding copyright ownership. The ASF licenses this file\n" + + "# to you under the Apache License, Version 2.0 (the\n" + + "# \"License\"); you may not use this file except in compliance\n" + + "# with the License. You may obtain a copy of the License at\n" + + "# \n" + + "# http://www.apache.org/licenses/LICENSE-2.0\n" + + "# \n" + + "# Unless required by applicable law or agreed to in writing,\n" + + "# software distributed under the License is distributed on an\n" + + "# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n" + + "# KIND, either express or implied. See the License for the\n" + + "# specific language governing permissions and limitations\n" + + "# under the License.\n"; public static final String WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH = ".mvn/wrapper/MavenWrapperDownloader.java"; public static final String WRAPPER_JAR_LOCATION_RELATIVE_PATH = ".mvn/wrapper/maven-wrapper.jar"; public static final String WRAPPER_PROPERTIES_LOCATION_RELATIVE_PATH = ".mvn/wrapper/maven-wrapper.properties"; @@ -98,7 +99,7 @@ public static MavenWrapper create( new LatestRelease(null) : requireNonNull(Semver.validate(distributionVersion, null).getValue()); - MavenRepository repository = repositoryUrl == null ? + MavenRepository repository = StringUtils.isBlank(repositoryUrl) ? MavenRepository.MAVEN_CENTRAL : MavenRepository.builder() .uri(repositoryUrl) @@ -191,14 +192,14 @@ public Remote mvnwCmd() { private static String getDownloadUriFor(MavenRepository repository, GroupArtifact ga, String version, @Nullable String classifier, String extension) { return repository.getUri() + - "/" + - ga.getGroupId().replace(".", "/") + - "/" + - ga.getArtifactId() + - "/" + - version + - "/" + - ga.getArtifactId() + "-" + version + (classifier == null ? "" : "-" + classifier) + "." + extension; + "/" + + ga.getGroupId().replace(".", "/") + + "/" + + ga.getArtifactId() + + "/" + + version + + "/" + + ga.getArtifactId() + "-" + version + (classifier == null ? "" : "-" + classifier) + "." + extension; } public enum DistributionType { From a28ef451bbc929216783461fde6feb93ed1d1189 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Tue, 4 Jul 2023 15:26:42 -0400 Subject: [PATCH 022/447] Fix LocalRemoteArtifactCache path --- .../java/org/openrewrite/remote/LocalRemoteArtifactCache.java | 2 +- .../src/main/java/org/openrewrite/remote/RemoteArchive.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java b/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java index e745b77de13..51a6934a6af 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java @@ -54,7 +54,7 @@ public Path put(URI uri, InputStream artifactInputStream, Consumer on } Files.move(artifact, cacheDir.resolve(hashUri(uri)), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); - return artifact; + return cacheDir.resolve(hashUri(uri)); } catch (Exception e) { onError.accept(e); return null; diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java index bb736eba396..8a018209fc2 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java @@ -107,7 +107,7 @@ public InputStream getInputStream(ExecutionContext ctx) { } return inner; } catch (IOException e) { - throw new UncheckedIOException("Unable to download " + uri + " to temporary file", e); + throw new UncheckedIOException("Unable to download " + uri + " to file", e); } } From 3c81272bc976ae184a5fc3f76285bbc455a5f958 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Tue, 4 Jul 2023 21:56:32 +0200 Subject: [PATCH 023/447] Allow more context-free class-level statements When the block `firstStatement()` and `lastStatement()` coordinates are used a somewhat strange cursor gets created with the block as a child of itself. For this case and also other cases when an initializer block is added at the class level, the context-free templating was lacking support. --- .../internal/template/BlockStatementTemplateGenerator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java index 7b14e25aa22..2491c02e241 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java @@ -242,7 +242,9 @@ private void contextFreeTemplate(Cursor cursor, J j, StringBuilder before, Strin before.append("Object o = "); after.append(";"); after.append("\n}}"); - } else if (j instanceof J.MethodDeclaration || j instanceof J.VariableDeclarations && cursor.getValue() instanceof J.Block && cursor.getParent().getValue() instanceof J.ClassDeclaration) { + } else if ((j instanceof J.MethodDeclaration || j instanceof J.VariableDeclarations || j instanceof J.Block) + && cursor.getValue() instanceof J.Block + && (cursor.getParent().getValue() instanceof J.ClassDeclaration || cursor.getParent().getValue() instanceof J.NewClass)) { before.insert(0, "class Template {\n"); after.append("\n}"); } else if (!(j instanceof J.Import) && !(j instanceof J.Package)) { From bb5176ab448380711aa9b4083cec11fd737879ce Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Tue, 4 Jul 2023 22:13:51 +0200 Subject: [PATCH 024/447] Allow yet more context-free class level statements --- .../BlockStatementTemplateGenerator.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java index 2491c02e241..1fb335e2eae 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java @@ -214,22 +214,13 @@ private void template(Cursor cursor, J prior, StringBuilder before, StringBuilde if (contextSensitive) { contextTemplate(cursor, prior, before, after, insertionPoint, mode); } else { - contextFreeTemplate(cursor, prior, before, after); + contextFreeTemplate(cursor, prior, before, after, insertionPoint, mode); } } @SuppressWarnings("DataFlowIssue") - private void contextFreeTemplate(Cursor cursor, J j, StringBuilder before, StringBuilder after) { - if (j instanceof J.ClassDeclaration) { - // While not impossible to handle, reaching this point is likely to be a mistake. - // Without context a class declaration can include no imports, package, or outer class. - // It is a rare class that is deliberately in the root package with no imports. - // In the more likely case omission of these things is unintentional, the resulting type metadata would be - // incorrect, and it would not be obvious to the recipe author why. - throw new IllegalArgumentException( - "Templating a class declaration requires context from which package declaration and imports may be reached. " + - "Mark this template as context-sensitive by calling JavaTemplate.Builder#contextSensitive()."); - } else if (j instanceof J.Lambda) { + private void contextFreeTemplate(Cursor cursor, J j, StringBuilder before, StringBuilder after, J insertionPoint, JavaCoordinates.Mode mode) { + if (j instanceof J.Lambda) { throw new IllegalArgumentException( "Templating a lambda requires a cursor so that it can be properly parsed and type-attributed. " + "Mark this template as context-sensitive by calling JavaTemplate.Builder#contextSensitive()."); @@ -242,15 +233,25 @@ private void contextFreeTemplate(Cursor cursor, J j, StringBuilder before, Strin before.append("Object o = "); after.append(";"); after.append("\n}}"); - } else if ((j instanceof J.MethodDeclaration || j instanceof J.VariableDeclarations || j instanceof J.Block) + } else if ((j instanceof J.MethodDeclaration || j instanceof J.VariableDeclarations || j instanceof J.Block || j instanceof J.ClassDeclaration) && cursor.getValue() instanceof J.Block && (cursor.getParent().getValue() instanceof J.ClassDeclaration || cursor.getParent().getValue() instanceof J.NewClass)) { before.insert(0, "class Template {\n"); after.append("\n}"); - } else if (!(j instanceof J.Import) && !(j instanceof J.Package)) { + } else if (j instanceof J.ClassDeclaration) { + // While not impossible to handle, reaching this point is likely to be a mistake. + // Without context a class declaration can include no imports, package, or outer class. + // It is a rare class that is deliberately in the root package with no imports. + // In the more likely case omission of these things is unintentional, the resulting type metadata would be + // incorrect, and it would not be obvious to the recipe author why. + throw new IllegalArgumentException( + "Templating a class declaration requires context from which package declaration and imports may be reached. " + + "Mark this template as context-sensitive by calling JavaTemplate.Builder#contextSensitive()."); + } else if (j instanceof Statement && !(j instanceof J.Import) && !(j instanceof J.Package)) { before.insert(0, "class Template {{\n"); after.append("\n}}"); } + before.insert(0, EXPR_STATEMENT_PARAM + METHOD_INVOCATION_STUBS); for (String anImport : imports) { before.insert(0, anImport); From 6defd9f96f077948d829cecb7e4b741668cef8d1 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Tue, 4 Jul 2023 22:40:42 -0400 Subject: [PATCH 025/447] Add large file HTTP sender --- .../openrewrite/HttpSenderExecutionContextView.java | 10 ++++++++++ .../java/org/openrewrite/remote/RemoteArchive.java | 2 +- .../main/java/org/openrewrite/remote/RemoteFile.java | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/HttpSenderExecutionContextView.java b/rewrite-core/src/main/java/org/openrewrite/HttpSenderExecutionContextView.java index a0700c38fdf..b47752f898c 100644 --- a/rewrite-core/src/main/java/org/openrewrite/HttpSenderExecutionContextView.java +++ b/rewrite-core/src/main/java/org/openrewrite/HttpSenderExecutionContextView.java @@ -20,6 +20,7 @@ public class HttpSenderExecutionContextView extends DelegatingExecutionContext { private static final String HTTP_SENDER = "org.openrewrite.httpSender"; + private static final String LARGE_FILE_HTTP_SENDER = "org.openrewrite.largeFileHttpSender"; public HttpSenderExecutionContextView(ExecutionContext delegate) { super(delegate); @@ -40,4 +41,13 @@ public HttpSenderExecutionContextView setHttpSender(HttpSender httpSender) { public HttpSender getHttpSender() { return getMessage(HTTP_SENDER, new HttpUrlConnectionSender()); } + + public HttpSenderExecutionContextView setLargeFileHttpSender(HttpSender httpSender) { + putMessage(LARGE_FILE_HTTP_SENDER, httpSender); + return this; + } + + public HttpSender getLargeFileHttpSender() { + return getMessage(LARGE_FILE_HTTP_SENDER, new HttpUrlConnectionSender()); + } } diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java index 8a018209fc2..e32dfb9cf78 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteArchive.java @@ -87,7 +87,7 @@ public class RemoteArchive implements Remote { @Override public InputStream getInputStream(ExecutionContext ctx) { - HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); + HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getLargeFileHttpSender(); RemoteArtifactCache cache = RemoteExecutionContextView.view(ctx).getArtifactCache(); try { Path localArchive = cache.compute(uri, () -> { diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java index b4a0e91b910..622c1fcc228 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteFile.java @@ -63,7 +63,7 @@ public class RemoteFile implements Remote { @Override public InputStream getInputStream(ExecutionContext ctx) { - HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); + HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getLargeFileHttpSender(); RemoteArtifactCache cache = RemoteExecutionContextView.view(ctx).getArtifactCache(); try { Path localFile = cache.compute(uri, () -> { From 0d6d2052b878035aec7bf20088ce7fec40c64c65 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Tue, 4 Jul 2023 23:11:12 -0400 Subject: [PATCH 026/447] Fix test --- .../test/java/org/openrewrite/remote/RemoteArchiveTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java index 4d45023213d..8191e52fa6c 100644 --- a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java @@ -38,7 +38,8 @@ class RemoteArchiveTest { void gradleWrapper(String version) throws Exception { URL distributionUrl = requireNonNull(RemoteArchiveTest.class.getClassLoader().getResource("gradle-" + version + "-bin.zip")); ExecutionContext ctx = new InMemoryExecutionContext(); - HttpSenderExecutionContextView.view(ctx).setHttpSender(new MockHttpSender(distributionUrl::openStream)); + HttpSenderExecutionContextView.view(ctx) + .setLargeFileHttpSender(new MockHttpSender(distributionUrl::openStream)); RemoteArchive remoteArchive = Remote .builder( From 071185a5b10a207bff46c69d16c18db695be0f0d Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Wed, 5 Jul 2023 09:02:03 +0200 Subject: [PATCH 027/447] Improve performance of `UsesType` The `UsesType` visitor is very frequently used in the `Preconditions`-based checks of recipes and it therefore makes sense to optimize it for performance. --- .../openrewrite/java/search/UsesTypeTest.java | 27 +++++++++++++++++++ .../org/openrewrite/java/search/UsesType.java | 15 +++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/UsesTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/UsesTypeTest.java index 94916c5ef2b..755c4cdcfb8 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/UsesTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/UsesTypeTest.java @@ -121,6 +121,33 @@ class Test { ); } + /** + * Type wildcards are greedy. + */ + @Test + void usesRecursiveTypeWildcard() { + rewriteRun( + spec -> + spec.recipe(RewriteTest.toRecipe(() -> new UsesType<>("java..*", false))), + java( + """ + import java.io.File; + import static java.util.Collections.singleton; + + class Test { + } + """, + """ + /*~~>*/import java.io.File; + import static java.util.Collections.singleton; + + class Test { + } + """ + ) + ); + } + @Test void usesFullyQualifiedReference() { rewriteRun( diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java b/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java index c774ff89e8a..7503bed0831 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java @@ -27,13 +27,23 @@ import static java.util.Objects.requireNonNull; public class UsesType

extends JavaIsoVisitor

{ + + @Nullable + private final String fullyQualifiedType; + @Nullable private final Pattern typePattern; @Nullable private final Boolean includeImplicit; public UsesType(String fullyQualifiedType, @Nullable Boolean includeImplicit) { - this.typePattern = Pattern.compile(StringUtils.aspectjNameToPattern(fullyQualifiedType)); + if (fullyQualifiedType.contains("*")) { + this.fullyQualifiedType = null; + this.typePattern = Pattern.compile(StringUtils.aspectjNameToPattern(fullyQualifiedType)); + } else { + this.fullyQualifiedType = fullyQualifiedType; + this.typePattern = null; + } this.includeImplicit = includeImplicit; } @@ -87,7 +97,8 @@ private JavaSourceFile maybeMark(JavaSourceFile c, @Nullable JavaType type) { return c; } - if (TypeUtils.isAssignableTo(typePattern, type)) { + if (typePattern != null && TypeUtils.isAssignableTo(typePattern, type) + || fullyQualifiedType != null && TypeUtils.isAssignableTo(fullyQualifiedType, type)) { return SearchResult.found(c); } From 465476d2fda53946505320d71d5eb9a125118e3a Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Wed, 5 Jul 2023 09:31:16 +0200 Subject: [PATCH 028/447] Fix bug in `TypeUtils#isAssignableTo(String, JavaType)` This should also work when the given type name doesn't use `$` symbols for nested types. --- .../openrewrite/java/search/UsesTypeTest.java | 21 +++++++++++++++++++ .../org/openrewrite/java/tree/TypeUtils.java | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/UsesTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/UsesTypeTest.java index 755c4cdcfb8..6455cce320e 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/UsesTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/UsesTypeTest.java @@ -282,4 +282,25 @@ public void foo() { ) ); } + + @Test + void findNestedType() { + rewriteRun( + spec -> spec.recipe(RewriteTest.toRecipe(() -> new UsesType<>("java.util.Map.Entry", false))), + java( + """ + import java.util.Map.Entry; + + class Test { + } + """, + """ + /*~~>*/import java.util.Map.Entry; + + class Test { + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index b19c0fdc23a..bd58e1be018 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -41,7 +41,7 @@ public static boolean isString(@Nullable JavaType type) { public static boolean fullyQualifiedNamesAreEqual(@Nullable String fqn1, @Nullable String fqn2) { if (fqn1 != null && fqn2 != null) { return fqn1.equals(fqn2) || fqn1.length() == fqn2.length() - && fqn1.replace("$", ".").equals(fqn2.replace("$", ".")); + && fqn1.replace('$', '.').equals(fqn2.replace('$', '.')); } return fqn1 == null && fqn2 == null; } @@ -231,7 +231,7 @@ public static boolean isAssignableTo(String to, @Nullable JavaType from) { } } JavaType.FullyQualified classFrom = (JavaType.FullyQualified) from; - return to.equals(classFrom.getFullyQualifiedName()) || + return fullyQualifiedNamesAreEqual(to, classFrom.getFullyQualifiedName()) || isAssignableTo(to, classFrom.getSupertype()) || classFrom.getInterfaces().stream().anyMatch(i -> isAssignableTo(to, i)); } else if (from instanceof JavaType.GenericTypeVariable) { From d3ede5aa4f945ab9e9191f2e387d3b104c28228b Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Wed, 5 Jul 2023 09:00:46 -0400 Subject: [PATCH 029/447] Pass ExecutionContext down through MavenWrapper into Checksum --- .../src/main/java/org/openrewrite/Checksum.java | 12 ++++++------ .../openrewrite/maven/utilities/MavenWrapper.java | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/Checksum.java b/rewrite-core/src/main/java/org/openrewrite/Checksum.java index 918beb9c7cf..9a65325790e 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Checksum.java +++ b/rewrite-core/src/main/java/org/openrewrite/Checksum.java @@ -85,15 +85,15 @@ public static Checksum fromUri(HttpSender httpSender, URI uri, String algorithm) } } - public static SourceFile md5(SourceFile sourceFile) { - return checksum(sourceFile, "MD5"); + public static SourceFile md5(SourceFile sourceFile, ExecutionContext ctx) { + return checksum(sourceFile, "MD5", ctx); } - public static SourceFile sha256(SourceFile sourceFile) { - return checksum(sourceFile, "SHA-256"); + public static SourceFile sha256(SourceFile sourceFile, ExecutionContext ctx) { + return checksum(sourceFile, "SHA-256", ctx); } - public static SourceFile checksum(SourceFile sourceFile, @Nullable String algorithm) { + public static SourceFile checksum(SourceFile sourceFile, @Nullable String algorithm, ExecutionContext ctx) { if(algorithm == null) { return sourceFile; } @@ -102,7 +102,7 @@ public static SourceFile checksum(SourceFile sourceFile, @Nullable String algori MessageDigest md = MessageDigest.getInstance(algorithm); InputStream is; if (sourceFile instanceof Remote) { - is = ((Remote) sourceFile).getInputStream(new InMemoryExecutionContext()); + is = ((Remote) sourceFile).getInputStream(ctx); } else { is = Files.newInputStream(sourceFile.getSourcePath()); } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java index 57b32e9582b..7b389527547 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java @@ -132,12 +132,12 @@ public static MavenWrapper create( Remote wrapperJar = (Remote) Checksum.sha256(Remote.builder( WRAPPER_JAR_LOCATION, URI.create(resolvedWrapperUri) - ).build()); + ).build(), ctx); Remote mavenDistribution = (Remote) Checksum.sha256(Remote.builder( Paths.get(""), URI.create(resolvedDistributionUri) - ).build()); + ).build(), ctx); return new MavenWrapper( resolvedWrapperVersion, From 3c0d8db11432e055f3e0028cf8edd9f8798a1db5 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Wed, 5 Jul 2023 09:46:33 -0400 Subject: [PATCH 030/447] Use large http sender for downloading gradle wrapper --- .../gradle/UpdateGradleWrapper.java | 8 ++--- .../gradle/util/GradleWrapper.java | 3 +- .../openrewrite/maven/UpdateMavenWrapper.java | 31 ++++++++++--------- .../maven/utilities/MavenWrapper.java | 13 +++----- 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java index 50c16b9dfd5..af9d1279189 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java @@ -112,10 +112,8 @@ public Validated validate() { transient GradleWrapper gradleWrapper; private GradleWrapper getGradleWrapper(ExecutionContext ctx) { - if(gradleWrapper == null) { - HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); - gradleWrapper = GradleWrapper.create( - distribution, version, repositoryUrl,httpSender); + if (gradleWrapper == null) { + gradleWrapper = GradleWrapper.create(distribution, version, repositoryUrl, ctx); } return gradleWrapper; } @@ -195,7 +193,7 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionCon } if ((sourceFile instanceof Quark || sourceFile instanceof Remote) && - equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_JAR_LOCATION)) { + equalIgnoringSeparators(sourceFile.getSourcePath(), WRAPPER_JAR_LOCATION)) { acc.addGradleWrapperJar = false; return true; } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java index 47adbb547cd..2f8944ae98b 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java @@ -54,7 +54,7 @@ public class GradleWrapper { String version; DistributionInfos distributionInfos; - public static GradleWrapper create(@Nullable String distributionTypeName, @Nullable String version, @Nullable String repositoryUrl, HttpSender httpSender) { + public static GradleWrapper create(@Nullable String distributionTypeName, @Nullable String version, @Nullable String repositoryUrl, ExecutionContext ctx) { DistributionType distributionType = Arrays.stream(DistributionType.values()) .filter(dt -> dt.name().equalsIgnoreCase(distributionTypeName)) .findAny() @@ -63,6 +63,7 @@ public static GradleWrapper create(@Nullable String distributionTypeName, @Nulla new LatestRelease(null) : requireNonNull(Semver.validate(version, null).getValue()); + HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getLargeFileHttpSender(); String gradleVersionsUrl = (StringUtils.isBlank(repositoryUrl)) ? "https://services.gradle.org/versions/all" : repositoryUrl; try (HttpSender.Response resp = httpSender.send(httpSender.get(gradleVersionsUrl).build())) { if (resp.isSuccessful()) { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java index f98856588f8..b942448fa42 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenWrapper.java @@ -18,6 +18,7 @@ import lombok.*; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; +import org.intellij.lang.annotations.Language; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; @@ -47,7 +48,7 @@ /** * This recipe expects for the specified repository to be a Maven layout with `maven-metadata.xml` files containing all * the following REQUIRED publications: - * + *
* org.apache.maven.wrapper:maven-wrapper:{wrapperVersion} * org.apache.maven.wrapper:maven-wrapper-distribution:{wrapperVersion} * org.apache.maven:apache-maven:{distributionVersion} @@ -72,11 +73,11 @@ public class UpdateMavenWrapper extends ScanningRecipe generate(MavenWrapperState acc, ExecutionContext c ZonedDateTime now = ZonedDateTime.now(); if (acc.addMavenWrapperProperties) { + @Language("properties") String mavenWrapperPropertiesText = ASF_LICENSE_HEADER + - DISTRIBUTION_URL_KEY + "=" + mavenWrapper.getPropertiesFormattedDistributionUrl() + "\n" + - DISTRIBUTION_SHA_256_SUM_KEY + "=" + mavenWrapper.getDistributionChecksum().getHexValue(); + DISTRIBUTION_URL_KEY + "=" + mavenWrapper.getPropertiesFormattedDistributionUrl() + "\n" + + DISTRIBUTION_SHA_256_SUM_KEY + "=" + mavenWrapper.getDistributionChecksum().getHexValue(); if (mavenWrapper.getWrapperDistributionType() != DistributionType.OnlyScript) { mavenWrapperPropertiesText += "\n" + - WRAPPER_URL_KEY + "=" + mavenWrapper.getPropertiesFormattedWrapperUrl() + "\n" + - WRAPPER_SHA_256_SUM_KEY + "=" + mavenWrapper.getWrapperChecksum().getHexValue(); + WRAPPER_URL_KEY + "=" + mavenWrapper.getPropertiesFormattedWrapperUrl() + "\n" + + WRAPPER_SHA_256_SUM_KEY + "=" + mavenWrapper.getWrapperChecksum().getHexValue(); } //noinspection UnusedProperty Properties.File mavenWrapperProperties = new PropertiesParser().parse(mavenWrapperPropertiesText) @@ -391,7 +393,7 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { return null; } } else if (PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_JAR_LOCATION_RELATIVE_PATH) || - PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH)) { + PathUtils.matchesGlob(sourceFile.getSourcePath(), "**/" + WRAPPER_DOWNLOADER_LOCATION_RELATIVE_PATH)) { return null; } return sourceFile; @@ -443,7 +445,6 @@ public Properties.File visitFile(Properties.File file, ExecutionContext executio } @Override - @Nullable public Properties.Entry visitEntry(Properties.Entry entry, ExecutionContext executionContext) { if (DISTRIBUTION_URL_KEY.equals(entry.getKey())) { Properties.Value value = entry.getValue(); @@ -463,6 +464,7 @@ public Properties.Entry visitEntry(Properties.Entry entry, ExecutionContext exec return entry.withValue(value.withText(mavenWrapper.getPropertiesFormattedWrapperUrl())); } } else { + //noinspection ConstantConditions return null; } } else if (WRAPPER_SHA_256_SUM_KEY.equals(entry.getKey())) { @@ -473,6 +475,7 @@ public Properties.Entry visitEntry(Properties.Entry entry, ExecutionContext exec return entry.withValue(value.withText(wrapperJarChecksum.getHexValue())); } } else { + //noinspection ConstantConditions return null; } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java index 7b389527547..402088aeb5f 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java @@ -106,6 +106,7 @@ public static MavenWrapper create( .releases(true) .snapshots(true) .build(); + List repositories = Collections.singletonList(repository); try { GroupArtifact wrapperDistributionGroupArtifact = new GroupArtifact("org.apache.maven.wrapper", "maven-wrapper-distribution"); @@ -191,14 +192,10 @@ public Remote mvnwCmd() { } private static String getDownloadUriFor(MavenRepository repository, GroupArtifact ga, String version, @Nullable String classifier, String extension) { - return repository.getUri() + - "/" + - ga.getGroupId().replace(".", "/") + - "/" + - ga.getArtifactId() + - "/" + - version + - "/" + + return repository.getUri() + "/" + + ga.getGroupId().replace(".", "/") + "/" + + ga.getArtifactId() + "/" + + version + "/" + ga.getArtifactId() + "-" + version + (classifier == null ? "" : "-" + classifier) + "." + extension; } From 157d0c5eb7b865819f6e02a6915c5301f070e657 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Wed, 5 Jul 2023 17:01:00 -0400 Subject: [PATCH 031/447] Fix compilation in gradle Assertions --- .../src/main/java/org/openrewrite/gradle/Assertions.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/Assertions.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/Assertions.java index 332a9d0e031..2a9d267ad4c 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/Assertions.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/Assertions.java @@ -90,8 +90,7 @@ public static UncheckedConsumer> withToolingApi(@Nullable Strin } if (version != null) { - HttpSender httpSender = HttpSenderExecutionContextView.view(new InMemoryExecutionContext()).getHttpSender(); - GradleWrapper gradleWrapper = GradleWrapper.create(distribution, version, null,httpSender); + GradleWrapper gradleWrapper = GradleWrapper.create(distribution, version, null, new InMemoryExecutionContext()); Files.createDirectories(projectDir.resolve("gradle/wrapper/")); Files.write(projectDir.resolve(GradleWrapper.WRAPPER_PROPERTIES_LOCATION), ("distributionBase=GRADLE_USER_HOME\n" + "distributionPath=wrapper/dists\n" + From 27f42fb1cde07d86d884dc977a1953fd6420b173 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Thu, 6 Jul 2023 08:55:36 +0200 Subject: [PATCH 032/447] Optimize `JavaPrinter` for performance Most LST elements have no markers and most `Space` elements have no comments, but virtually all LST elements have a `Markers` object and `Space` object. Thus, it makes sense to optimize for this case in `JavaPrinter` by not using the enhanced for-loop, since that will as a side effect always allocate an `Iterator` object. --- .../java/org/openrewrite/java/JavaPrinter.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java index 30ed8494b85..9f07fcf7692 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java @@ -55,10 +55,13 @@ protected void visitContainer(String before, @Nullable JContainer c } @Override + @SuppressWarnings("ForLoopReplaceableByForEach") public Space visitSpace(Space space, Space.Location loc, PrintOutputCapture

p) { p.append(space.getWhitespace()); - for (Comment comment : space.getComments()) { + List comments = space.getComments(); + for (int i = 0; i < comments.size(); i++) { + Comment comment = comments.get(i); visitMarkers(comment.getMarkers(), p); comment.printComment(getCursor(), p); p.append(comment.getSuffix()); @@ -1137,15 +1140,19 @@ protected void beforeSyntax(J j, Space.Location loc, PrintOutputCapture

p) { beforeSyntax(j.getPrefix(), j.getMarkers(), loc, p); } + @SuppressWarnings("ForLoopReplaceableByForEach") protected void beforeSyntax(Space prefix, Markers markers, @Nullable Space.Location loc, PrintOutputCapture

p) { - for (Marker marker : markers.getMarkers()) { + List markersList = markers.getMarkers(); + for (int i = 0; i < markersList.size(); i++) { + Marker marker = markersList.get(i); p.append(p.getMarkerPrinter().beforePrefix(marker, new Cursor(getCursor(), marker), JAVA_MARKER_WRAPPER)); } if (loc != null) { visitSpace(prefix, loc, p); } visitMarkers(markers, p); - for (Marker marker : markers.getMarkers()) { + for (int i = 0; i < markersList.size(); i++) { + Marker marker = markersList.get(i); p.append(p.getMarkerPrinter().beforeSyntax(marker, new Cursor(getCursor(), marker), JAVA_MARKER_WRAPPER)); } } @@ -1154,8 +1161,11 @@ protected void afterSyntax(J j, PrintOutputCapture

p) { afterSyntax(j.getMarkers(), p); } + @SuppressWarnings("ForLoopReplaceableByForEach") protected void afterSyntax(Markers markers, PrintOutputCapture

p) { - for (Marker marker : markers.getMarkers()) { + List markersMarkers = markers.getMarkers(); + for (int i = 0; i < markersMarkers.size(); i++) { + Marker marker = markersMarkers.get(i); p.append(p.getMarkerPrinter().afterSyntax(marker, new Cursor(getCursor(), marker), JAVA_MARKER_WRAPPER)); } } From 0cc8c345e137ac7aa793bd44eb0ab546d42d7b5a Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Wed, 5 Jul 2023 22:41:38 -0400 Subject: [PATCH 033/447] Polish --- .../maven/ChangeParentPomTest.java | 710 ++++++++++-------- 1 file changed, 377 insertions(+), 333 deletions(-) diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java index 3d8d0a4794f..85a6fae5a3f 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java @@ -512,36 +512,41 @@ void keepsRedundantExplicitVersionsNotMatchingOldOrNewParent() { @Test void upgradeNonSemverVersion() { - rewriteRun(spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-starter-parent", null, "2021.0.5", null, false, null)), - pomXml(""" - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-starter-parent - Hoxton.SR12 - - - """, """ - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-starter-parent - 2021.0.5 - - - """)); + rewriteRun( + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-starter-parent", null, "2021.0.5", null, false, null)), + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + org.springframework.cloud + spring-cloud-starter-parent + Hoxton.SR12 + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + org.springframework.cloud + spring-cloud-starter-parent + 2021.0.5 + + + """ + ) + ); } @Nested @@ -550,313 +555,343 @@ class RetainVersions { @DocumentExample @Test void dependencyWithExplicitVersionRemovedFromDepMgmt() { - rewriteRun(spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, + rewriteRun( + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, List.of("com.jcraft:jsch"))), - pomXml(""" - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-config-dependencies - 3.1.2 - - - - - com.jcraft - jsch - 0.1.55 - - - - """, """ - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-config-dependencies - 3.1.4 - - - - - com.jcraft - jsch - 0.1.55 - - - - """)); + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + org.springframework.cloud + spring-cloud-config-dependencies + 3.1.2 + + + + + com.jcraft + jsch + 0.1.55 + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + org.springframework.cloud + spring-cloud-config-dependencies + 3.1.4 + + + + + com.jcraft + jsch + 0.1.55 + + + + """ + ) + ); } @Test void dependencyWithoutExplicitVersionRemovedFromDepMgmt() { - rewriteRun(spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, + rewriteRun( + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, Collections.singletonList("com.jcraft:jsch"))), - pomXml(""" - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-config-dependencies - 3.1.2 - - - - - com.jcraft - jsch - - - - """, """ - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-config-dependencies - 3.1.4 - - - - - com.jcraft - jsch - 0.1.55 - - - - """)); + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + org.springframework.cloud + spring-cloud-config-dependencies + 3.1.2 + + + + + com.jcraft + jsch + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + org.springframework.cloud + spring-cloud-config-dependencies + 3.1.4 + + + + + com.jcraft + jsch + 0.1.55 + + + + """ + ) + ); } @Test void dependencyWithoutExplicitVersionRemovedFromDepMgmtRetainSpecificVersion() { - rewriteRun(spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, + rewriteRun( + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, Collections.singletonList("com.jcraft:jsch:0.1.50"))), - pomXml(""" - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-config-dependencies - 3.1.2 - - - - - com.jcraft - jsch - - - - """, """ - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-config-dependencies - 3.1.4 - - - - - com.jcraft - jsch - 0.1.50 - - - - """)); + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + org.springframework.cloud + spring-cloud-config-dependencies + 3.1.2 + + + + + com.jcraft + jsch + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + org.springframework.cloud + spring-cloud-config-dependencies + 3.1.4 + + + + + com.jcraft + jsch + 0.1.50 + + + + """ + ) + ); } @Test void multipleRetainVersions() { - rewriteRun(spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, true, + rewriteRun( + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, true, Lists.newArrayList("com.jcraft:jsch", "org.springframework.cloud:spring-cloud-schema-registry-*:1.1.1"))), - pomXml(""" - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.1 - - - - - com.jcraft - jsch - - + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + org.springframework.cloud - spring-cloud-schema-registry-core - - - - """, """ - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-dependencies - 2021.0.5 - - - - - com.jcraft - jsch - 0.1.55 - - + spring-cloud-dependencies + 2020.0.1 + + + + + com.jcraft + jsch + + + org.springframework.cloud + spring-cloud-schema-registry-core + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + org.springframework.cloud - spring-cloud-schema-registry-core - 1.1.1 - - - - """)); + spring-cloud-dependencies + 2021.0.5 + + + + + com.jcraft + jsch + 0.1.55 + + + org.springframework.cloud + spring-cloud-schema-registry-core + 1.1.1 + + + + """ + ) + ); } @Test void globGavWithNoVersion() { - rewriteRun(spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, true, + rewriteRun( + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, true, Lists.newArrayList("org.springframework.cloud:spring-cloud-schema-registry-*"))), - pomXml(""" - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.1 - - - - + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + org.springframework.cloud - spring-cloud-schema-registry-core - - - - """, """ - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-dependencies - 2021.0.5 - - - - + spring-cloud-dependencies + 2020.0.1 + + + + + org.springframework.cloud + spring-cloud-schema-registry-core + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + org.springframework.cloud - spring-cloud-schema-registry-core - 1.1.1 - - - - """)); + spring-cloud-dependencies + 2021.0.5 + + + + + org.springframework.cloud + spring-cloud-schema-registry-core + 1.1.1 + + + + """ + ) + ); } @Test void preservesExplicitVersionIfNotRequested() { - rewriteRun(spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, true, + rewriteRun( + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, true, Lists.newArrayList("org.springframework.cloud:spring-cloud-schema-registry-*"))), - pomXml(""" - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-dependencies - 2020.0.1 - - - - + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + org.springframework.cloud - spring-cloud-schema-registry-core - 1.1.0 - - - - """, """ - - - 4.0.0 - org.sample - sample - 1.0.0 - - - org.springframework.cloud - spring-cloud-dependencies - 2021.0.5 - - - - + spring-cloud-dependencies + 2020.0.1 + + + + + org.springframework.cloud + spring-cloud-schema-registry-core + 1.1.0 + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + org.springframework.cloud - spring-cloud-schema-registry-core - 1.1.0 - - - - """)); + spring-cloud-dependencies + 2021.0.5 + + + + + org.springframework.cloud + spring-cloud-schema-registry-core + 1.1.0 + + + + """ + ) + ); } } @@ -864,9 +899,11 @@ void preservesExplicitVersionIfNotRequested() { @Issue("https://github.com/openrewrite/rewrite/issues/1753") void multiModule() { ChangeParentPom recipe = new ChangeParentPom("org.springframework.boot", null, "spring-boot-starter-parent", null, "2.6.7", null, true, null); - rewriteRun(spec -> spec.recipe(recipe), + rewriteRun( + spec -> spec.recipe(recipe), mavenProject("parent", - pomXml(""" + pomXml( + """ 4.0.0 @@ -905,33 +942,40 @@ void multiModule() { module2 - """), + """ + ), mavenProject("module1", - pomXml(""" - - - 4.0.0 - - org.sample - sample - 1.0.0 - - module1 - - """)), + pomXml( + """ + + + 4.0.0 + + org.sample + sample + 1.0.0 + + module1 + + """ + )), mavenProject("module2", - pomXml(""" - - - 4.0.0 - - org.sample - sample - 1.0.0 - - module2 - - """)) - )); + pomXml( + """ + + + 4.0.0 + + org.sample + sample + 1.0.0 + + module2 + + """ + ) + ) + ) + ); } } From 7aea1f10d72e3db4aafd142f91bd8026ba52e19d Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Thu, 6 Jul 2023 14:20:06 -0400 Subject: [PATCH 034/447] FindRepositoryOrder --- .../internal/FindRecipeRunException.java | 8 +- .../maven/search/FindRepositoryOrder.java | 83 +++++++++++++ .../maven/table/MavenRepositoryOrder.java | 50 ++++++++ .../maven/search/FindRepositoryOrderTest.java | 116 ++++++++++++++++++ 4 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/search/FindRepositoryOrder.java create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/table/MavenRepositoryOrder.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/search/FindRepositoryOrderTest.java diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/FindRecipeRunException.java b/rewrite-core/src/main/java/org/openrewrite/internal/FindRecipeRunException.java index 5ecd7fe0217..f2b9ec1eec5 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/FindRecipeRunException.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/FindRecipeRunException.java @@ -30,8 +30,12 @@ public class FindRecipeRunException extends TreeVisitor { public FindRecipeRunException(RecipeRunException rre) { this.vt = rre; - Iterator path = requireNonNull(rre.getCursor()).getPath(Tree.class::isInstance); - this.nearestTree = path.hasNext() ? (Tree) path.next() : null; + if (rre.getCursor() == null) { + this.nearestTree = null; + } else { + Iterator path = requireNonNull(rre.getCursor()).getPath(Tree.class::isInstance); + this.nearestTree = path.hasNext() ? (Tree) path.next() : null; + } } @Override diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindRepositoryOrder.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindRepositoryOrder.java new file mode 100644 index 00000000000..a333a175ea7 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindRepositoryOrder.java @@ -0,0 +1,83 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.maven.search; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.marker.SearchResult; +import org.openrewrite.maven.MavenExecutionContextView; +import org.openrewrite.maven.MavenIsoVisitor; +import org.openrewrite.maven.table.MavenRepositoryOrder; +import org.openrewrite.maven.tree.MavenRepository; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.xml.tree.Xml; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class FindRepositoryOrder extends Recipe { + transient MavenRepositoryOrder repositoryOrder = new MavenRepositoryOrder(this); + + @Override + public String getDisplayName() { + return "Maven repository order"; + } + + @Override + public String getDescription() { + return "Determine the order in which dependencies will be resolved for each `pom.xml` based on its defined repositories and effective `settings.xml`."; + } + + @Override + public TreeVisitor getVisitor() { + return new MavenIsoVisitor() { + @Override + public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + MavenResolutionResult mrr = getResolutionResult(); + + Map repositories = new LinkedHashMap<>(); + for (MavenRepository repository : mrr.getPom().getRepositories()) { + repositories.put(repository.getUri(), repository); + } + for (MavenRepository repository : MavenExecutionContextView.view(ctx) + .getRepositories( + mrr.getMavenSettings(), + StreamSupport.stream(mrr.getPom().getActiveProfiles().spliterator(), false) + .collect(Collectors.toList()) + )) { + repositories.put(repository.getUri(), repository); + } + + int i = 0; + for (MavenRepository repository : repositories.values()) { + repositoryOrder.insertRow(ctx, new MavenRepositoryOrder.Row( + repository.getId(), + repository.getUri(), + repository.isKnownToExist(), + i++ + )); + } + + return SearchResult.found(document, repositories.values().stream() + .map(MavenRepository::getUri) + .collect(Collectors.joining("\n"))); + } + }; + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/table/MavenRepositoryOrder.java b/rewrite-maven/src/main/java/org/openrewrite/maven/table/MavenRepositoryOrder.java new file mode 100644 index 00000000000..9404a089526 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/table/MavenRepositoryOrder.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.maven.table; + +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +public class MavenRepositoryOrder extends DataTable { + + public MavenRepositoryOrder(Recipe recipe) { + super(recipe, MavenRepositoryOrder.Row.class, + MavenRepositoryOrder.class.getName(), + "Maven repository order", + "The order in which dependencies will be resolved for each `pom.xml` based on its defined repositories and effective `settings.xml`."); + } + + @Value + public static class Row { + @Column(displayName = "Repository ID", + description = "The ID of the repository. Note that projects may define the same physical repository with different IDs.") + String id; + + @Column(displayName = "Repository URI", + description = "The URI of the repository.") + String uri; + + @Column(displayName = "Known to exist", + description = "If the repository is provably reachable. If false, does not guarantee that the repository does not exist.") + boolean knownToExist; + + @Column(displayName = "Rank", + description = "The index order of this repository in the repository list.") + int rank; + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindRepositoryOrderTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindRepositoryOrderTest.java new file mode 100644 index 00000000000..3d546b85d4a --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindRepositoryOrderTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.maven.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.maven.MavenExecutionContextView; +import org.openrewrite.maven.MavenSettings; +import org.openrewrite.maven.table.MavenRepositoryOrder; +import org.openrewrite.test.RewriteTest; + +import java.io.ByteArrayInputStream; +import java.nio.file.Paths; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.maven.Assertions.pomXml; + +public class FindRepositoryOrderTest implements RewriteTest { + + @Test + void findRepositoryOrder() { + var ctx = MavenExecutionContextView.view(new InMemoryExecutionContext()); + ctx.setMavenSettings(MavenSettings.parse(new Parser.Input(Paths.get("settings.xml"), () -> new ByteArrayInputStream( + //language=xml + """ + + + + repo + + + + + repo + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + + + """.getBytes() + )), ctx)); + + rewriteRun( + spec -> spec + .recipe(new FindRepositoryOrder()) + .executionContext(ctx) + .dataTable(MavenRepositoryOrder.Row.class, failures -> + assertThat(failures.stream().map(MavenRepositoryOrder.Row::getUri).distinct()) + .containsExactlyInAnyOrder( + "https://myrepo.maven.com/repo", + "https://repo.spring.io/milestone")), + pomXml( + """ + + + org.springframework.boot + spring-boot-starter-parent + 1.5.12.RELEASE + + + com.mycompany.app + my-app + 1 + + + myRepo + https://myrepo.maven.com/repo + + + + """, + """ + + + org.springframework.boot + spring-boot-starter-parent + 1.5.12.RELEASE + + + com.mycompany.app + my-app + 1 + + + myRepo + https://myrepo.maven.com/repo + + + + """ + ) + ); + } +} From 379e82f0002e400581267db203fa1814e56c4476 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Fri, 7 Jul 2023 00:31:34 -0400 Subject: [PATCH 035/447] Remove group and artifact id from rewrite.maven.download metric --- .../openrewrite/maven/internal/MavenPomDownloader.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index ea3c592060e..7c94fac0671 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -208,10 +208,7 @@ public MavenMetadata downloadMetadata(GroupArtifactVersion gav, @Nullable Resolv } Timer.Sample sample = Timer.start(); - Timer.Builder timer = Timer.builder("rewrite.maven.download") - .tag("group.id", gav.getGroupId()) - .tag("artifact.id", gav.getArtifactId()) - .tag("type", "metadata"); + Timer.Builder timer = Timer.builder("rewrite.maven.download").tag("type", "metadata"); MavenMetadata mavenMetadata = null; Collection normalizedRepos = distinctNormalizedRepositories(repositories, containingPom, null); @@ -480,10 +477,7 @@ public Pom download(GroupArtifactVersion gav, Collection normalizedRepos = distinctNormalizedRepositories(repositories, containingPom, gav.getVersion()); Timer.Sample sample = Timer.start(); - Timer.Builder timer = Timer.builder("rewrite.maven.download") - .tag("group.id", gav.getGroupId()) - .tag("artifact.id", gav.getArtifactId()) - .tag("type", "pom"); + Timer.Builder timer = Timer.builder("rewrite.maven.download").tag("type", "pom"); Map repositoryResponses = new LinkedHashMap<>(); String versionMaybeDatedSnapshot = datedSnapshotVersion(gav, containingPom, repositories, ctx); From f6bd415e116795df8b1516f7dbda7574a0aa56f9 Mon Sep 17 00:00:00 2001 From: Tracey Yoshima Date: Fri, 7 Jul 2023 13:36:52 -0600 Subject: [PATCH 036/447] Only return the fully matched items from a sequence of binary expressions in JsonPathMatcher. (#3403) --- .../org/openrewrite/json/JsonPathMatcher.java | 11 ++++ .../openrewrite/json/JsonPathMatcherTest.java | 66 +++++++++++++++++++ .../openrewrite/json/search/FindKeyTest.java | 54 +++++++++++++++ .../org/openrewrite/yaml/JsonPathMatcher.java | 11 ++++ .../openrewrite/yaml/JsonPathMatcherTest.java | 54 +++++++++++++++ .../openrewrite/yaml/search/FindKeyTest.java | 41 ++++++++++++ 6 files changed, 237 insertions(+) diff --git a/rewrite-json/src/main/java/org/openrewrite/json/JsonPathMatcher.java b/rewrite-json/src/main/java/org/openrewrite/json/JsonPathMatcher.java index 5decead4dba..df595e5f5b9 100644 --- a/rewrite-json/src/main/java/org/openrewrite/json/JsonPathMatcher.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/JsonPathMatcher.java @@ -572,6 +572,17 @@ public Object visitBinaryExpression(JsonPathParser.BinaryExpressionContext ctx) rhs = getBinaryExpressionResult(rhs); if ("&&".equals(operator) && ((lhs != null && (!(lhs instanceof List) || !((List) lhs).isEmpty())) && (rhs != null && (!(rhs instanceof List) || !((List) rhs).isEmpty())))) { + // Return the result of the evaluated expression. + if (lhs instanceof Json) { + return rhs; + } else if (rhs instanceof Json) { + return lhs; + } + + // Return the result of the expression that has the fewest matches. + if (lhs instanceof List && rhs instanceof List && ((List) lhs).size() != ((List) rhs).size()) { + return ((List) lhs).size() < ((List) rhs).size() ? lhs : rhs; + } return scopeOfLogicalOp; } else if ("||".equals(operator) && ((lhs != null && (!(lhs instanceof List) || !((List) lhs).isEmpty())) || (rhs != null && (!(rhs instanceof List) || !((List) rhs).isEmpty())))) { diff --git a/rewrite-json/src/test/java/org/openrewrite/json/JsonPathMatcherTest.java b/rewrite-json/src/test/java/org/openrewrite/json/JsonPathMatcherTest.java index 6967710a3b0..de1aed63b68 100644 --- a/rewrite-json/src/test/java/org/openrewrite/json/JsonPathMatcherTest.java +++ b/rewrite-json/src/test/java/org/openrewrite/json/JsonPathMatcherTest.java @@ -22,6 +22,7 @@ import org.openrewrite.json.tree.Space; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -749,6 +750,71 @@ void matchesListWithByFilterConditionInList() { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/3401") + @Test + void multipleBinaryExpressions() { + assertMatched( + "$.foo.bar[?(@.types == 'something' && @.group == 'group' && @.category == 'match' && @.type == 'type')].pattern", + List.of( + """ + { + "foo": { + "bar": [ + { + "type": "type", + "group": "group", + "category": "other", + "types": "something", + "pattern": "p1" + }, + { + "type": "type", + "group": "group", + "category": "match", + "types": "something", + "pattern": "p2" + } + ] + } + } + """), + List.of("\"pattern\": \"p2\"" + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/3401") + @Test + void noMatchWithBinaryExpressions() { + assertMatched( + "$.foo.bar[?(@.types == 'something' && @.group == 'group' && @.category == 'nomatch' && @.type == 'type')].pattern", + List.of( + """ + { + "foo": { + "bar": [ + { + "type": "type", + "group": "group", + "category": "other", + "types": "something", + "pattern": "p1" + }, + { + "type": "type", + "group": "group", + "category": "match", + "types": "something", + "pattern": "p2" + } + ] + } + } + """), + Collections.emptyList() + ); + } + @Test void returnResultsWithVisitDocument() { var matcher = new JsonPathMatcher("$.root.literal"); diff --git a/rewrite-json/src/test/java/org/openrewrite/json/search/FindKeyTest.java b/rewrite-json/src/test/java/org/openrewrite/json/search/FindKeyTest.java index bb3772a6552..0e7482bc3b2 100644 --- a/rewrite-json/src/test/java/org/openrewrite/json/search/FindKeyTest.java +++ b/rewrite-json/src/test/java/org/openrewrite/json/search/FindKeyTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; import static org.openrewrite.json.Assertions.json; @@ -50,4 +51,57 @@ void findKey() { ) ); } + + @Issue("https://github.com/openrewrite/rewrite/issues/3401") + @Test + void findKeyWithMultipleBinaryExpressions() { + rewriteRun( + spec -> spec.recipe(new FindKey("$.foo.bar[?(@.types == 'something' && @.group == 'group' && @.category == 'match' && @.type == 'type')].pattern")), + json(""" + { + "foo": { + "bar": [ + { + "type": "type", + "group": "group", + "category": "other", + "types": "something", + "pattern": "p1" + }, + { + "type": "type", + "group": "group", + "category": "match", + "types": "something", + "pattern": "p2" + } + ] + } + } + """, + """ + { + "foo": { + "bar": [ + { + "type": "type", + "group": "group", + "category": "other", + "types": "something", + "pattern": "p1" + }, + { + "type": "type", + "group": "group", + "category": "match", + "types": "something", + /*~~>*/"pattern": "p2" + } + ] + } + } + """ + ) + ); + } } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/JsonPathMatcher.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/JsonPathMatcher.java index 7e1ced67633..645149b8048 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/JsonPathMatcher.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/JsonPathMatcher.java @@ -587,6 +587,17 @@ public Object visitBinaryExpression(JsonPathParser.BinaryExpressionContext ctx) if ("&&".equals(operator) && ((lhs != null && (!(lhs instanceof List) || !((List) lhs).isEmpty())) && (rhs != null && (!(rhs instanceof List) || !((List) rhs).isEmpty())))) { + // Return the result of the evaluated expression. + if (lhs instanceof Yaml) { + return rhs; + } else if (rhs instanceof Yaml) { + return lhs; + } + + // Return the result of the expression that has the fewest matches. + if (lhs instanceof List && rhs instanceof List && ((List) lhs).size() != ((List) rhs).size()) { + return ((List) lhs).size() < ((List) rhs).size() ? lhs : rhs; + } return scopeOfLogicalOp; } else if ("||".equals(operator) && ((lhs != null && (!(lhs instanceof List) || !((List) lhs).isEmpty())) || diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/JsonPathMatcherTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/JsonPathMatcherTest.java index f6cc4b8cfad..2a90331d9c8 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/JsonPathMatcherTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/JsonPathMatcherTest.java @@ -21,6 +21,7 @@ import org.openrewrite.yaml.tree.Yaml; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -677,6 +678,59 @@ void matchesListWithByFilterConditionInList() { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/3401") + @Test + void multipleBinaryExpressions() { + assertMatched( + "$.foo.bar[?(@.types == 'something' && @.group == 'group' && @.category == 'match' && @.type == 'type')].pattern", + List.of( + """ + foo: + bar: + - + type: "type" + group: "group" + category: "other" + types: "something" + pattern: "p1" + - + type: "type" + group: "group" + category: "match" + types: "something" + pattern: "p2" + """), + List.of("pattern: \"p2\"" + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/3401") + @Test + void noMatchWithBinaryExpressions() { + assertMatched( + "$.foo.bar[?(@.types == 'something' && @.group == 'group' && @.category == 'nomatch' && @.type == 'type')].pattern", + List.of( + """ + foo: + bar: + - + type: "type" + group: "group" + category: "other" + types: "something" + pattern: "p1" + - + type: "type" + group: "group" + category: "match" + types: "something" + pattern: "p2" + """), + Collections.emptyList() + ); + } + @Test void returnResultsWithVisitDocument() { // var ctx = InMemoryExecutionContext diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/search/FindKeyTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/search/FindKeyTest.java index 050f21f3c14..f70c6431384 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/search/FindKeyTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/search/FindKeyTest.java @@ -63,4 +63,45 @@ void findKeyWithSpecificName() { ) ); } + + @Issue("https://github.com/openrewrite/rewrite/issues/3401") + @Test + void findKeyWithMultipleBinaryExpressions() { + rewriteRun( + spec -> spec.recipe(new FindKey("$.foo.bar[?(@.types == 'something' && @.group == 'group' && @.category == 'match' && @.type == 'type')].pattern")), + yaml(""" + foo: + bar: + - + type: "type" + group: "group" + category: "other" + types: "something" + pattern: "p1" + - + type: "type" + group: "group" + category: "match" + types: "something" + pattern: "p2" + """, + """ + foo: + bar: + - + type: "type" + group: "group" + category: "other" + types: "something" + pattern: "p1" + - + type: "type" + group: "group" + category: "match" + types: "something" + ~~>pattern: "p2" + """ + ) + ); + } } From bc98018efdf6e5710a6eaf7bdeab79a4557e0a2c Mon Sep 17 00:00:00 2001 From: Nick McKinney Date: Fri, 7 Jul 2023 16:31:41 -0400 Subject: [PATCH 037/447] Made AddDependency$Scanned public facilitates composition with other scanning recipes, eg, for https://github.com/openrewrite/rewrite-migrate-java/issues/254 --- .../src/main/java/org/openrewrite/maven/AddDependency.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java index 0887047b90e..cec503f5647 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java @@ -154,7 +154,7 @@ public String getDescription() { return "Add a Maven dependency to a `pom.xml` file in the correct scope based on where it is used."; } - static class Scanned { + public static class Scanned { boolean usingType; Map scopeByProject = new HashMap<>(); } From 632ff0a525bf912cebeab05623224a56fef74365 Mon Sep 17 00:00:00 2001 From: Nick McKinney Date: Fri, 7 Jul 2023 16:59:57 -0400 Subject: [PATCH 038/447] JavaTemplate.Builder.imports - throw on blank import (#3402) --- .../java/org/openrewrite/java/JavaTemplate.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java index 6c13de7806e..61fcb34a7a4 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java @@ -144,31 +144,28 @@ public Builder contextSensitive() { public Builder imports(String... fullyQualifiedTypeNames) { for (String typeName : fullyQualifiedTypeNames) { - if (shouldAddImport(typeName)) { - this.imports.add("import " + typeName + ";\n"); - } + validateImport(typeName); + this.imports.add("import " + typeName + ";\n"); } return this; } public Builder staticImports(String... fullyQualifiedMemberTypeNames) { for (String typeName : fullyQualifiedMemberTypeNames) { - if (shouldAddImport(typeName)) { - this.imports.add("import static " + typeName + ";\n"); - } + validateImport(typeName); + this.imports.add("import static " + typeName + ";\n"); } return this; } - private boolean shouldAddImport(String typeName) { + private void validateImport(String typeName) { if (StringUtils.isBlank(typeName)) { - return false; + throw new IllegalArgumentException("Imports must not be blank"); } else if (typeName.startsWith("import ") || typeName.startsWith("static ")) { throw new IllegalArgumentException("Imports are expressed as fully-qualified names and should not include an \"import \" or \"static \" prefix"); } else if (typeName.endsWith(";") || typeName.endsWith("\n")) { throw new IllegalArgumentException("Imports are expressed as fully-qualified names and should not include a suffixed terminator"); } - return true; } public Builder javaParser(JavaParser.Builder javaParser) { From b5f0d7e46441cdd91e82b8600d112366544e4e38 Mon Sep 17 00:00:00 2001 From: Tracey Yoshima Date: Fri, 7 Jul 2023 18:59:12 -0600 Subject: [PATCH 039/447] Added optional filters on find parse failures. (#3405) --- .../org/openrewrite/FindParseFailures.java | 20 +++++++++++++++++++ .../openrewrite/FindParseFailuresTest.java | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/FindParseFailures.java b/rewrite-core/src/main/java/org/openrewrite/FindParseFailures.java index c3754507918..dd408f46a88 100644 --- a/rewrite-core/src/main/java/org/openrewrite/FindParseFailures.java +++ b/rewrite-core/src/main/java/org/openrewrite/FindParseFailures.java @@ -32,6 +32,18 @@ public class FindParseFailures extends Recipe { @Nullable Integer maxSnippetLength; + @Option(displayName = "Parser name", + description = "Only display specified parser failures.", + required = false) + @Nullable + String parserType; + + @Option(displayName = "Stack trace", + description = "Only mark specified stack traces.", + required = false) + @Nullable + String stackTrace; + transient ParseFailures failures = new ParseFailures(this); @Override @@ -53,6 +65,14 @@ public TreeVisitor getVisitor() { public Tree postVisit(Tree tree, ExecutionContext ctx) { return tree.getMarkers().findFirst(ParseExceptionResult.class) .map(exceptionResult -> { + if (parserType != null && !exceptionResult.getParserType().equals(parserType)) { + return tree; + } + + if (stackTrace != null && !exceptionResult.getMessage().contains(stackTrace)) { + return tree; + } + String snippet = tree instanceof SourceFile ? null : tree.printTrimmed(getCursor()); if(snippet != null && maxSnippetLength != null && snippet.length() > maxSnippetLength) { snippet = snippet.substring(0, maxSnippetLength); diff --git a/rewrite-core/src/test/java/org/openrewrite/FindParseFailuresTest.java b/rewrite-core/src/test/java/org/openrewrite/FindParseFailuresTest.java index a9be1113514..228db90be1e 100644 --- a/rewrite-core/src/test/java/org/openrewrite/FindParseFailuresTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/FindParseFailuresTest.java @@ -32,7 +32,7 @@ public class FindParseFailuresTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new FindParseFailures(5)); + spec.recipe(new FindParseFailures(5, null, null)); } @Test From 0366306c20e66752329f4e41a8a12297bbac2720 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 7 Jul 2023 09:37:40 +0200 Subject: [PATCH 040/447] Move some annotations on `JavaType` --- .../org/openrewrite/java/tree/JavaType.java | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java index 44c2e15c189..30702218c45 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java @@ -31,7 +31,8 @@ import java.util.regex.Pattern; import static java.util.Collections.*; -import static org.openrewrite.internal.ListUtils.*; +import static org.openrewrite.internal.ListUtils.arrayOrNullIfEmpty; +import static org.openrewrite.internal.ListUtils.nullIfEmpty; import static org.openrewrite.java.tree.TypeUtils.unknownIfNull; @SuppressWarnings("unused") @@ -51,6 +52,7 @@ default String getJacksonPolymorphicTypeTag() { return getClass().getName(); } + // TODO: To be removed with OpenRewrite 9 @Nullable default Integer getManagedReference() { return null; @@ -107,11 +109,14 @@ public MultiCatch(@Nullable List throwableTypes) { this.throwableTypes = arrayOrNullIfEmpty(throwableTypes, EMPTY_JAVA_TYPE_ARRAY); } - @JsonCreator MultiCatch(@Nullable JavaType[] throwableTypes) { this.throwableTypes = nullIfEmpty(throwableTypes); } + @JsonCreator + MultiCatch() { + } + private JavaType[] throwableTypes; public List getThrowableTypes() { @@ -322,12 +327,15 @@ class Class extends FullyQualified { Integer managedReference; @With(AccessLevel.NONE) + @NonFinal long flagsBitMap; @With + @NonFinal String fullyQualifiedName; @With + @NonFinal Kind kind; @NonFinal @@ -367,7 +375,6 @@ public Class(@Nullable Integer managedReference, long flagsBitMap, String fullyQ ); } - @JsonCreator Class(@Nullable Integer managedReference, long flagsBitMap, String fullyQualifiedName, Kind kind, @Nullable JavaType[] typeParameters, @Nullable FullyQualified supertype, @Nullable FullyQualified owningClass, @Nullable FullyQualified[] annotations, @Nullable FullyQualified[] interfaces, @@ -386,6 +393,10 @@ public Class(@Nullable Integer managedReference, long flagsBitMap, String fullyQ this.methods = nullIfEmpty(methods); } + @JsonCreator + Class() { + } + public List getAnnotations() { return annotations == null ? emptyList() : Arrays.asList(annotations); } @@ -543,6 +554,10 @@ public ShallowClass(@Nullable Integer managedReference, long flagsBitMap, String super(managedReference, flagsBitMap, fullyQualifiedName, kind, (List) null, null, owningClass, null, null, null, null); } + @JsonCreator + ShallowClass() { + } + /** * Build a class type only from the class' fully qualified name. * @@ -601,7 +616,6 @@ public Parameterized(@Nullable Integer managedReference, @Nullable FullyQualifie ); } - @JsonCreator Parameterized(@Nullable Integer managedReference, @Nullable FullyQualified type, @Nullable JavaType[] typeParameters) { this.managedReference = managedReference; @@ -609,6 +623,10 @@ public Parameterized(@Nullable Integer managedReference, @Nullable FullyQualifie this.typeParameters = nullIfEmpty(typeParameters); } + @JsonCreator + Parameterized() { + } + public FullyQualified getType() { return type; } @@ -745,7 +763,6 @@ public GenericTypeVariable(@Nullable Integer managedReference, String name, Vari ); } - @JsonCreator GenericTypeVariable(@Nullable Integer managedReference, String name, Variance variance, @Nullable JavaType[] bounds) { this.managedReference = managedReference; this.name = name; @@ -753,6 +770,10 @@ public GenericTypeVariable(@Nullable Integer managedReference, String name, Vari this.bounds = nullIfEmpty(bounds); } + @JsonCreator + GenericTypeVariable() { + } + public List getBounds() { return bounds == null ? emptyList() : Arrays.asList(bounds); } @@ -822,6 +843,10 @@ public Array(@Nullable Integer managedReference, @Nullable JavaType elemType) { this.elemType = unknownIfNull(elemType); } + @JsonCreator + Array() { + } + public JavaType getElemType() { return elemType; } @@ -975,6 +1000,7 @@ class Method implements JavaType { Integer managedReference; @With(AccessLevel.PRIVATE) + @NonFinal long flagsBitMap; @With @@ -982,6 +1008,7 @@ class Method implements JavaType { FullyQualified declaringType; @With + @NonFinal String name; @With @@ -989,6 +1016,7 @@ class Method implements JavaType { JavaType returnType; @Nullable + @NonFinal String[] parameterNames; @NonFinal @@ -1005,6 +1033,7 @@ class Method implements JavaType { @Incubating(since = "7.34.0") @Nullable + @NonFinal List defaultValue; public Method(@Nullable Integer managedReference, long flagsBitMap, @Nullable FullyQualified declaringType, String name, @@ -1033,7 +1062,6 @@ public Method(@Nullable Integer managedReference, long flagsBitMap, @Nullable Fu ); } - @JsonCreator public Method(@Nullable Integer managedReference, long flagsBitMap, @Nullable FullyQualified declaringType, String name, @Nullable JavaType returnType, @Nullable String[] parameterNames, @Nullable JavaType[] parameterTypes, @Nullable FullyQualified[] thrownExceptions, @@ -1050,6 +1078,10 @@ public Method(@Nullable Integer managedReference, long flagsBitMap, @Nullable Fu this.defaultValue = nullIfEmpty(defaultValue); } + @JsonCreator + Method() { + } + @Override public Method unsafeSetManagedReference(Integer id) { this.managedReference = id; @@ -1247,9 +1279,11 @@ class Variable implements JavaType { Integer managedReference; @With(AccessLevel.PRIVATE) + @NonFinal long flagsBitMap; @With + @NonFinal String name; @With @@ -1277,7 +1311,6 @@ public Variable(@Nullable Integer managedReference, long flagsBitMap, String nam ); } - @JsonCreator Variable(@Nullable Integer managedReference, long flagsBitMap, String name, @Nullable JavaType owner, @Nullable JavaType type, @Nullable FullyQualified[] annotations) { this.managedReference = managedReference; @@ -1288,6 +1321,10 @@ public Variable(@Nullable Integer managedReference, long flagsBitMap, String nam this.annotations = nullIfEmpty(annotations); } + @JsonCreator + Variable() { + } + @Nullable public JavaType getOwner() { return owner; From 9ef0b76add16eb15e7dcae0a15588ce32543a0b8 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Sun, 9 Jul 2023 15:04:33 -0400 Subject: [PATCH 041/447] Build metadata marker --- .../org/openrewrite/marker/BuildMetadata.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 rewrite-core/src/main/java/org/openrewrite/marker/BuildMetadata.java diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/BuildMetadata.java b/rewrite-core/src/main/java/org/openrewrite/marker/BuildMetadata.java new file mode 100644 index 00000000000..2d540d1f3f4 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/marker/BuildMetadata.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.marker; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.With; + +import java.util.Map; +import java.util.UUID; + +@Value +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@With +public class BuildMetadata implements Marker { + @EqualsAndHashCode.Include + UUID id; + + Map metadata; +} From b29e875c7c3ced8f469c3a0b1b46d02f9e59d1a8 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Sun, 9 Jul 2023 16:32:25 -0400 Subject: [PATCH 042/447] FindBuildMetadata --- .../openrewrite/search/FindBuildMetadata.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 rewrite-core/src/main/java/org/openrewrite/search/FindBuildMetadata.java diff --git a/rewrite-core/src/main/java/org/openrewrite/search/FindBuildMetadata.java b/rewrite-core/src/main/java/org/openrewrite/search/FindBuildMetadata.java new file mode 100644 index 00000000000..d439112b6aa --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/search/FindBuildMetadata.java @@ -0,0 +1,67 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.BuildMetadata; +import org.openrewrite.marker.SearchResult; + +@Value +@EqualsAndHashCode(callSuper = false) +public class FindBuildMetadata extends Recipe { + + @Option(displayName = "Build metadata key", + description = "The key to search for in the build metadata.", + example = "lstFormatVersion") + String key; + + @Option(displayName = "Build metadata value", + description = "The value to search for in the build metadata.", + example = "2") + String value; + + @Override + public String getDisplayName() { + return "Find build metadata"; + } + + @Override + public String getDescription() { + return "Find source files with matching build metadata."; + } + + @Override + public TreeVisitor getVisitor() { + return new TreeVisitor() { + @Override + public Tree visit(@Nullable Tree tree, ExecutionContext executionContext) { + if (tree instanceof SourceFile) { + for (BuildMetadata buildMetadata : tree.getMarkers().findAll(BuildMetadata.class)) { + if (buildMetadata.getMetadata().containsKey(key)) { + if (buildMetadata.getMetadata().get(key).equals(value)) { + return SearchResult.found(tree, "Found build metadata"); + } + } + } + } + return tree; + } + }; + } +} From c27fcb269be0680e04929b5c015994bbf865bed7 Mon Sep 17 00:00:00 2001 From: tclayton-newr <87031785+tclayton-newr@users.noreply.github.com> Date: Mon, 10 Jul 2023 08:20:23 -0400 Subject: [PATCH 043/447] Issue 3275/fix remove used param (#3397) * adding class path matching predicate * adding test for issue * separating tests * updating util with qualified name predicate --- .../java/RemoveUnusedImportsTest.java | 66 +++++++++++++++++++ .../openrewrite/java/RemoveUnusedImports.java | 6 +- .../org/openrewrite/java/tree/TypeUtils.java | 4 ++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java index f3340129077..04b0718d527 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java @@ -1072,6 +1072,72 @@ public static void main(String[] args) { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/3275") + @Test + void doesNotRemoveReferencedClassesBeingUsedAsParameters() { + rewriteRun( + java( + """ + package com.Source.mine; + + public class A { + public static final short SHORT1 = (short)1; + + public short getShort1() { + return SHORT1; + } + } + """ + ), + java( + """ + package com.test; + + import com.Source.mine.A; + + class Test { + void f(A classA) { + classA.getShort1(); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/3275") + @Test + void doesNotRemoveReferencedClassesBeingUsedAsParametersUnusualPackageName() { + rewriteRun( + java( + """ + package com.Source.$; + + public class A { + public static final short SHORT1 = (short)1; + + public short getShort1() { + return SHORT1; + } + } + """ + ), + java( + """ + package com.test; + + import com.Source.$.A; + + class Test { + void f(A classA) { + classA.getShort1(); + } + } + """ + ) + ); + } + @Test void removeImportUsedAsLambdaParameter() { rewriteRun( diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java index 98c1a2166aa..f947cbfba60 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java @@ -26,9 +26,11 @@ import java.time.Duration; import java.util.*; +import java.util.function.Predicate; import static java.util.Collections.emptySet; import static org.openrewrite.java.style.ImportLayoutStyle.isPackageAlwaysFolded; +import static org.openrewrite.java.tree.TypeUtils.fullyQualifiedNamesAreEqualAsPredicate; /** * This recipe will remove any imports for types that are not referenced within the compilation unit. This recipe @@ -135,7 +137,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon // see https://github.com/openrewrite/rewrite/issues/1698 for more detail String target = qualid.getTarget().toString(); String modifiedTarget = methodsAndFieldsByTypeName.keySet().stream() - .filter(key -> key.matches(target.replaceAll("\\.", "(\\\\\\.|\\\\\\$)"))) + .filter(fullyQualifiedNamesAreEqualAsPredicate(target)) .findFirst() .orElse(target); SortedSet targetMethodsAndFields = methodsAndFieldsByTypeName.get(modifiedTarget); @@ -228,7 +230,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon if ("*".equals(elem.getQualid().getSimpleName())) { return elem.getPackageName().equals(c.getPackageName()); } - return elem.getTypeName().equals(c.getFullyQualifiedName()); + return fullyQualifiedNamesAreEqualAsPredicate(c.getFullyQualifiedName()).test(elem.getTypeName()); })) { anImport.used = false; changed = true; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index bd58e1be018..19eb6601cc4 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -46,6 +46,10 @@ public static boolean fullyQualifiedNamesAreEqual(@Nullable String fqn1, @Nullab return fqn1 == null && fqn2 == null; } + public static Predicate fullyQualifiedNamesAreEqualAsPredicate (@Nullable String fqn1) { + return (fqn2) -> fullyQualifiedNamesAreEqual(fqn1, fqn2); + } + /** * Returns true if the JavaTypes are of the same type. * {@link JavaType.Parameterized} will be checked for both the FQN and each of the parameters. From 098c07cef8389c4cc257063d1a83633d40ae4cd6 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa Date: Mon, 10 Jul 2023 16:27:32 +0200 Subject: [PATCH 044/447] Improved newVersion description (#3395) * Improved newVersion description * Update UpgradeDependencyVersion.java * Update UpgradeDependencyVersion.java --- .../org/openrewrite/gradle/UpgradeDependencyVersion.java | 7 +++++-- .../org/openrewrite/maven/UpgradeDependencyVersion.java | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index 82e8d0e7604..5cd839f1a46 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -68,13 +68,16 @@ public class UpgradeDependencyVersion extends Recipe { String artifactId; @Option(displayName = "New version", - description = "An exact version number or node-style semver selector used to select the version number.", + description = "An exact version number or node-style semver selector used to select the version number. " + + "You can also use `latest.release` for the latest available version and `latest.patch` if " + + "the current version is a valid semantic version. For more details, you can look at the documentation " + + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors)", example = "29.X") String newVersion; @Option(displayName = "Version pattern", description = "Allows version selection to be extended beyond the original Node Semver semantics. So for example," + - "Setting 'version' to \"25-29\" can be paired with a metadata pattern of \"-jre\" to select Guava 29.0-jre", + "Setting 'newVersion' to \"25-29\" can be paired with a metadata pattern of \"-jre\" to select Guava 29.0-jre", example = "-jre", required = false) @Nullable diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java index 58a57a69eaa..c8ee8cd4dec 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java @@ -68,13 +68,16 @@ public class UpgradeDependencyVersion extends ScanningRecipe> String artifactId; @Option(displayName = "New version", - description = "An exact version number or node-style semver selector used to select the version number.", + description = "An exact version number or node-style semver selector used to select the version number. " + + "You can also use `latest.release` for the latest available version and `latest.patch` if " + + "the current version is a valid semantic version. For more details, you can look at the documentation " + + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors)", example = "29.X") String newVersion; @Option(displayName = "Version pattern", description = "Allows version selection to be extended beyond the original Node Semver semantics. So for example," + - "Setting 'version' to \"25-29\" can be paired with a metadata pattern of \"-jre\" to select Guava 29.0-jre", + "Setting 'newVersion' to \"25-29\" can be paired with a metadata pattern of \"-jre\" to select Guava 29.0-jre", example = "-jre", required = false) @Nullable From 9cdaa3ae62aa895d8ae22dd930b8891392530e4f Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Tue, 11 Jul 2023 00:11:37 -0400 Subject: [PATCH 045/447] FindBuildMetadataTest --- .../search/FindBuildMetadataTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 rewrite-core/src/test/java/org/openrewrite/search/FindBuildMetadataTest.java diff --git a/rewrite-core/src/test/java/org/openrewrite/search/FindBuildMetadataTest.java b/rewrite-core/src/test/java/org/openrewrite/search/FindBuildMetadataTest.java new file mode 100644 index 00000000000..9bbcef48a29 --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/search/FindBuildMetadataTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 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. + */ +package org.openrewrite.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.marker.BuildMetadata; +import org.openrewrite.test.RewriteTest; + +import java.util.Map; + +import static org.openrewrite.Tree.randomId; +import static org.openrewrite.test.SourceSpecs.text; + +public class FindBuildMetadataTest implements RewriteTest { + + @Test + void findBuildMetadata() { + rewriteRun( + spec -> spec.recipe(new FindBuildMetadata("lstFormatVersion", "2")), + text( + "hello world", + "~~(Found build metadata)~~>hello world", + spec -> spec.mapBeforeRecipe(pt -> pt.withMarkers(pt.getMarkers().add(new BuildMetadata(randomId(), + Map.of("lstFormatVersion", "2"))))) + ) + ); + } +} From 8b4c86357c1484325869c34889bb90e286bfcc98 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Tue, 11 Jul 2023 21:15:18 +0200 Subject: [PATCH 046/447] `UsesMethod` should rely on `TypesInUse` (#3408) If `UsesMethod` does not find any reference to the desired method in `TypesInUse` then it should return rather than traversing the entire LST in an attempt to find a match. This change is being made for performance reasons, because this visitor is used very frequently as a precondition by recipes. --- .../src/main/java/org/openrewrite/java/search/UsesMethod.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/UsesMethod.java b/rewrite-java/src/main/java/org/openrewrite/java/search/UsesMethod.java index 1d852a2c027..60ad4d76a14 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/UsesMethod.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/UsesMethod.java @@ -54,6 +54,7 @@ public J visit(@Nullable Tree tree, P p) { return SearchResult.found(cu); } } + return (J) tree; } return super.visit(tree, p); } From 0f50ab42160250b1c9a3b454d8e9e1aee46c1a09 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Tue, 11 Jul 2023 22:55:12 +0200 Subject: [PATCH 047/447] Minor performance improvement for `ReloadableJava17Parser` --- .../openrewrite/java/isolated/ReloadableJava17Parser.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java index 9c69a756df0..1a6801cf809 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java @@ -30,7 +30,6 @@ import org.objectweb.asm.Opcodes; import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.tree.ParseError; import org.openrewrite.SourceFile; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.NonNullApi; @@ -41,6 +40,7 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.Space; import org.openrewrite.style.NamedStyles; +import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; @@ -329,8 +329,8 @@ public String inferBinaryName(Location location, JavaFileObject file) { public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException { if (StandardLocation.CLASS_PATH.equals(location)) { Iterable listed = super.list(location, packageName, kinds, recurse); - return Stream.concat( - classByteClasspath.stream() + return classByteClasspath.isEmpty() ? listed + : Stream.concat(classByteClasspath.stream() .filter(jfo -> jfo.getPackage().equals(packageName)), StreamSupport.stream(listed.spliterator(), false) ).collect(toList()); From 2d2f6a423b86dea875e4475cf2b9a443d45af9bb Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Wed, 12 Jul 2023 04:55:10 -0400 Subject: [PATCH 048/447] Fix valid value capitalization on AddGradleEnterprise --- .../org/openrewrite/gradle/plugins/AddGradleEnterprise.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterprise.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterprise.java index 8884640d8c9..3b09c55e1d7 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterprise.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterprise.java @@ -94,8 +94,8 @@ public class AddGradleEnterprise extends Recipe { "When set to `failure` the plugin will only publish build scans when the build fails. " + "When omitted scans will be published only when the `--scan` option is passed to the build.", required = false, - valid = {"always", "failure"}, - example = "always") + valid = {"Always", "Failure"}, + example = "Always") @Nullable PublishCriteria publishCriteria; From ea1098d1cdf38d758eb1a04827f58dac6d827f3d Mon Sep 17 00:00:00 2001 From: Jonathan Schneider Date: Wed, 12 Jul 2023 06:21:26 -0400 Subject: [PATCH 049/447] Fix AppendToTextFile strategy casing --- .../openrewrite/text/AppendToTextFile.java | 23 ++++++------ .../text/AppendToTextFileTest.java | 37 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/AppendToTextFile.java b/rewrite-core/src/main/java/org/openrewrite/text/AppendToTextFile.java index a4bacac2c97..78c9db99430 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/AppendToTextFile.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/AppendToTextFile.java @@ -53,15 +53,16 @@ public class AppendToTextFile extends ScanningRecipe { @Option(displayName = "Existing file strategy", description = "Determines behavior if a file exists at this location prior to Rewrite execution.\n\n" - + "- `continue`: append new content to existing file contents. If existing file is not plaintext, recipe does nothing.\n" - + "- `replace`: remove existing content from file.\n" - + "- `leave`: *(default)* do nothing. Existing file is fully preserved.\n\n" + + "- `Continue`: append new content to existing file contents. If existing file is not plaintext, recipe does nothing.\n" + + "- `Replace`: remove existing content from file.\n" + + "- `Leave`: *(default)* do nothing. Existing file is fully preserved.\n\n" + "Note: this only affects the first interaction with the specified file per Rewrite execution.\n" + "Subsequent instances of this recipe in the same Rewrite execution will always append.", - valid = {"continue", "replace", "leave"}, + valid = {"Continue", "Replace", "Leave"}, required = false) @Nullable Strategy existingFileStrategy; - public enum Strategy { CONTINUE, REPLACE, LEAVE } + + public enum Strategy {Continue, Replace, Leave} @Override public String getDisplayName() { @@ -104,9 +105,9 @@ public Collection

generate(AtomicBoolean fileExists, Collection<Sourc String preamble = this.preamble != null ? this.preamble + maybeNewline : ""; boolean exists = fileExists.get(); - if(!exists) { + if (!exists) { for (SourceFile generated : generatedInThisCycle) { - if(generated.getSourcePath().toString().equals(Paths.get(relativeFileName).toString())) { + if (generated.getSourcePath().toString().equals(Paths.get(relativeFileName).toString())) { exists = true; break; } @@ -133,13 +134,13 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { String preamble = AppendToTextFile.this.preamble != null ? AppendToTextFile.this.preamble + maybeNewline : ""; PlainText existingPlainText = (PlainText) sourceFile; - switch (existingFileStrategy != null ? existingFileStrategy : Strategy.LEAVE) { - case CONTINUE: - if(!maybeNewline.isEmpty() && !existingPlainText.getText().endsWith(maybeNewline)) { + switch (existingFileStrategy != null ? existingFileStrategy : Strategy.Leave) { + case Continue: + if (!maybeNewline.isEmpty() && !existingPlainText.getText().endsWith(maybeNewline)) { content = maybeNewline + content; } return existingPlainText.withText(existingPlainText.getText() + content); - case REPLACE: + case Replace: return existingPlainText.withText(preamble + content); } } diff --git a/rewrite-core/src/test/java/org/openrewrite/text/AppendToTextFileTest.java b/rewrite-core/src/test/java/org/openrewrite/text/AppendToTextFileTest.java index 66f809e6451..1057c24e918 100644 --- a/rewrite-core/src/test/java/org/openrewrite/text/AppendToTextFileTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/text/AppendToTextFileTest.java @@ -20,7 +20,6 @@ import org.openrewrite.Issue; import org.openrewrite.Recipe; import org.openrewrite.test.RewriteTest; -import org.openrewrite.test.SourceSpec; import java.util.function.Supplier; @@ -32,7 +31,7 @@ class AppendToTextFileTest implements RewriteTest { @Test void createsFileIfNeeded() { rewriteRun( - spec -> spec.recipe(new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.LEAVE)), + spec -> spec.recipe(new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.Leave)), text( null, """ @@ -45,7 +44,7 @@ void createsFileIfNeeded() { @Test void createsFileIfNeededWithMultipleInstances() { - Supplier<Recipe> r = () -> new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.LEAVE); + Supplier<Recipe> r = () -> new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.Leave); rewriteRun( spec -> spec.recipes(r.get(), r.get()), text( @@ -63,7 +62,7 @@ void createsFileIfNeededWithMultipleInstances() { @Test void replacesFileIfRequested() { rewriteRun( - spec -> spec.recipe(new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.REPLACE)), + spec -> spec.recipe(new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.Replace)), text( """ existing @@ -80,7 +79,7 @@ void replacesFileIfRequested() { @Test void continuesFileIfRequested() { rewriteRun( - spec -> spec.recipe(new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.CONTINUE)), + spec -> spec.recipe(new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.Continue)), text( """ existing @@ -97,7 +96,7 @@ void continuesFileIfRequested() { @Test void leavesFileIfRequested() { rewriteRun( - spec -> spec.recipe(new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.LEAVE)), + spec -> spec.recipe(new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.Leave)), text("existing", spec -> spec.path("file.txt")) ); } @@ -106,8 +105,8 @@ void leavesFileIfRequested() { void multipleInstancesCanAppend() { rewriteRun( spec -> spec.recipes( - new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.CONTINUE), - new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.CONTINUE) + new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.Continue), + new AppendToTextFile("file.txt", "content", "preamble", true, AppendToTextFile.Strategy.Continue) ), text( "existing", @@ -124,7 +123,7 @@ void multipleInstancesCanAppend() { @Test void noLeadingNewlineIfNoPreamble() { rewriteRun( - spec -> spec.recipe(new AppendToTextFile("file.txt", "content", null, true, AppendToTextFile.Strategy.REPLACE)), + spec -> spec.recipe(new AppendToTextFile("file.txt", "content", null, true, AppendToTextFile.Strategy.Replace)), text( """ existing @@ -141,8 +140,8 @@ void noLeadingNewlineIfNoPreamble() { void multipleFiles() { rewriteRun( spec -> spec.recipes( - new AppendToTextFile("file1.txt", "content1", "preamble1", true, AppendToTextFile.Strategy.REPLACE), - new AppendToTextFile("file2.txt", "content2", "preamble2", true, AppendToTextFile.Strategy.REPLACE) + new AppendToTextFile("file1.txt", "content1", "preamble1", true, AppendToTextFile.Strategy.Replace), + new AppendToTextFile("file2.txt", "content2", "preamble2", true, AppendToTextFile.Strategy.Replace) ), text( "existing1", @@ -169,8 +168,8 @@ void missingExpectedGeneratedFiles() { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> rewriteRun( spec -> spec.recipes( - new AppendToTextFile("file1.txt", "content1", "preamble1", true, AppendToTextFile.Strategy.REPLACE), - new AppendToTextFile("file2.txt", "content2", "preamble2", true, AppendToTextFile.Strategy.REPLACE) + new AppendToTextFile("file1.txt", "content1", "preamble1", true, AppendToTextFile.Strategy.Replace), + new AppendToTextFile("file2.txt", "content2", "preamble2", true, AppendToTextFile.Strategy.Replace) ), text( "existing2", @@ -188,8 +187,8 @@ void missingExpectedGeneratedFiles() { void changeAndCreatedFilesIfNeeded() { rewriteRun( spec -> spec.recipes( - new AppendToTextFile("file1.txt", "content1", "preamble1", true, AppendToTextFile.Strategy.REPLACE), - new AppendToTextFile("file2.txt", "content2", "preamble2", true, AppendToTextFile.Strategy.REPLACE) + new AppendToTextFile("file1.txt", "content1", "preamble1", true, AppendToTextFile.Strategy.Replace), + new AppendToTextFile("file2.txt", "content2", "preamble2", true, AppendToTextFile.Strategy.Replace) ), text( "existing2", @@ -213,10 +212,10 @@ void changeAndCreatedFilesIfNeeded() { void multipleInstancesOnMultipleFiles() { rewriteRun( spec -> spec.recipes( - new AppendToTextFile("file1.txt", "content1", "preamble1", true, AppendToTextFile.Strategy.REPLACE), - new AppendToTextFile("file2.txt", "content2", "preamble2", true, AppendToTextFile.Strategy.REPLACE), - new AppendToTextFile("file1.txt", "content1", "preamble1", true, AppendToTextFile.Strategy.REPLACE), - new AppendToTextFile("file2.txt", "content2", "preamble2", true, AppendToTextFile.Strategy.REPLACE) + new AppendToTextFile("file1.txt", "content1", "preamble1", true, AppendToTextFile.Strategy.Replace), + new AppendToTextFile("file2.txt", "content2", "preamble2", true, AppendToTextFile.Strategy.Replace), + new AppendToTextFile("file1.txt", "content1", "preamble1", true, AppendToTextFile.Strategy.Replace), + new AppendToTextFile("file2.txt", "content2", "preamble2", true, AppendToTextFile.Strategy.Replace) ), text( "existing1", From 159d1264dc4a641fbda042fa10070b0ebad3f80f Mon Sep 17 00:00:00 2001 From: Tracey Yoshima <tracey.yoshima@gmail.com> Date: Wed, 12 Jul 2023 13:45:46 -0400 Subject: [PATCH 050/447] Prevent NPE and surface the cause as a ParserErrorResult in YamlParser. (#3411) --- .../src/main/java/org/openrewrite/yaml/YamlParser.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index 14bb97ddf75..5de0129d96f 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -306,6 +306,9 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr AliasEvent alias = (AliasEvent) event; Yaml.Anchor anchor = anchors.get(alias.getAnchor()); + if (anchor == null) { + throw new UnsupportedOperationException("Unknown anchor: " + alias.getAnchor()); + } BlockBuilder builder = blockStack.peek(); builder.push(new Yaml.Alias(randomId(), fmt, Markers.EMPTY, anchor)); lastEnd = event.getEndMark().getIndex(); From e3741ed02a43e8b20f5139f0e759d5c79ccde79d Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 12 Jul 2023 12:09:38 -0700 Subject: [PATCH 051/447] Find text case-insensitive by default --- rewrite-core/src/main/java/org/openrewrite/text/Find.java | 8 ++++---- .../main/java/org/openrewrite/text/FindAndReplace.java | 8 ++++---- .../src/test/java/org/openrewrite/text/FindTest.java | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/Find.java b/rewrite-core/src/main/java/org/openrewrite/text/Find.java index 1fb1e770453..00117051266 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/Find.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/Find.java @@ -57,11 +57,11 @@ public String getDescription() { @Nullable Boolean regex; - @Option(displayName = "Case Insensitive", - description = "If `true` the search will be insensitive to case. Default `false`.", + @Option(displayName = "Case sensitive", + description = "If `true` the search will be sensitive to case. Default `false`.", required = false) @Nullable - Boolean caseInsensitive; + Boolean caseSensitive; @Option(displayName = "Regex Multiline Mode", description = "When performing a regex search setting this to `true` allows \"^\" and \"$\" to match the beginning and end of lines, respectively. " + @@ -101,7 +101,7 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { searchStr = Pattern.quote(searchStr); } int patternOptions = 0; - if(Boolean.TRUE.equals(caseInsensitive)) { + if(!Boolean.TRUE.equals(caseSensitive)) { patternOptions |= Pattern.CASE_INSENSITIVE; } if(Boolean.TRUE.equals(multiline)) { diff --git a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java index 443a51d7cac..13588399866 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java @@ -52,11 +52,11 @@ public class FindAndReplace extends Recipe { @Nullable Boolean regex; - @Option(displayName = "Case Insensitive", - description = "If `true` the search will be insensitive to case. Default `false`.", + @Option(displayName = "Case sensitive", + description = "If `true` the search will be sensitive to case. Default `false`.", required = false) @Nullable - Boolean caseInsensitive; + Boolean caseSensitive; @Option(displayName = "Regex Multiline Mode", description = "When performing a regex search setting this to `true` allows \"^\" and \"$\" to match the beginning and end of lines, respectively. " + @@ -120,7 +120,7 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { searchStr = Pattern.quote(searchStr); } int patternOptions = 0; - if(Boolean.TRUE.equals(caseInsensitive)) { + if(!Boolean.TRUE.equals(caseSensitive)) { patternOptions |= Pattern.CASE_INSENSITIVE; } if(Boolean.TRUE.equals(multiline)) { diff --git a/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java b/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java index ab3c399b41b..5d85c017bff 100644 --- a/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java @@ -28,7 +28,7 @@ class FindTest implements RewriteTest { @Test void regex() { rewriteRun( - spec -> spec.recipe(new Find("[T\\s]", true, null, null, null, null)), + spec -> spec.recipe(new Find("[T\\s]", true, true, null, null, null)), text( """ This is\ttext. @@ -58,7 +58,7 @@ void plainText() { @Test void caseInsensitive() { rewriteRun( - spec -> spec.recipe(new Find("text", null, true, null, null, "**/foo/**")), + spec -> spec.recipe(new Find("text", null, null, null, null, "**/foo/**")), dir("foo", text( """ From 64a8c36c3d9f4aed7e4fa6442e7fd22a3cd66690 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 12 Jul 2023 12:40:50 -0700 Subject: [PATCH 052/447] Limit Java AutoFormat to apply itself only to Java source files as it will mis-format other languages --- .../java/org/openrewrite/java/format/AutoFormatVisitor.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/AutoFormatVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/AutoFormatVisitor.java index eddc86246e6..fd1c8411736 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/AutoFormatVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/AutoFormatVisitor.java @@ -42,6 +42,11 @@ public AutoFormatVisitor(@Nullable Tree stopAfter) { this.stopAfter = stopAfter; } + @Override + public boolean isAcceptable(SourceFile sourceFile, P p) { + return sourceFile instanceof J.CompilationUnit; + } + @Override public J visit(@Nullable Tree tree, P p, Cursor cursor) { JavaSourceFile cu = (tree instanceof JavaSourceFile) ? From 54dc8020d791259ef1d961a10db0dc986dd97824 Mon Sep 17 00:00:00 2001 From: Alexis Tual <atual@gradle.com> Date: Thu, 13 Jul 2023 13:13:45 +0200 Subject: [PATCH 053/447] Polish Gradle Enterprise recipes names and descriptions (#3413) * Fix metadata in Add GE Maven extension recipe * Rename AddGradleEnterprise recipe To have a consistent naming with AddGradleEnterpriseMavenExtension recipe. --- ...e.java => AddGradleEnterpriseGradlePlugin.java} | 6 +++--- ...va => AddGradleEnterpriseGradlePluginTest.java} | 8 ++++---- .../maven/AddGradleEnterpriseMavenExtension.java | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) rename rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/{AddGradleEnterprise.java => AddGradleEnterpriseGradlePlugin.java} (98%) rename rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/{AddGradleEnterpriseTest.java => AddGradleEnterpriseGradlePluginTest.java} (93%) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterprise.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java similarity index 98% rename from rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterprise.java rename to rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java index 3b09c55e1d7..7959c58e72a 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterprise.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java @@ -49,7 +49,7 @@ @Value @EqualsAndHashCode(callSuper = true) @Incubating(since = "7.33.0") -public class AddGradleEnterprise extends Recipe { +public class AddGradleEnterpriseGradlePlugin extends Recipe { @EqualsAndHashCode.Exclude MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this); @@ -106,12 +106,12 @@ public enum PublishCriteria { @Override public String getDisplayName() { - return "Add the Gradle Enterprise plugin"; + return "Add the Gradle Enterprise Gradle plugin"; } @Override public String getDescription() { - return "Add the Gradle Enterprise plugin to settings.gradle files."; + return "Add the Gradle Enterprise Gradle plugin to settings.gradle files."; } @Override diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java similarity index 93% rename from rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseTest.java rename to rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java index beab2e6dca8..80996a3cf4e 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java @@ -33,12 +33,12 @@ import static org.openrewrite.gradle.Assertions.*; import static org.openrewrite.test.SourceSpecs.dir; -class AddGradleEnterpriseTest implements RewriteTest { +class AddGradleEnterpriseGradlePluginTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec.beforeRecipe(withToolingApi()) - .recipe(new AddGradleEnterprise("3.x", null, null, null, null, null)); + .recipe(new AddGradleEnterpriseGradlePlugin("3.x", null, null, null, null, null)); } private static Consumer<SourceSpec<CompilationUnit>> interpolateResolvedVersion(@Language("groovy") String after) { @@ -174,7 +174,7 @@ void addExistingSettingsPluginsBlock() { void withConfigurationInSettings() { rewriteRun( spec -> spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))) - .recipe(new AddGradleEnterprise("3.x", "https://ge.sam.com/", true, true, true, AddGradleEnterprise.PublishCriteria.Always)), + .recipe(new AddGradleEnterpriseGradlePlugin("3.x", "https://ge.sam.com/", true, true, true, AddGradleEnterpriseGradlePlugin.PublishCriteria.Always)), buildGradle( "" ), @@ -206,7 +206,7 @@ void withConfigurationInSettings() { void withConfigurationOldInputCapture() { rewriteRun( spec -> spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))) - .recipe(new AddGradleEnterprise("3.6", null, null, true, null, null)), + .recipe(new AddGradleEnterpriseGradlePlugin("3.6", null, null, true, null, null)), buildGradle( "" ), diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java index d9d342e5a5f..00832e2d6c2 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java @@ -67,9 +67,9 @@ public class AddGradleEnterpriseMavenExtension extends ScanningRecipe<AddGradleE " <artifactId>gradle-enterprise-maven-extension</artifactId>\n" + "</extension>"; - @Option(displayName = "Plugin version", - description = "An exact version number or node-style semver selector used to select the gradle-enterprise-maven-extension version.", - example = "1.x") + @Option(displayName = "Extension version", + description = "A maven-compatible version number to select the gradle-enterprise-maven-extension version.", + example = "1.17.4") @Nullable String version; @@ -79,8 +79,8 @@ public class AddGradleEnterpriseMavenExtension extends ScanningRecipe<AddGradleE String server; @Option(displayName = "Allow untrusted server", - description = "When set to `true` the plugin will be configured to allow unencrypted http connections with the server. " + - "If set to `false` or omitted, the plugin will refuse to communicate without transport layer security enabled.", + description = "When set to `true` the extension will be configured to allow unencrypted http connections with the server. " + + "If set to `false` or omitted, the extension will refuse to communicate without transport layer security enabled.", required = false, example = "true") @Nullable @@ -127,12 +127,12 @@ public enum PublishCriteria { @Override public String getDisplayName() { - return "Add Gradle Enterprise Maven Extension to maven projects"; + return "Add Gradle Enterprise Maven extension to maven projects"; } @Override public String getDescription() { - return "To integrate gradle enterprise maven extension into maven projects, ensure that the " + + return "To integrate Gradle Enterprise Maven extension into maven projects, ensure that the " + "`gradle-enterprise-maven-extension` is added to the `.mvn/extensions.xml` file if not already present. " + "Additionally, configure the extension by adding the `.mvn/gradle-enterprise.xml` configuration file."; } From f03d1926a3814c412936ad6f5684e1d4fd24edd4 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Thu, 13 Jul 2023 14:53:46 +0100 Subject: [PATCH 054/447] UpdateMovedRecipe for AddGradleEnterpriseGradlePlugin Following #3413 --- .../src/main/resources/META-INF/rewrite/migrate-rewrite.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rewrite-java/src/main/resources/META-INF/rewrite/migrate-rewrite.yml b/rewrite-java/src/main/resources/META-INF/rewrite/migrate-rewrite.yml index 25075d074a3..a22ee76b7fb 100644 --- a/rewrite-java/src/main/resources/META-INF/rewrite/migrate-rewrite.yml +++ b/rewrite-java/src/main/resources/META-INF/rewrite/migrate-rewrite.yml @@ -374,4 +374,7 @@ recipeList: newRecipeFullyQualifiedClassName: org.openrewrite.staticanalysis.WhileInsteadOfFor - org.openrewrite.java.recipes.UpdateMovedRecipe: oldRecipeFullyQualifiedClassName: org.openrewrite.java.cleanup.WriteOctalValuesAsDecimal - newRecipeFullyQualifiedClassName: org.openrewrite.staticanalysis.WriteOctalValuesAsDecimal \ No newline at end of file + newRecipeFullyQualifiedClassName: org.openrewrite.staticanalysis.WriteOctalValuesAsDecimal + - org.openrewrite.java.recipes.UpdateMovedRecipe: + oldRecipeFullyQualifiedClassName: org.openrewrite.gradle.plugins.AddGradleEnterprise + newRecipeFullyQualifiedClassName: org.openrewrite.gradle.plugins.AddGradleEnterpriseGradlePlugin From 80a630494c3294eff9eac1d24d39955df258ac3d Mon Sep 17 00:00:00 2001 From: Mike Solomon <mike@moderne.io> Date: Thu, 13 Jul 2023 07:10:17 -0700 Subject: [PATCH 055/447] Improve OrderImports description (#3412) * Improve OrderImports description When I originally looked at this recipe, I was a bit confused on what it does as the description was very generic. I think that providing more information would help people who view this recipe in our docs or in the saas who won't necessarily take the time to dig into the code. * Update rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java Co-authored-by: Knut Wannheden <knut@moderne.io> --------- Co-authored-by: Mike Sol <mike-solomon@users.noreply.github.com> Co-authored-by: Knut Wannheden <knut@moderne.io> --- .../src/main/java/org/openrewrite/java/OrderImports.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java b/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java index d17f5e3da88..6a607f94a0a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java @@ -60,7 +60,9 @@ public String getDisplayName() { @Override public String getDescription() { - return "Group and order imports."; + return "Groups and orders import statements. If a [style has been defined](https://docs.openrewrite.org/concepts-explanations/styles), this recipe will order the imports " + + "according to that style. If no style is detected, this recipe will default to ordering imports in " + + "the same way that IntelliJ does."; } @Override From 78b64cc0d6f1020b07e585ca492e80035e2b1cc8 Mon Sep 17 00:00:00 2001 From: Alex Kirichenko <121981713+alexkir28@users.noreply.github.com> Date: Thu, 13 Jul 2023 14:54:31 -0700 Subject: [PATCH 056/447] String fields are null by default. (#3404) --- .../java/org/openrewrite/internal/RecipeIntrospectionUtils.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java index 94c22fb3cba..531b86295ea 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java @@ -112,8 +112,6 @@ private static <V> V construct(Class<?> clazz) { java.lang.reflect.Parameter param = primaryConstructor.getParameters()[i]; if (param.getType().isPrimitive()) { constructorArgs[i] = getPrimitiveDefault(param.getType()); - } else if (param.getType().equals(String.class)) { - constructorArgs[i] = ""; } else if (Enum.class.isAssignableFrom(param.getType())) { try { Object[] values = (Object[]) param.getType().getMethod("values").invoke(null); From 71ebb89bfef8f53bfda0571912c7e8f3700a004b Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Fri, 14 Jul 2023 10:01:33 -0700 Subject: [PATCH 057/447] Prevent UnnecessaryParentheses from running on non-java languages --- .../openrewrite/java/cleanup/UnnecessaryParentheses.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParentheses.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParentheses.java index 72dc97feaaa..1ec364d2d16 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParentheses.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParentheses.java @@ -52,6 +52,13 @@ public Duration getEstimatedEffortPerOccurrence() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { return new JavaVisitor<ExecutionContext>() { + + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionContext) { + // Causes problems on other languages like JavaScript + return sourceFile instanceof J.CompilationUnit; + } + private static final String UNNECESSARY_PARENTHESES_MESSAGE = "unnecessaryParenthesesUnwrapTarget"; transient UnnecessaryParenthesesStyle style; From 8daf1c374362e4484fb2f547b286864283eb32a3 Mon Sep 17 00:00:00 2001 From: Alex Kirichenko <121981713+alexkir28@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:32:29 -0700 Subject: [PATCH 058/447] Fixed nullpointer exception. (#3415) --- .../org/openrewrite/java/ReplaceStringLiteralWithConstant.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralWithConstant.java b/rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralWithConstant.java index 4511c9b574a..cd844f41ada 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralWithConstant.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralWithConstant.java @@ -70,7 +70,7 @@ public String getDescription() { } public String getLiteralValue() { - if (this.literalValue == null) { + if (this.literalValue == null && this.fullyQualifiedConstantName != null) { try { this.literalValue = (String) getConstantValueByFullyQualifiedName(this.fullyQualifiedConstantName); } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) { From 33f9711c001efd4fe523b66e719c8e91185fb37f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20McCarpenter=E2=84=A2=EF=B8=8F?= <kevin@moderne.io> Date: Fri, 14 Jul 2023 18:32:23 -0300 Subject: [PATCH 059/447] chore(metrics): Add timer to configuring a recipe (#3417) --- .../openrewrite/config/ClasspathScanningLoader.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java index c6d34bda70d..effb79bdf5a 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java @@ -18,9 +18,12 @@ import io.github.classgraph.ClassGraph; import io.github.classgraph.ClassInfo; import io.github.classgraph.ScanResult; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Timer; import org.openrewrite.Contributor; import org.openrewrite.Recipe; import org.openrewrite.ScanningRecipe; +import org.openrewrite.internal.MetricsHelper; import org.openrewrite.internal.RecipeIntrospectionUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.style.NamedStyles; @@ -156,12 +159,19 @@ private void configureRecipes(ScanResult result, String className) { || (recipeClass.getModifiers() & Modifier.ABSTRACT) != 0) { continue; } + Timer.Builder builder = Timer.builder("rewrite.scan.configure.recipe") + .tags("recipe", recipeClass.getName()); + Timer.Sample sample = Timer.start(); try { Recipe recipe = constructRecipe(recipeClass); recipeDescriptors.add(recipe.getDescriptor()); recipes.add(recipe); + MetricsHelper.successTags(builder); } catch (Throwable e) { + MetricsHelper.errorTags(builder, e); logger.warn("Unable to configure {}", recipeClass.getName(), e); + } finally { + sample.stop(builder.register(Metrics.globalRegistry)); } } } From 41eb9c5b4a340c44a14a6c931a2cfe4fe467a03e Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh <jonathan.leitschuh@gmail.com> Date: Fri, 14 Jul 2023 18:44:29 -0400 Subject: [PATCH 060/447] Fix SearchResults.mergingFound duplicating description (#3418) Signed-off-by: Jonathan Leitschuh <Jonathan.Leitschuh@gmail.com> --- .../org/openrewrite/marker/SearchResult.java | 17 ++- .../openrewrite/marker/SearchResultsTest.java | 129 ++++++++++++++++++ 2 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 rewrite-java-test/src/test/java/org/openrewrite/marker/SearchResultsTest.java diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/SearchResult.java b/rewrite-core/src/main/java/org/openrewrite/marker/SearchResult.java index 6435d1449a2..96a105db392 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/SearchResult.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/SearchResult.java @@ -55,17 +55,22 @@ public static <T extends Tree> T found(@Nullable T t, @Nullable String descripti /** * Merge the description of two search results into a single search result with a unified description. + * <p> + * If the there already exists a search result with the same description, the existing search result is returned. */ - @Incubating(since ="8.0.0") - public static <T extends Tree> T mergingFound(@Nullable T t,String description) { + @Incubating(since = "8.0.0") + public static <T extends Tree> T mergingFound(@Nullable T t, String description) { return mergingFound(t, description, ", "); } /** * Merge the description of two search results into a single search result with a unified description. + * <p> + * If the there already exists a search result with the same description, the existing search result is returned. + * * @param delimiter The delimiter to use when merging descriptions. */ - @Incubating(since ="8.0.0") + @Incubating(since = "8.0.0") public static <T extends Tree> T mergingFound(@Nullable T t, String description, String delimiter) { Objects.requireNonNull(delimiter, "delimiter must not be null"); if (t == null) { @@ -86,6 +91,12 @@ public static <T extends Tree> T mergingFound(@Nullable T t, String description, if (s2.getDescription() == null) { return s1; } + if (s1.getDescription().equals(s2.getDescription()) || + s1.getDescription().startsWith(s2.getDescription() + delimiter) || + s1.getDescription().contains(delimiter + s2.getDescription() + delimiter) || + s1.getDescription().endsWith(s2.getDescription())) { + return s1; + } return s1.withDescription(s1.getDescription() + delimiter + s2.getDescription()); })); } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/marker/SearchResultsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/marker/SearchResultsTest.java new file mode 100644 index 00000000000..af481b698dc --- /dev/null +++ b/rewrite-java-test/src/test/java/org/openrewrite/marker/SearchResultsTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.marker; + +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.RewriteTest.toRecipe; + +public class SearchResultsTest implements RewriteTest { + + @Test + void searchResultIsOnlyAddedOnceEvenWhenRunMultipleTimesByScheduler() { + rewriteRun(spec -> { + spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { + return SearchResult.mergingFound(super.visitMethodInvocation(method, executionContext), method.getSimpleName()); + } + })); + }, + java( + """ + class Test { + void test() { + System.out.println("Hello, world!"); + } + } + """, + """ + class Test { + void test() { + /*~~(println)~~>*/System.out.println("Hello, world!"); + } + } + """ + ) + ); + } + + @Test + void multipleSearchResultIsOnlyAddedOnceEvenWhenRunMultipleTimesByScheduler() { + rewriteRun(spec -> { + spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { + return SearchResult.mergingFound( + SearchResult.mergingFound( + SearchResult.mergingFound( + super.visitMethodInvocation(method, executionContext), + "42" + ), + "Hello, world!" + ), + method.getSimpleName() + ); + } + })); + }, + java( + """ + class Test { + void test() { + System.out.println("Hello, world!"); + } + } + """, + """ + class Test { + void test() { + /*~~(42, Hello, world!, println)~~>*/System.out.println("Hello, world!"); + } + } + """ + ) + ); + } + + @Test + void foundSearchResultsShouldNotClobberResultsWithDescription() { + rewriteRun(spec -> { + spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { + return SearchResult.found( + SearchResult.found( + super.visitMethodInvocation(method, executionContext), + "Hello, world!" + ) + ); + } + })); + }, + java( + """ + class Test { + void test() { + System.out.println("Hello, world!"); + } + } + """, + """ + class Test { + void test() { + /*~~(Hello, world!)~~>*/System.out.println("Hello, world!"); + } + } + """ + ) + ); + } +} From 1d72be16f6c5e9bd6d9fcb0fab3a6d4981a2a2ce Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Fri, 14 Jul 2023 19:50:51 -0400 Subject: [PATCH 061/447] ClasspathScanningLoader prevent high cardinality tag --- .../org/openrewrite/config/ClasspathScanningLoader.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java index effb79bdf5a..887f01e72a0 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java @@ -159,16 +159,15 @@ private void configureRecipes(ScanResult result, String className) { || (recipeClass.getModifiers() & Modifier.ABSTRACT) != 0) { continue; } - Timer.Builder builder = Timer.builder("rewrite.scan.configure.recipe") - .tags("recipe", recipeClass.getName()); + Timer.Builder builder = Timer.builder("rewrite.scan.configure.recipe"); Timer.Sample sample = Timer.start(); try { Recipe recipe = constructRecipe(recipeClass); recipeDescriptors.add(recipe.getDescriptor()); recipes.add(recipe); - MetricsHelper.successTags(builder); + MetricsHelper.successTags(builder.tags("recipe", "elided")); } catch (Throwable e) { - MetricsHelper.errorTags(builder, e); + MetricsHelper.errorTags(builder.tags("recipe", recipeClass.getName()), e); logger.warn("Unable to configure {}", recipeClass.getName(), e); } finally { sample.stop(builder.register(Metrics.globalRegistry)); From ef8e7e37ca75882bc4dab235008c9007cc1ad53c Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Fri, 14 Jul 2023 17:36:54 -0700 Subject: [PATCH 062/447] Fix #3414 RecipeTest.recipe(InputStream yaml) variant did not search the classpath for imperative recipes --- .../org/openrewrite/config/Environment.java | 13 +++++-- .../openrewrite/config/ChangeTextToSam.java | 39 +++++++++++++++++++ .../java/org/openrewrite/test/RecipeSpec.java | 1 + 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 rewrite-core/src/test/java/org/openrewrite/config/ChangeTextToSam.java diff --git a/rewrite-core/src/main/java/org/openrewrite/config/Environment.java b/rewrite-core/src/main/java/org/openrewrite/config/Environment.java index 9de09e7c9a6..3765fd51139 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/Environment.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/Environment.java @@ -43,8 +43,8 @@ public List<Recipe> listRecipes() { Map<String, List<RecipeExample>> recipeExamples = new HashMap<>(); for (ResourceLoader r : resourceLoaders) { if (r instanceof YamlResourceLoader) { - recipeExamples.putAll(((YamlResourceLoader) r).listRecipeExamples()); - recipeToContributors.putAll(((YamlResourceLoader) r).listContributors()); + recipeExamples.putAll(r.listRecipeExamples()); + recipeToContributors.putAll(r.listContributors()); } } @@ -85,8 +85,8 @@ public Collection<RecipeDescriptor> listRecipeDescriptors() { Map<String, List<RecipeExample>> recipeToExamples = new HashMap<>(); for (ResourceLoader r : resourceLoaders) { if (r instanceof YamlResourceLoader) { - recipeToContributors.putAll(((YamlResourceLoader) r).listContributors()); - recipeToExamples.putAll(((YamlResourceLoader) r).listRecipeExamples()); + recipeToContributors.putAll(r.listContributors()); + recipeToExamples.putAll(r.listRecipeExamples()); } else if (r instanceof ClasspathScanningLoader) { ClasspathScanningLoader classpathScanningLoader = (ClasspathScanningLoader) r; @@ -160,6 +160,7 @@ public Recipe activateRecipes(String... activeRecipes) { return activateRecipes(Arrays.asList(activeRecipes)); } + //TODO: Nothing uses this and in most cases it would be a bad idea anyway, should consider removing public Recipe activateAll() { return new CompositeRecipe(listRecipes()); } @@ -186,6 +187,7 @@ public List<NamedStyles> activateStyles(Iterable<String> activeStyles) { return activated; } + @SuppressWarnings("unused") public List<NamedStyles> activateStyles(String... activeStyles) { return activateStyles(Arrays.asList(activeStyles)); } @@ -222,6 +224,7 @@ public Builder scanRuntimeClasspath(String... acceptPackages) { return load(new ClasspathScanningLoader(properties, acceptPackages)); } + @SuppressWarnings("unused") public Builder scanClassLoader(ClassLoader classLoader) { return load(new ClasspathScanningLoader(properties, classLoader)); } @@ -231,6 +234,7 @@ public Builder scanClassLoader(ClassLoader classLoader) { * @param classLoader A classloader that is populated with the transitive dependencies of the jar. * @return This builder. */ + @SuppressWarnings("unused") public Builder scanJar(Path jar, Collection<Path> dependencies, ClassLoader classLoader) { List<ClasspathScanningLoader> list = new ArrayList<>(); for (Path dep : dependencies) { @@ -240,6 +244,7 @@ public Builder scanJar(Path jar, Collection<Path> dependencies, ClassLoader clas return load(new ClasspathScanningLoader(jar, properties, list, classLoader), list); } + @SuppressWarnings("unused") public Builder scanUserHome() { File userHomeRewriteConfig = new File(System.getProperty("user.home") + "/.rewrite/rewrite.yml"); if (userHomeRewriteConfig.exists()) { diff --git a/rewrite-core/src/test/java/org/openrewrite/config/ChangeTextToSam.java b/rewrite-core/src/test/java/org/openrewrite/config/ChangeTextToSam.java new file mode 100644 index 00000000000..563582e2067 --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/config/ChangeTextToSam.java @@ -0,0 +1,39 @@ +package org.openrewrite.config; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.text.PlainText; +import org.openrewrite.text.PlainTextVisitor; + +@SuppressWarnings("unused") // Used in a yaml recipe +public class ChangeTextToSam extends Recipe { + + @Override + public String getDisplayName() { + return "Change text to Sam"; + } + + @Override + public String getDescription() { + return "Does the text file say Sam? It will after you run this recipe."; + } + + @Option(displayName = "bool", + description = "Exists to test that optional configuration may be omitted", + required = false) + @Nullable + Boolean bool; + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new PlainTextVisitor<>() { + @Override + public PlainText visitText(PlainText text, ExecutionContext executionContext) { + return text.withText("Sam"); + } + }; + } +} diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java index 6a7635f1310..1fc836945c9 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java @@ -116,6 +116,7 @@ public RecipeSpec recipes(Recipe... recipes) { public RecipeSpec recipe(InputStream yaml, String... activeRecipes) { return recipe(Environment.builder() + .scanRuntimeClasspath() // Slow but required to find recipe classes the yaml recipe may depend on .load(new YamlResourceLoader(yaml, URI.create("rewrite.yml"), new Properties())) .build() .activateRecipes(activeRecipes)); From 0ad31430395faecc4f6607e4d3fc0a8d030472af Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Sat, 15 Jul 2023 19:40:56 +0200 Subject: [PATCH 063/447] Account for Unicode characters in Yaml FormatPreservingReader (#3419) * Replicate issue with Yaml Unicode suffix parsing https://github.com/openrewrite/rewrite/issues/2062 * Account for double byte unicode characters * Add license to ChangeTextToSam.java --- .../openrewrite/config/ChangeTextToSam.java | 15 +++++ .../yaml/FormatPreservingReader.java | 7 ++- .../org/openrewrite/yaml/YamlParserTest.java | 55 +++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java diff --git a/rewrite-core/src/test/java/org/openrewrite/config/ChangeTextToSam.java b/rewrite-core/src/test/java/org/openrewrite/config/ChangeTextToSam.java index 563582e2067..1b40fdfc0cf 100644 --- a/rewrite-core/src/test/java/org/openrewrite/config/ChangeTextToSam.java +++ b/rewrite-core/src/test/java/org/openrewrite/config/ChangeTextToSam.java @@ -1,3 +1,18 @@ +/* + * Copyright 2022 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite.config; import org.openrewrite.ExecutionContext; diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java index eab2075ada6..e974792a8c2 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java @@ -37,7 +37,6 @@ class FormatPreservingReader extends Reader { this.delegate = delegate; } - // VisibleForTesting String prefix(int lastEnd, int startIndex) { assert lastEnd <= startIndex; @@ -78,7 +77,11 @@ public int read(@NonNull char[] cbuf, int off, int len) throws IOException { if (read > 0) { buffer.ensureCapacity(buffer.size() + read); for (int i = 0; i < read; i++) { - buffer.add(cbuf[i]); + char e = cbuf[i]; + if (Character.UnicodeBlock.of(e) != Character.UnicodeBlock.BASIC_LATIN && i % 2 == 0) { + bufferIndex--; + } + buffer.add(e); } } return read; diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java new file mode 100644 index 00000000000..bcdf07ae339 --- /dev/null +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.yaml; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.SourceFile; +import org.openrewrite.tree.ParseError; +import org.openrewrite.yaml.tree.Yaml; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class YamlParserTest { + + @ParameterizedTest + @ValueSource(strings = { + "b", + "🛠", + "🛠🛠", + "🛠 🛠" + }) + void parseYamlWithUnicode(String input) { + Stream<SourceFile> yamlSources = YamlParser.builder().build().parse("a: %s\n".formatted(input)); + SourceFile sourceFile = yamlSources.findFirst().get(); + assertThat(sourceFile).isNotInstanceOf(ParseError.class); + + Yaml.Documents documents = (Yaml.Documents) sourceFile; + Yaml.Document document = documents.getDocuments().get(0); + + // Assert that end is parsed correctly + assertThat(document.getEnd().getPrefix()).isEqualTo("\n"); + + // Assert that the title is parsed correctly + Yaml.Mapping mapping = (Yaml.Mapping) document.getBlock(); + Yaml.Mapping.Entry entry = mapping.getEntries().get(0); + Yaml.Scalar title = (Yaml.Scalar) entry.getValue(); + assertThat(title.getValue()).isEqualTo(input); + } + +} From 70121bdf0146d4c25b35f675f00138a461db99d6 Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Mon, 17 Jul 2023 11:47:04 -0500 Subject: [PATCH 064/447] Add missing latest release test for UpdateMavenWrapper. Account for empty strings in both UpdateGradleWrapper and UpdateMavenWrapper for versions --- .../gradle/util/GradleWrapper.java | 4 +- .../maven/utilities/MavenWrapper.java | 4 +- .../maven/UpdateMavenWrapperTest.java | 70 +++++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java index 2f8944ae98b..e3a27985d11 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/util/GradleWrapper.java @@ -59,12 +59,12 @@ public static GradleWrapper create(@Nullable String distributionTypeName, @Nulla .filter(dt -> dt.name().equalsIgnoreCase(distributionTypeName)) .findAny() .orElse(DistributionType.Bin); - VersionComparator versionComparator = (version == null) ? + VersionComparator versionComparator = StringUtils.isBlank(version) ? new LatestRelease(null) : requireNonNull(Semver.validate(version, null).getValue()); HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getLargeFileHttpSender(); - String gradleVersionsUrl = (StringUtils.isBlank(repositoryUrl)) ? "https://services.gradle.org/versions/all" : repositoryUrl; + String gradleVersionsUrl = StringUtils.isBlank(repositoryUrl) ? "https://services.gradle.org/versions/all" : repositoryUrl; try (HttpSender.Response resp = httpSender.send(httpSender.get(gradleVersionsUrl).build())) { if (resp.isSuccessful()) { List<GradleVersion> allVersions = new ObjectMapper() diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java index 402088aeb5f..1e0e8c6ee5f 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenWrapper.java @@ -92,10 +92,10 @@ public static MavenWrapper create( MavenPomDownloader pomDownloader = new MavenPomDownloader(Collections.emptyMap(), ctx, null, null); - VersionComparator wrapperVersionComparator = (wrapperVersion == null) ? + VersionComparator wrapperVersionComparator = StringUtils.isBlank(wrapperVersion) ? new LatestRelease(null) : requireNonNull(Semver.validate(wrapperVersion, null).getValue()); - VersionComparator distributionVersionComparator = (distributionVersion == null) ? + VersionComparator distributionVersionComparator = StringUtils.isBlank(distributionVersion) ? new LatestRelease(null) : requireNonNull(Semver.validate(distributionVersion, null).getValue()); diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java index 782bfd9570d..2e834ac0e68 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java @@ -38,6 +38,8 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.function.UnaryOperator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -386,6 +388,74 @@ void allowUpdatingDistributionTypeWhenSameVersion() { ); } + @Test + void defaultsToLatestRelease() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenWrapper(null, null, null, null, null)) + .allSources(source -> source.markers(new BuildTool(Tree.randomId(), BuildTool.Type.Maven, "3.8.0"))) + .afterRecipe(run -> { + var mvnw = result(run, PlainText.class, "mvnw"); + assertThat(mvnw.getSourcePath()).isEqualTo(WRAPPER_SCRIPT_LOCATION); + assertThat(mvnw.getText()).isNotBlank(); + assertThat(mvnw.getFileAttributes()).isNotNull(); + assertThat(mvnw.getFileAttributes().isReadable()).isTrue(); + assertThat(mvnw.getFileAttributes().isWritable()).isTrue(); + + var mvnwCmd = result(run, PlainText.class, "mvnw.cmd"); + assertThat(mvnwCmd.getSourcePath()).isEqualTo(WRAPPER_BATCH_LOCATION); + assertThat(mvnwCmd.getText()).isNotBlank(); + + var mavenWrapperJar = result(run, Remote.class, "maven-wrapper.jar"); + assertThat(mavenWrapperJar.getSourcePath()).isEqualTo(WRAPPER_JAR_LOCATION); + //noinspection OptionalGetWithoutIsPresent + BuildTool buildTool = mavenWrapperJar.getMarkers().findFirst(BuildTool.class).get(); + assertThat(buildTool.getVersion()).isNotEqualTo("3.8.0"); + assertThat(mavenWrapperJar.getUri()).isEqualTo(URI.create("https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/" + buildTool.getVersion() + "/maven-wrapper-" + buildTool.getVersion() + ".jar")); + assertThat(isValidWrapperJar(mavenWrapperJar)).as("Wrapper jar is not valid").isTrue(); + }), + properties( + withLicenseHeader(""" + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar + distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a + wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d + """), + spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") + .after(after -> { + Matcher distributionVersionMatcher = Pattern.compile("apache-maven-(.*?)-bin.zip").matcher(after); + assertThat(distributionVersionMatcher.find()).isTrue(); + String mavenDistributionVersion = distributionVersionMatcher.group(1); + assertThat(mavenDistributionVersion).isNotEqualTo("3.8.0"); + + Matcher distributionChecksumMatcher = Pattern.compile("distributionSha256Sum=(.*)").matcher(after); + assertThat(distributionChecksumMatcher.find()).isTrue(); + String distributionChecksum = distributionChecksumMatcher.group(1); + assertThat(distributionChecksum).isNotBlank(); + + Matcher wrapperVersionMatcher = Pattern.compile("maven-wrapper-(.*?).jar").matcher(after); + assertThat(wrapperVersionMatcher.find()).isTrue(); + String wrapperVersion = wrapperVersionMatcher.group(1); + assertThat(wrapperVersion).isNotEqualTo("3.1.1"); + + Matcher wrapperChecksumMatcher = Pattern.compile("wrapperSha256Sum=(.*)").matcher(after); + assertThat(wrapperChecksumMatcher.find()).isTrue(); + String wrapperChecksum = wrapperChecksumMatcher.group(1); + assertThat(wrapperChecksum).isNotBlank(); + + return """ + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/%s/apache-maven-%s-bin.zip + wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/%s/maven-wrapper-%s.jar + distributionSha256Sum=%s + wrapperSha256Sum=%s + """.formatted(mavenDistributionVersion, mavenDistributionVersion, wrapperVersion, wrapperVersion, distributionChecksum, wrapperChecksum); + }) + ), + mvnw, + mvnwCmd, + mvnWrapperJarQuark + ); + } + private String withLicenseHeader(@Language("properties") String original) { return MavenWrapper.ASF_LICENSE_HEADER + original; } From a41cafa8af8a0b0cd1d5aad1598fb0d68fbf87bc Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Mon, 17 Jul 2023 11:51:10 -0500 Subject: [PATCH 065/447] Polish --- .../test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java index 2e834ac0e68..3dca7e357e9 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java @@ -415,7 +415,7 @@ void defaultsToLatestRelease() { }), properties( withLicenseHeader(""" - distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip + distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.0/apache-maven-3.8.0-bin.zip wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar distributionSha256Sum=2e181515ce8ae14b7a904c40bb4794831f5fd1d9641107a13b916af15af4001a wrapperSha256Sum=ff7f21f2ef81723377e3d42d06661c4e3af60cf4bdfb7579ac8f22051399942d From cbdb0975d68c622558529a29bd59f4e1f9a9dbff Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Mon, 17 Jul 2023 12:32:05 -0500 Subject: [PATCH 066/447] Fix the test --- .../maven/UpdateMavenWrapperTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java index 3dca7e357e9..7cfac9bc873 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenWrapperTest.java @@ -407,10 +407,11 @@ void defaultsToLatestRelease() { var mavenWrapperJar = result(run, Remote.class, "maven-wrapper.jar"); assertThat(mavenWrapperJar.getSourcePath()).isEqualTo(WRAPPER_JAR_LOCATION); - //noinspection OptionalGetWithoutIsPresent - BuildTool buildTool = mavenWrapperJar.getMarkers().findFirst(BuildTool.class).get(); - assertThat(buildTool.getVersion()).isNotEqualTo("3.8.0"); - assertThat(mavenWrapperJar.getUri()).isEqualTo(URI.create("https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/" + buildTool.getVersion() + "/maven-wrapper-" + buildTool.getVersion() + ".jar")); + Matcher wrapperVersionMatcher = Pattern.compile("maven-wrapper-(.*?)\\.jar").matcher(mavenWrapperJar.getUri().toString()); + assertThat(wrapperVersionMatcher.find()).isTrue(); + String wrapperVersion = wrapperVersionMatcher.group(1); + assertThat(wrapperVersion).isNotEqualTo("3.1.1"); + assertThat(mavenWrapperJar.getUri()).isEqualTo(URI.create("https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/" + wrapperVersion + "/maven-wrapper-" + wrapperVersion + ".jar")); assertThat(isValidWrapperJar(mavenWrapperJar)).as("Wrapper jar is not valid").isTrue(); }), properties( @@ -422,7 +423,7 @@ void defaultsToLatestRelease() { """), spec -> spec.path(".mvn/wrapper/maven-wrapper.properties") .after(after -> { - Matcher distributionVersionMatcher = Pattern.compile("apache-maven-(.*?)-bin.zip").matcher(after); + Matcher distributionVersionMatcher = Pattern.compile("apache-maven-(.*?)-bin\\.zip").matcher(after); assertThat(distributionVersionMatcher.find()).isTrue(); String mavenDistributionVersion = distributionVersionMatcher.group(1); assertThat(mavenDistributionVersion).isNotEqualTo("3.8.0"); @@ -432,7 +433,7 @@ void defaultsToLatestRelease() { String distributionChecksum = distributionChecksumMatcher.group(1); assertThat(distributionChecksum).isNotBlank(); - Matcher wrapperVersionMatcher = Pattern.compile("maven-wrapper-(.*?).jar").matcher(after); + Matcher wrapperVersionMatcher = Pattern.compile("maven-wrapper-(.*?)\\.jar").matcher(after); assertThat(wrapperVersionMatcher.find()).isTrue(); String wrapperVersion = wrapperVersionMatcher.group(1); assertThat(wrapperVersion).isNotEqualTo("3.1.1"); @@ -442,12 +443,12 @@ void defaultsToLatestRelease() { String wrapperChecksum = wrapperChecksumMatcher.group(1); assertThat(wrapperChecksum).isNotBlank(); - return """ + return withLicenseHeader(""" distributionUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/%s/apache-maven-%s-bin.zip wrapperUrl=https\\://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/%s/maven-wrapper-%s.jar distributionSha256Sum=%s wrapperSha256Sum=%s - """.formatted(mavenDistributionVersion, mavenDistributionVersion, wrapperVersion, wrapperVersion, distributionChecksum, wrapperChecksum); + """.formatted(mavenDistributionVersion, mavenDistributionVersion, wrapperVersion, wrapperVersion, distributionChecksum, wrapperChecksum)); }) ), mvnw, From 5e65404eda61110cfc2077e951825f3173758e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20McCarpenter=E2=84=A2=EF=B8=8F?= <kevin@moderne.io> Date: Mon, 17 Jul 2023 19:20:56 -0300 Subject: [PATCH 067/447] Visit markers on RemoteVisitor (#3422) --- .../org/openrewrite/remote/RemoteVisitor.java | 4 +- .../openrewrite/remote/RemoteVisitorTest.java | 64 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 rewrite-core/src/test/java/org/openrewrite/remote/RemoteVisitorTest.java diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteVisitor.java b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteVisitor.java index 8d0adcc43b7..737b9b28fab 100755 --- a/rewrite-core/src/main/java/org/openrewrite/remote/RemoteVisitor.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/RemoteVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2023 the original author or authors. * <p> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,6 @@ public boolean isAcceptable(SourceFile sourceFile, P p) { } public Remote visitRemote(Remote remote, P p) { - return remote; + return remote.withMarkers(visitMarkers(remote.getMarkers(), p)); } } diff --git a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteVisitorTest.java b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteVisitorTest.java new file mode 100644 index 00000000000..d55a1051f35 --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteVisitorTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.remote; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openrewrite.Tree; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.Markers; +import org.openrewrite.marker.Markup; +import org.openrewrite.marker.SearchResult; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class RemoteVisitorTest { + + @Test + void visitsMarkers() throws URISyntaxException { + AtomicBoolean markersVisited = new AtomicBoolean(); + RemoteVisitor<Integer> remoteVisitor = new RemoteVisitor<>() { + @Override + public Markers visitMarkers(@Nullable Markers markers, Integer integer) { + Markers m = super.visitMarkers(markers, integer); + markersVisited.set(true); + return m; + } + }; + remoteVisitor.visitRemote(new RemoteArchive( + Tree.randomId(), + Path.of("foo/bar/gradle-wrapper.jar"), + Markers.EMPTY, + new URI("https://gradle.gradle/gradle-wrapper.jar"), + null, + false, + null, + "Gradle wrapper jar", + Collections.emptyList(), + null + ), 0); + + assertThat(markersVisited.get()).isTrue(); + } + +} From 0eaa9a3542850578db3bf92d633b43863faf7f7f Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Tue, 18 Jul 2023 16:39:02 +0200 Subject: [PATCH 068/447] Checking RecipeIntrospectionUtils in rewrite-test (#3420) --- .../org/openrewrite/RecipeSchedulerTest.java | 3 +++ .../openrewrite/table/RecipeRunStatsTest.java | 2 ++ .../openrewrite/java/ExtractInterfaceTest.java | 2 ++ .../org/openrewrite/maven/AssertionsTest.java | 2 ++ .../java/org/openrewrite/test/RewriteTest.java | 17 +++++++++++++++-- 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/rewrite-core/src/test/java/org/openrewrite/RecipeSchedulerTest.java b/rewrite-core/src/test/java/org/openrewrite/RecipeSchedulerTest.java index e3e51b1257c..b4c485747e0 100644 --- a/rewrite-core/src/test/java/org/openrewrite/RecipeSchedulerTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/RecipeSchedulerTest.java @@ -15,6 +15,8 @@ */ package org.openrewrite; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; import org.junit.jupiter.api.Test; import org.openrewrite.marker.Markup; import org.openrewrite.test.RewriteTest; @@ -56,6 +58,7 @@ void exceptionsCauseResult() { } } +@AllArgsConstructor class BoomRecipe extends Recipe { @Override public String getDisplayName() { diff --git a/rewrite-core/src/test/java/org/openrewrite/table/RecipeRunStatsTest.java b/rewrite-core/src/test/java/org/openrewrite/table/RecipeRunStatsTest.java index ce1d8259641..8374e7bf6dd 100644 --- a/rewrite-core/src/test/java/org/openrewrite/table/RecipeRunStatsTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/table/RecipeRunStatsTest.java @@ -15,6 +15,7 @@ */ package org.openrewrite.table; +import lombok.AllArgsConstructor; import org.junit.jupiter.api.Test; import org.openrewrite.*; import org.openrewrite.marker.SearchResult; @@ -28,6 +29,7 @@ public class RecipeRunStatsTest implements RewriteTest { + @AllArgsConstructor static class RecipeWithApplicabilityTest extends Recipe { @Override public String getDisplayName() { diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ExtractInterfaceTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ExtractInterfaceTest.java index 9912ed7d3f3..f6d8f01b355 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ExtractInterfaceTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ExtractInterfaceTest.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java; +import lombok.AllArgsConstructor; import org.junit.jupiter.api.Test; import org.openrewrite.*; import org.openrewrite.java.tree.J; @@ -29,6 +30,7 @@ class ExtractInterfaceTest implements RewriteTest { + @AllArgsConstructor private static class ExtractTestInterface extends ScanningRecipe<AtomicReference<J.CompilationUnit>> { @Override public String getDisplayName() { diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AssertionsTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AssertionsTest.java index 87f96822767..d303caac057 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/AssertionsTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AssertionsTest.java @@ -15,6 +15,7 @@ */ package org.openrewrite.maven; +import lombok.AllArgsConstructor; import lombok.Getter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -94,6 +95,7 @@ void xmlAndPomXmlUseCorrectParserWhenPomXmlIsLast() { assertThat(xmlCount.get()).isEqualTo(2); } + @AllArgsConstructor private static class MavenOnlyRecipe extends Recipe { @Override public String getDisplayName() { diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java index 7cac878e578..977c3daeeda 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java @@ -22,6 +22,7 @@ import org.openrewrite.config.CompositeRecipe; import org.openrewrite.config.Environment; import org.openrewrite.config.OptionDescriptor; +import org.openrewrite.internal.RecipeIntrospectionUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.NonNull; import org.openrewrite.internal.lang.Nullable; @@ -41,8 +42,7 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.Assertions.*; import static org.openrewrite.internal.StringUtils.trimIndentPreserveCRLF; @SuppressWarnings("unused") @@ -157,12 +157,25 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) .isNotNull(); if (!(recipe instanceof AdHocRecipe) && !(recipe instanceof CompositeRecipe) && + !(recipe.equals(Recipe.noop())) && testClassSpec.serializationValidation && testMethodSpec.serializationValidation) { RecipeSerializer recipeSerializer = new RecipeSerializer(); assertThat(recipeSerializer.read(recipeSerializer.write(recipe))) .as("Recipe must be serializable/deserializable") .isEqualTo(recipe); + assertThatCode(() -> { + Recipe r = RecipeIntrospectionUtils.constructRecipe(recipe.getClass()); + // getRecipeList should not fail with default parameters from RecipeIntrospectionUtils. + r.getRecipeList(); + // We add recipes to HashSet in some places, we need to validate that hashCode and equals does not fail. + //noinspection ResultOfMethodCallIgnored + r.hashCode(); + //noinspection EqualsWithItself,ResultOfMethodCallIgnored + r.equals(r); + }) + .as("Recipe must be able to instantiate via RecipeIntrospectionUtils") + .doesNotThrowAnyException(); validateRecipeNameAndDescription(recipe); validateRecipeOptions(recipe); } From 58c31126f21ce8d8707c3a4701ffc3bd2f647e34 Mon Sep 17 00:00:00 2001 From: Mike Solomon <mike@moderne.io> Date: Tue, 18 Jul 2023 08:35:38 -0700 Subject: [PATCH 069/447] Improve recipe descriptions (#3424) * Improve recipe descriptions I'm working through the recipes trying to make sure that the descriptions and params make sense grammatically and things are spelled correctly. This PR is mostly dedicated to YAML recipes with 1 minor JSON recipe included. * Add missing dot --------- Co-authored-by: Mike Sol <mike-solomon@users.noreply.github.com> Co-authored-by: Tim te Beek <tim@moderne.io> --- .../src/main/java/org/openrewrite/json/ChangeKey.java | 2 +- .../main/java/org/openrewrite/yaml/AppendToSequence.java | 6 +++--- .../src/main/java/org/openrewrite/yaml/ChangeKey.java | 6 +++--- .../main/java/org/openrewrite/yaml/ChangePropertyKey.java | 8 +++----- .../java/org/openrewrite/yaml/ChangePropertyValue.java | 7 +++---- .../src/main/java/org/openrewrite/yaml/ChangeValue.java | 6 +++--- .../java/org/openrewrite/yaml/CoalesceProperties.java | 3 +-- .../java/org/openrewrite/yaml/CommentOutProperty.java | 4 ++-- .../src/main/java/org/openrewrite/yaml/CopyValue.java | 4 ++-- .../src/main/java/org/openrewrite/yaml/DeleteKey.java | 2 +- .../main/java/org/openrewrite/yaml/DeleteProperty.java | 2 +- .../src/main/java/org/openrewrite/yaml/MergeYaml.java | 2 +- .../java/org/openrewrite/yaml/cleanup/RemoveUnused.java | 2 +- .../main/java/org/openrewrite/yaml/search/FindKey.java | 2 +- .../java/org/openrewrite/yaml/search/FindProperty.java | 5 ++--- 15 files changed, 28 insertions(+), 33 deletions(-) diff --git a/rewrite-json/src/main/java/org/openrewrite/json/ChangeKey.java b/rewrite-json/src/main/java/org/openrewrite/json/ChangeKey.java index 5d7e3141963..bf7a9c1b7a0 100644 --- a/rewrite-json/src/main/java/org/openrewrite/json/ChangeKey.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/ChangeKey.java @@ -40,7 +40,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Change a JSON mapping entry key leaving the value intact."; + return "Changes a JSON mapping entry key, while leaving the value intact."; } @Override diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/AppendToSequence.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/AppendToSequence.java index 50b8d5804f8..8047c19f486 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/AppendToSequence.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/AppendToSequence.java @@ -29,7 +29,7 @@ @EqualsAndHashCode(callSuper = true) public class AppendToSequence extends Recipe { @Option(displayName = "sequence path", - description = "A JsonPath expression to locate a YAML sequence.", + description = "A [JsonPath](https://github.com/json-path/JsonPath) expression to locate a YAML sequence.", example = "$.universe.planets") String sequencePath; @@ -39,14 +39,14 @@ public class AppendToSequence extends Recipe { String value; @Option(displayName = "Optional: match existing sequence values", - description = "Recipe appends to sequence only when existing sequence values match", + description = "If specified, the item will only be appended if the existing sequence matches these values.", example = "existingValue1", required = false) @Nullable List<String> existingSequenceValues; @Option(displayName = "Optional: match existing sequence values in any order", - description = "match existing sequence values in any order", + description = "If specified in combination with the above parameter, the item will only be appended if the existing sequence has the specified values in any order.", example = "true", required = false) @Nullable diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeKey.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeKey.java index 866650d78bb..ad0fc8f347d 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeKey.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeKey.java @@ -27,12 +27,12 @@ @EqualsAndHashCode(callSuper = true) public class ChangeKey extends Recipe { @Option(displayName = "Old key path", - description = "A JsonPath expression to locate a YAML entry.", + description = "A [JsonPath](https://github.com/json-path/JsonPath) expression to locate a YAML entry.", example = "$.subjects.kind") String oldKeyPath; @Option(displayName = "New key", - description = "The new name for the key selected by oldKeyPath.", + description = "The new name for the key selected by the `oldKeyPath`.", example = "kind") String newKey; @@ -43,7 +43,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Change a YAML mapping entry key leaving the value intact."; + return "Change a YAML mapping entry key while leaving the value intact."; } @Override diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyKey.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyKey.java index 2e006d8eec3..347afdc861b 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyKey.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyKey.java @@ -47,7 +47,7 @@ public class ChangePropertyKey extends Recipe { @Option(displayName = "Old property key", - description = "The property key to rename. Supports glob", + description = "The property key to rename. Supports glob patterns.", example = "management.metrics.binders.*.enabled") String oldPropertyKey; @@ -58,7 +58,7 @@ public class ChangePropertyKey extends Recipe { @Option(displayName = "Use relaxed binding", description = "Whether to match the `oldPropertyKey` using [relaxed binding](https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding) " + - "rules. Default is `true`. Set to `false` to use exact matching.", + "rules. Defaults to `true`. If you want to use exact matching in your search, set this to `false`.", required = false) @Nullable Boolean relaxedBinding; @@ -76,9 +76,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Change a YAML property key leaving the value intact. Nested YAML mappings are " + - "interpreted as dot separated property names, i.e. as Spring Boot interprets " + - "application.yml files."; + return "Change a YAML property key while leaving the value intact. Expects dot notation for nested YAML mappings, similar to how Spring Boot interprets `application.yml` files."; } @Override diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyValue.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyValue.java index 2daba1394bc..0d885b1e71a 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyValue.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangePropertyValue.java @@ -30,7 +30,7 @@ @EqualsAndHashCode(callSuper = true) public class ChangePropertyValue extends Recipe { @Option(displayName = "Property key", - description = "The key to look for. Glob is supported.", + description = "The key to look for. Supports glob patterns.", example = "management.metrics.binders.*.enabled") String propertyKey; @@ -45,7 +45,7 @@ public class ChangePropertyValue extends Recipe { String oldValue; @Option(displayName = "Regex", - description = "Default false. If enabled, `oldValue` will be interpreted as a Regular Expression, and capture group contents will be available in `newValue`", + description = "Defaults to `false`. If enabled, `oldValue` will be interpreted as a regular expression, and the capture group contents will be available in `newValue`", required = false) @Nullable Boolean regex; @@ -64,8 +64,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Change a YAML property. Nested YAML mappings are interpreted as dot separated property names, i.e. " + - " as Spring Boot interprets `application.yml` files."; + return "Change a YAML property. Expects dot notation for nested YAML mappings, similar to how Spring Boot interprets `application.yml` files."; } @Override diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeValue.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeValue.java index 74dd6ea241e..22549feca10 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeValue.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/ChangeValue.java @@ -30,12 +30,12 @@ @EqualsAndHashCode(callSuper = true) public class ChangeValue extends Recipe { @Option(displayName = "Key path", - description = "A JsonPath expression to locate a YAML entry.", + description = "A [JsonPath](https://github.com/json-path/JsonPath) expression to locate a YAML entry.", example = "$.subjects.kind") String oldKeyPath; @Option(displayName = "New value", - description = "The new value to set for the key identified by oldKeyPath.", + description = "The new value to set for the key identified by the `oldKeyPath`.", example = "Deployment") String value; @@ -46,7 +46,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Change a YAML mapping entry value leaving the key intact."; + return "Change a YAML mapping entry value while leaving the key intact."; } @Override diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/CoalesceProperties.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/CoalesceProperties.java index ff43085326e..1657acde7e8 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/CoalesceProperties.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/CoalesceProperties.java @@ -28,8 +28,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Simplify nested map hierarchies into their simplest dot separated property form, i.e. as Spring Boot interprets " + - "application.yml files."; + return "Simplify nested map hierarchies into their simplest dot separated property form, similar to how Spring Boot interprets `application.yml` files."; } @Override diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/CommentOutProperty.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/CommentOutProperty.java index 8ca957e629d..f1d2d4dda5a 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/CommentOutProperty.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/CommentOutProperty.java @@ -38,8 +38,8 @@ public class CommentOutProperty extends Recipe { String propertyKey; @Option(displayName = "comment text", - description = "comment text to be added.", - example = "This property is deprecated, please migrate") + description = "The comment text to be added before the specified key.", + example = "The `foo` property is deprecated, please migrate") String commentText; @Override diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/CopyValue.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/CopyValue.java index 557b5ae6f49..3dd1ca35967 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/CopyValue.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/CopyValue.java @@ -27,12 +27,12 @@ @EqualsAndHashCode(callSuper = true) public class CopyValue extends Recipe { @Option(displayName = "Old key path", - description = "A JsonPath expression to locate a YAML key/value pair to copy.", + description = "A [JsonPath](https://github.com/json-path/JsonPath) expression to locate a YAML key/value pair to copy.", example = "$.source.kind") String oldKeyPath; @Option(displayName = "New key path", - description = "A JsonPath expression for where the new value should be copied to.", + description = "A [JsonPath](https://github.com/json-path/JsonPath) expression for where the new value should be copied to.", example = "$.dest.kind") String newKey; diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteKey.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteKey.java index c8a286669c2..3a25bdda7fa 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteKey.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteKey.java @@ -27,7 +27,7 @@ @EqualsAndHashCode(callSuper = true) public class DeleteKey extends Recipe { @Option(displayName = "Key path", - description = "A JsonPath expression to locate a YAML entry.", + description = "A [JsonPath](https://github.com/json-path/JsonPath) expression to locate a YAML entry.", example = "$.source.kind") String keyPath; diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteProperty.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteProperty.java index 7fa395325ff..2060a8a69fb 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteProperty.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/DeleteProperty.java @@ -53,7 +53,7 @@ public class DeleteProperty extends Recipe { @Option(displayName = "Use relaxed binding", description = "Whether to match the `propertyKey` using [relaxed binding](https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding) " + - "rules. Default is `true`. Set to `false` to use exact matching.", + "rules. Defaults to `true`. If you want to use exact matching in your search, set this to `false`.", required = false) @Nullable Boolean relaxedBinding; diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYaml.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYaml.java index 1cdaec98b46..952c0868d3c 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYaml.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYaml.java @@ -27,7 +27,7 @@ @EqualsAndHashCode(callSuper = true) public class MergeYaml extends Recipe { @Option(displayName = "Key path", - description = "A JsonPath expression used to find matching keys.", + description = "A [JsonPath](https://github.com/json-path/JsonPath) expression used to find matching keys.", example = "$.metadata") String key; diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/cleanup/RemoveUnused.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/cleanup/RemoveUnused.java index a6b41e4459a..817ee8c12ea 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/cleanup/RemoveUnused.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/cleanup/RemoveUnused.java @@ -28,7 +28,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Remove YAML mapping and sequence keys that have no value."; + return "Remove YAML mappings and sequence keys that have no value."; } @Override diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/search/FindKey.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/search/FindKey.java index 95a0bf0322d..b40f50d9970 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/search/FindKey.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/search/FindKey.java @@ -44,7 +44,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Find YAML entries by JsonPath expression."; + return "Find YAML entries that match the specified [JsonPath](https://github.com/json-path/JsonPath) expression."; } @Override diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/search/FindProperty.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/search/FindProperty.java index 7b4914fa05b..e1115d61329 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/search/FindProperty.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/search/FindProperty.java @@ -41,7 +41,7 @@ public class FindProperty extends Recipe { @Option(displayName = "Use relaxed binding", description = "Whether to match the `propertyKey` using [relaxed binding](https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding) " + - "rules. Default is `true`. Set to `false` to use exact matching.", + "rules. Defaults to `true`. If you want to use exact matching in your search, set this to `false`.", required = false) @Nullable Boolean relaxedBinding; @@ -53,8 +53,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Find a YAML property. Nested YAML mappings are interpreted as dot separated property names, i.e. " + - " as Spring Boot interprets `application.yml` files."; + return "Find YAML properties that match the specified `propertyKey`. Expects dot notation for nested YAML mappings, similar to how Spring Boot interprets `application.yml` files."; } @Override From 735bd0f5804532ed902e50145652ad73853eeaac Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Wed, 19 Jul 2023 10:15:26 -0400 Subject: [PATCH 070/447] Add matchOverrides on several core Java recipes operating on methods --- .../openrewrite/java/ReorderMethodArgumentsTest.java | 8 ++++---- .../org/openrewrite/java/SimplifyMethodChainTest.java | 2 +- .../java/search/ResultOfMethodCallIgnoredTest.java | 2 +- .../java/org/openrewrite/java/RemoveImplements.java | 3 ++- .../org/openrewrite/java/ReorderMethodArguments.java | 8 +++++++- .../org/openrewrite/java/SimplifyMethodChain.java | 11 +++++++++-- .../java/search/ResultOfMethodCallIgnored.java | 9 ++++++++- 7 files changed, 32 insertions(+), 11 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java index 8a874faa496..ea3953e35c1 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java @@ -31,7 +31,7 @@ void reorderArguments() { rewriteRun( spec -> spec.recipes( new ReorderMethodArguments("a.A foo(String, Integer, Integer)", - new String[]{"n", "m", "s"}, null, null), + new String[]{"n", "m", "s"}, null, null, null), toRecipe(() -> new JavaVisitor<>() { @Override public J visitLiteral(J.Literal literal, ExecutionContext p) { @@ -86,7 +86,7 @@ public void test() { void reorderArgumentsWithNoSourceAttachment() { rewriteRun( spec -> spec.recipe(new ReorderMethodArguments("a.A foo(..)", - new String[]{"s", "n"}, new String[]{"n", "s"}, null)) + new String[]{"s", "n"}, new String[]{"n", "s"}, null, null)) .cycles(1).expectedCyclesThatMakeChanges(1), java( """ @@ -124,7 +124,7 @@ public void test() { void reorderArgumentsWhereOneOfTheOriginalArgumentsIsVararg() { rewriteRun( spec -> spec.recipe(new ReorderMethodArguments("a.A foo(..)", - new String[]{"s", "o", "n"}, null, null)) + new String[]{"s", "o", "n"}, null, null, null)) .cycles(1).expectedCyclesThatMakeChanges(1), java( """ @@ -162,7 +162,7 @@ public void test() { void reorderArgumentsWhereTheLastArgumentIsVarargAndNotPresentInInvocation() { rewriteRun( spec -> spec.recipe(new ReorderMethodArguments("a.A foo(..)", - new String[]{"o", "s"}, null, null)), + new String[]{"o", "s"}, null, null, null)), java( """ package a; diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/SimplifyMethodChainTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/SimplifyMethodChainTest.java index f9de3822e28..d0de68b3089 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/SimplifyMethodChainTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/SimplifyMethodChainTest.java @@ -28,7 +28,7 @@ class SimplifyMethodChainTest implements RewriteTest { void simplify() { rewriteRun( spec -> spec.recipe(new SimplifyMethodChain( - Arrays.asList("A b()", "B c()"), "c2")), + Arrays.asList("A b()", "B c()"), "c2", false)), java( """ class A { diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/ResultOfMethodCallIgnoredTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/ResultOfMethodCallIgnoredTest.java index 4b0d0d64f07..03d58d33539 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/ResultOfMethodCallIgnoredTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/ResultOfMethodCallIgnoredTest.java @@ -28,7 +28,7 @@ class ResultOfMethodCallIgnoredTest implements RewriteTest { @Test void resultOfMethodCallIgnored() { rewriteRun( - spec -> spec.recipe(new ResultOfMethodCallIgnored("java.io.File mkdir*()")), + spec -> spec.recipe(new ResultOfMethodCallIgnored("java.io.File mkdir*()", false)), java( """ import java.io.File; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImplements.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImplements.java index 778dc4f0e75..29345204c16 100755 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveImplements.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveImplements.java @@ -49,7 +49,8 @@ public String getDescription() { @Option(displayName = "Filter", description = "Only apply the interface removal to classes with fully qualified names that begin with this filter. " + "`null` or empty matches all classes.", - example = "com.yourorg.") + example = "com.yourorg.", + required = false) @Nullable String filter; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java b/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java index ea56265b29d..a7f5819b19c 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java @@ -73,6 +73,12 @@ public class ReorderMethodArguments extends Recipe { @Nullable Boolean ignoreDefinition; + @Option(displayName = "Match on overrides", + description = "When enabled, find methods that are overrides of the method pattern.", + required = false) + @Nullable + Boolean matchOverrides; + @JsonPOJOBuilder(withPrefix = "") public static class Builder { } @@ -104,7 +110,7 @@ public J visit(@Nullable Tree tree, ExecutionContext ctx) { } return super.visit(tree, ctx); } - }, new ReorderMethodArgumentsVisitor(new MethodMatcher(methodPattern))); + }, new ReorderMethodArgumentsVisitor(new MethodMatcher(methodPattern, matchOverrides))); } private class ReorderMethodArgumentsVisitor extends JavaIsoVisitor<ExecutionContext> { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java b/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java index 19484d894d5..dfa7910a69c 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java @@ -18,6 +18,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.*; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; @@ -37,6 +38,12 @@ public class SimplifyMethodChain extends Recipe { description = "The method name that will replace the existing name. The new method name target is assumed to have the same arguments as the last method in the chain.") String newMethodName; + @Option(displayName = "Match on overrides", + description = "When enabled, find methods that are overrides of the method pattern.", + required = false) + @Nullable + Boolean matchOverrides; + @Override public String getDisplayName() { return "Simplify a call chain"; @@ -57,7 +64,7 @@ public Validated<Object> validate() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { List<MethodMatcher> matchers = methodPatternChain.stream() - .map(MethodMatcher::new) + .map(matcher -> new MethodMatcher(matcher, matchOverrides)) .collect(Collectors.toList()); Collections.reverse(matchers); @@ -65,7 +72,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { @Override public J visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { for (String method : methodPatternChain) { - doAfterVisit(new UsesMethod<>(method)); + doAfterVisit(new UsesMethod<>(method, matchOverrides)); } return cu; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/ResultOfMethodCallIgnored.java b/rewrite-java/src/main/java/org/openrewrite/java/search/ResultOfMethodCallIgnored.java index db7b3c5b21d..6bffc65bb3a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/ResultOfMethodCallIgnored.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/ResultOfMethodCallIgnored.java @@ -21,6 +21,7 @@ import org.openrewrite.Option; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.J; @@ -38,6 +39,12 @@ public class ResultOfMethodCallIgnored extends Recipe { example = "java.io.File mkdir*()") String methodPattern; + @Option(displayName = "Match on overrides", + description = "When enabled, find methods that are overrides of the method pattern.", + required = false) + @Nullable + Boolean matchOverrides; + @Override public String getDisplayName() { return "Result of method call ignored"; @@ -50,7 +57,7 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - MethodMatcher methodMatcher = new MethodMatcher(methodPattern); + MethodMatcher methodMatcher = new MethodMatcher(methodPattern, matchOverrides); return new JavaIsoVisitor<ExecutionContext>() { @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { From f6c6d8c3b779c15a610f702a9cdedfbbb08e6e6a Mon Sep 17 00:00:00 2001 From: Mike Solomon <mike@moderne.io> Date: Wed, 19 Jul 2023 08:23:39 -0700 Subject: [PATCH 071/447] Update ChangeKey.java Minor consistency fix with recipe description --- rewrite-json/src/main/java/org/openrewrite/json/ChangeKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-json/src/main/java/org/openrewrite/json/ChangeKey.java b/rewrite-json/src/main/java/org/openrewrite/json/ChangeKey.java index bf7a9c1b7a0..c36e6e18714 100644 --- a/rewrite-json/src/main/java/org/openrewrite/json/ChangeKey.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/ChangeKey.java @@ -40,7 +40,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Changes a JSON mapping entry key, while leaving the value intact."; + return "Change a JSON mapping entry key, while leaving the value intact."; } @Override From c65cb5c9706356b3fae6de7c7100eb3293944e49 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Wed, 19 Jul 2023 12:06:25 -0400 Subject: [PATCH 072/447] Polish name of NormalizeLineBreaks --- .../java/org/openrewrite/xml/format/NormalizeLineBreaks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/format/NormalizeLineBreaks.java b/rewrite-xml/src/main/java/org/openrewrite/xml/format/NormalizeLineBreaks.java index 29d396183d1..3bd5d105e6e 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/format/NormalizeLineBreaks.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/format/NormalizeLineBreaks.java @@ -27,7 +27,7 @@ public class NormalizeLineBreaks extends Recipe { @Override public String getDisplayName() { - return "Normalize the line breaks"; + return "Normalize line breaks"; } @Override From c75ead0ed463b9df751c3d15ab3126f1d373f998 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Wed, 19 Jul 2023 12:18:21 -0400 Subject: [PATCH 073/447] Space flyweight synchronization --- .../src/main/java/org/openrewrite/java/tree/Space.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index 8c39aab2d09..2cbafcce5b2 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -20,10 +20,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.WeakHashMap; +import java.util.*; import static java.util.Collections.emptyList; @@ -46,7 +43,7 @@ public class Space { * e.g.: a single space between keywords, or the common indentation of every line in a block. * So use flyweights to avoid storing many instances of functionally identical spaces */ - private static final Map<String, Space> flyweights = new WeakHashMap<>(); + private static final Map<String, Space> flyweights = Collections.synchronizedMap(new WeakHashMap<>()); static { flyweights.put(" ", SINGLE_SPACE); } From 497dec6e85ce5af74aae0555a3e8ff3e2ed00bdc Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Wed, 19 Jul 2023 15:52:23 -0400 Subject: [PATCH 074/447] Revert "Space flyweight synchronization" This reverts commit c75ead0ed463b9df751c3d15ab3126f1d373f998. --- .../src/main/java/org/openrewrite/java/tree/Space.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index 2cbafcce5b2..8c39aab2d09 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -20,7 +20,10 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; import static java.util.Collections.emptyList; @@ -43,7 +46,7 @@ public class Space { * e.g.: a single space between keywords, or the common indentation of every line in a block. * So use flyweights to avoid storing many instances of functionally identical spaces */ - private static final Map<String, Space> flyweights = Collections.synchronizedMap(new WeakHashMap<>()); + private static final Map<String, Space> flyweights = new WeakHashMap<>(); static { flyweights.put(" ", SINGLE_SPACE); } From e77eabd1cbe3479de4f4d6e52728db2d6877399d Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 19 Jul 2023 13:41:47 -0700 Subject: [PATCH 075/447] Add LstProvenance marker, datatable, search recipe --- .../org/openrewrite/FindLstProvenance.java | 54 +++++++++++++++++++ .../org/openrewrite/marker/LstProvenance.java | 25 +++++++++ .../openrewrite/table/LstProvenanceTable.java | 33 ++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java create mode 100644 rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java create mode 100644 rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java diff --git a/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java b/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java new file mode 100644 index 00000000000..8e3703618c9 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java @@ -0,0 +1,54 @@ +package org.openrewrite; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.marker.LstProvenance; +import org.openrewrite.table.LstProvenanceTable; + +import java.util.HashSet; +import java.util.Set; + +@Value +@EqualsAndHashCode(callSuper = true) +public class FindLstProvenance extends ScanningRecipe<FindLstProvenance.Accumulator> { + + @Override + public String getDisplayName() { + return "Find LST provenance"; + } + + @Override + public String getDescription() { + return "Produces a data table showing what versions of OpenRewrite/Moderne tooling was used to produce a given LST. "; + } + + transient LstProvenanceTable provenanceTable = new LstProvenanceTable(this); + + public static class Accumulator { + Set<LstProvenance> seenProvenance = new HashSet<>(); + } + + @Override + public Accumulator getInitialValue(ExecutionContext ctx) { + return new Accumulator(); + } + + @Override + public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) { + return new TreeVisitor<Tree, ExecutionContext>() { + @Override + public Tree preVisit(Tree tree, ExecutionContext ctx) { + stopAfterPreVisit(); + LstProvenance lstProvenance = tree.getMarkers().findFirst(LstProvenance.class).orElse(null); + if (lstProvenance == null) { + return tree; + } + if(acc.seenProvenance.add(lstProvenance)) { + provenanceTable.insertRow(ctx, new LstProvenanceTable.Row(lstProvenance.getBuildToolType(), + lstProvenance.getBuildToolVersion(), lstProvenance.getLstSerializerVersion())); + } + return tree; + } + }; + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java b/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java new file mode 100644 index 00000000000..02608f92bc5 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java @@ -0,0 +1,25 @@ +package org.openrewrite.marker; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.With; + +import java.util.UUID; + +@Value +@With +public class LstProvenance implements Marker { + @EqualsAndHashCode.Exclude + UUID id; + + Type buildToolType; + String buildToolVersion; + String lstSerializerVersion; + + public enum Type { + Gradle, + Maven, + Bazel, + Cli + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java new file mode 100644 index 00000000000..8871187ceb7 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java @@ -0,0 +1,33 @@ +package org.openrewrite.table; + +import com.fasterxml.jackson.annotation.JsonIgnoreType; +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; +import org.openrewrite.marker.LstProvenance; + +@JsonIgnoreType +public class LstProvenanceTable extends DataTable<LstProvenanceTable.Row> { + + public LstProvenanceTable(Recipe recipe) { + super(recipe, + "Parser failures", + "A list of files that failed to parse along with stack traces of their failures."); + } + + @Value + public static class Row { + @Column(displayName = "Build tool type", + description = "The type of tool which produced the LST.") + LstProvenance.Type buildToolType; + + @Column(displayName = "Build tool version", + description = "The version of the build tool which produced the LST.") + String buildToolVersion; + + @Column(displayName = "LST serializer version", + description = "The version of LST serializer which produced the LST.") + String lstSerializerVersion; + } +} From f343ab23226d6c4a6b1acab9333b3127a3fe7d69 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 19 Jul 2023 14:28:06 -0700 Subject: [PATCH 076/447] License headers --- .../java/org/openrewrite/FindLstProvenance.java | 15 +++++++++++++++ .../org/openrewrite/marker/LstProvenance.java | 15 +++++++++++++++ .../org/openrewrite/table/LstProvenanceTable.java | 15 +++++++++++++++ settings.gradle.kts | 2 +- 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java b/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java index 8e3703618c9..25868b6ee1d 100644 --- a/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java +++ b/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite; import lombok.EqualsAndHashCode; diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java b/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java index 02608f92bc5..8108559761b 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite.marker; import lombok.EqualsAndHashCode; diff --git a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java index 8871187ceb7..9d37fb9e084 100644 --- a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java +++ b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite.table; import com.fasterxml.jackson.annotation.JsonIgnoreType; diff --git a/settings.gradle.kts b/settings.gradle.kts index e24fb973fdc..8cbfbd651a5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -42,7 +42,7 @@ val includedProjects = file("IDE.properties").let { }.toSet() if(!file("IDE.properties").exists() || includedProjects.contains("tools")) { - includeBuild("tools") +// includeBuild("tools") } include(*allProjects.toTypedArray()) From 76b321becb1bc42ccda456b572c2b0fe9ec9ef4c Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Wed, 19 Jul 2023 16:59:20 -0500 Subject: [PATCH 077/447] Polish version descriptions across the board. Set newVersion to `latest.release` where ever it makes sense --- .../org/openrewrite/gradle/AddDependency.java | 5 +- .../gradle/AddDependencyVisitor.java | 106 ++++++++---------- .../gradle/UpgradeDependencyVersion.java | 81 +++++-------- .../gradle/plugins/AddBuildPlugin.java | 15 ++- .../AddGradleEnterpriseGradlePlugin.java | 23 +++- .../gradle/plugins/AddPluginVisitor.java | 44 ++++---- .../gradle/plugins/AddSettingsPlugin.java | 23 +++- .../gradle/plugins/UpgradePluginVersion.java | 16 ++- .../gradle/UpgradeDependencyVersionTest.java | 44 ++++++++ .../gradle/plugins/AddBuildPluginTest.java | 20 ++++ .../AddGradleEnterpriseGradlePluginTest.java | 30 +++++ .../plugins/UpgradePluginVersionTest.java | 35 +++++- 12 files changed, 291 insertions(+), 151 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java index b22cfe4e3ed..2e57d403a3a 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java @@ -55,7 +55,10 @@ public class AddDependency extends ScanningRecipe<AddDependency.Scanned> { String artifactId; @Option(displayName = "Version", - description = "An exact version number or node-style semver selector used to select the version number.", + description = "An exact version number or node-style semver selector used to select the version number. " + + "You can also use `latest.release` for the latest available version and `latest.patch` if " + + "the current version is a valid semantic version. For more details, you can look at the documentation " + + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors).", example = "29.X", required = false) @Nullable diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java index a494fc7e882..cfea91a4d83 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java @@ -18,7 +18,6 @@ import lombok.RequiredArgsConstructor; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; -import org.openrewrite.Validated; import org.openrewrite.gradle.internal.InsertDependencyComparator; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; @@ -36,18 +35,14 @@ import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.table.MavenMetadataFailures; import org.openrewrite.maven.tree.*; -import org.openrewrite.semver.ExactVersion; -import org.openrewrite.semver.LatestPatch; -import org.openrewrite.semver.Semver; -import org.openrewrite.semver.VersionComparator; +import org.openrewrite.semver.*; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; +import static java.util.Collections.*; import static java.util.Objects.requireNonNull; @RequiredArgsConstructor @@ -57,12 +52,13 @@ public class AddDependencyVisitor extends GroovyIsoVisitor<ExecutionContext> { private final String groupId; private final String artifactId; + + @Nullable private final String version; @Nullable private final String versionPattern; - @Nullable private final String configuration; @Nullable @@ -77,9 +73,6 @@ public class AddDependencyVisitor extends GroovyIsoVisitor<ExecutionContext> { @Nullable private final MavenMetadataFailures metadataFailures; - @Nullable - private VersionComparator versionComparator; - @Nullable private String resolvedVersion; @@ -117,13 +110,6 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon dependenciesInvocation.withPrefix(Space.format("\n\n")))); } - if (version != null) { - Validated<VersionComparator> versionValidated = Semver.validate(version, versionPattern); - if (versionValidated.isValid()) { - versionComparator = versionValidated.getValue(); - } - } - g = (G.CompilationUnit) new InsertDependencyInOrder(configuration, gp) .visitNonNull(g, ctx, requireNonNull(getCursor().getParent())); @@ -237,16 +223,15 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return m; } - String resolvedVersion = null; - if (versionComparator != null) { + if (version != null) { try { - resolvedVersion = findNewerVersion(groupId, artifactId, gp, ctx); + resolvedVersion = resolveDependencyVersion(groupId, artifactId, "0", version, versionPattern, gp.getMavenRepositories(), metadataFailures, ctx) + .orElse(null); } catch (MavenDownloadingException e) { return e.warn(m); } } - J.Block body = (J.Block) dependenciesBlock.getBody(); String codeTemplate; @@ -326,47 +311,10 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu statements.add(addDependencyInvocation); } body = body.withStatements(statements); - m = m.withArguments(Collections.singletonList(dependenciesBlock.withBody(body))); + m = m.withArguments(singletonList(dependenciesBlock.withBody(body))); return m; } - - @Nullable - private String findNewerVersion(String groupId, String artifactId, GradleProject gradleProject, - ExecutionContext ctx) throws MavenDownloadingException { - if (resolvedVersion != null) { - return resolvedVersion; - } - - if (versionComparator == null || versionComparator instanceof ExactVersion) { - resolvedVersion = version; - } else { - // in the case of "latest.patch", a new version can only be derived if the - // current version is a semantic version - if (versionComparator instanceof LatestPatch && !versionComparator.isValid(version, version)) { - return null; - } - - try { - MavenMetadata mavenMetadata = metadataFailures == null ? - downloadMetadata(groupId, artifactId, gradleProject, ctx) : - metadataFailures.insertRows(ctx, () -> downloadMetadata(groupId, artifactId, gradleProject, ctx)); - resolvedVersion = versionComparator.upgrade(version, mavenMetadata.getVersioning().getVersions()) - .orElse(version); - } catch (IllegalStateException e) { - // this can happen when we encounter exotic versions - return null; - } - } - - return resolvedVersion; - } - - public MavenMetadata downloadMetadata(String groupId, String artifactId, GradleProject gradleProject, ExecutionContext ctx) throws MavenDownloadingException { - return new MavenPomDownloader(emptyMap(), ctx, null, null) - .downloadMetadata(new GroupArtifact(groupId, artifactId), null, - gradleProject.getMavenRepositories()); - } } enum DependencyStyle { @@ -396,4 +344,42 @@ private DependencyStyle autodetectDependencyStyle(List<Statement> statements) { return string >= map ? DependencyStyle.String : DependencyStyle.Map; } + + public static Optional<String> resolveDependencyVersion(String groupId, String artifactId, String currentVersion, @Nullable String newVersion, @Nullable String versionPattern, + List<MavenRepository> repositories, @Nullable MavenMetadataFailures metadataFailures, ExecutionContext ctx) throws MavenDownloadingException { + VersionComparator versionComparator = StringUtils.isBlank(newVersion) ? + new LatestRelease(versionPattern) : + requireNonNull(Semver.validate(newVersion, versionPattern).getValue()); + + Optional<String> version; + if (versionComparator instanceof ExactVersion) { + version = Optional.of(newVersion); + } else if (versionComparator instanceof LatestPatch && !versionComparator.isValid(currentVersion, currentVersion)) { + // in the case of "latest.patch", a new version can only be derived if the + // current version is a semantic version + return Optional.empty(); + } else { + version = findNewerVersion(groupId, artifactId, currentVersion, versionComparator, repositories, metadataFailures, ctx); + } + return version; + } + + private static Optional<String> findNewerVersion(String groupId, String artifactId, String version, VersionComparator versionComparator, + List<MavenRepository> repositories, @Nullable MavenMetadataFailures metadataFailures, ExecutionContext ctx) throws MavenDownloadingException { + try { + MavenMetadata mavenMetadata = metadataFailures == null ? + downloadMetadata(groupId, artifactId, repositories, ctx) : + metadataFailures.insertRows(ctx, () -> downloadMetadata(groupId, artifactId, repositories, ctx)); + return versionComparator.upgrade(version, mavenMetadata.getVersioning().getVersions()); + } catch (IllegalStateException e) { + // this can happen when we encounter exotic versions + return Optional.empty(); + } + } + + private static MavenMetadata downloadMetadata(String groupId, String artifactId, List<MavenRepository> repositories, ExecutionContext ctx) throws MavenDownloadingException { + return new MavenPomDownloader(emptyMap(), ctx, null, null) + .downloadMetadata(new GroupArtifact(groupId, artifactId), null, + repositories); + } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index 5cd839f1a46..773da665eee 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -71,8 +71,11 @@ public class UpgradeDependencyVersion extends Recipe { description = "An exact version number or node-style semver selector used to select the version number. " + "You can also use `latest.release` for the latest available version and `latest.patch` if " + "the current version is a valid semantic version. For more details, you can look at the documentation " + - "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors)", - example = "29.X") + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors). " + + "Defaults to `latest.release`.", + example = "29.X", + required = false) + @Nullable String newVersion; @Option(displayName = "Version pattern", @@ -99,8 +102,8 @@ public String getDescription() { } @Override - public Validated validate() { - Validated validated = super.validate(); + public Validated<Object> validate() { + Validated<Object> validated = super.validate(); if (newVersion != null) { validated = validated.and(Semver.validate(newVersion, versionPattern)); } @@ -110,8 +113,7 @@ public Validated validate() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); - VersionComparator versionComparator = requireNonNull(Semver.validate(newVersion, versionPattern).getValue()); - DependencyMatcher dependencyMatcher = new DependencyMatcher(groupId, artifactId, versionComparator); + DependencyMatcher dependencyMatcher = new DependencyMatcher(groupId, artifactId, null); return Preconditions.check(new FindGradleProject(FindGradleProject.SearchCriteria.Marker), new GroovyVisitor<ExecutionContext>() { @Override @@ -126,7 +128,7 @@ public J postVisit(J tree, ExecutionContext ctx) { return cu; } - cu = (JavaSourceFile) new UpdateVariable(variableNames, versionComparator, maybeGp.get()).visitNonNull(cu, ctx); + cu = (JavaSourceFile) new UpdateVariable(variableNames, maybeGp.get()).visitNonNull(cu, ctx); } Set<GroupArtifactVersion> versionUpdates = getCursor().getMessage(NEW_VERSION_KEY); if (versionUpdates != null) { @@ -198,8 +200,8 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte String version = dep.getVersion(); try { String newVersion = "classpath".equals(m.getSimpleName()) ? - findNewerPluginVersion(dep.getGroupId(), dep.getArtifactId(), version, versionComparator, gradleProject, ctx) : - findNewerProjectDependencyVersion(dep.getGroupId(), dep.getArtifactId(), version, versionComparator, gradleProject, ctx); + findNewerPluginVersion(dep.getGroupId(), dep.getArtifactId(), version, gradleProject, ctx) : + findNewerProjectDependencyVersion(dep.getGroupId(), dep.getArtifactId(), version, gradleProject, ctx); if (newVersion == null || version.equals(newVersion)) { return m; } @@ -249,8 +251,8 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte String newVersion; try { newVersion = "classpath".equals(m.getSimpleName()) ? - findNewerPluginVersion((String) groupLiteral.getValue(), (String) artifactLiteral.getValue(), version, versionComparator, gradleProject, ctx) : - findNewerProjectDependencyVersion((String) groupLiteral.getValue(), (String) artifactLiteral.getValue(), version, versionComparator, gradleProject, ctx); + findNewerPluginVersion((String) groupLiteral.getValue(), (String) artifactLiteral.getValue(), version, gradleProject, ctx) : + findNewerProjectDependencyVersion((String) groupLiteral.getValue(), (String) artifactLiteral.getValue(), version, gradleProject, ctx); } catch (MavenDownloadingException e) { return e.warn(m); } @@ -284,7 +286,6 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte @EqualsAndHashCode(callSuper = true) private class UpdateVariable extends GroovyIsoVisitor<ExecutionContext> { Map<String, GroupArtifact> versionVariableNames; - VersionComparator versionComparator; GradleProject gradleProject; @Override @@ -315,9 +316,9 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations } try { - String newVersion = findNewerProjectDependencyVersion(ga.getGroupId(), ga.getArtifactId(), version, versionComparator, gradleProject, ctx); + String newVersion = findNewerProjectDependencyVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); if (newVersion == null) { - newVersion = findNewerPluginVersion(ga.getGroupId(), ga.getArtifactId(), version, versionComparator, gradleProject, ctx); + newVersion = findNewerPluginVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); } if (newVersion == null) { return v; @@ -366,9 +367,9 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ct } try { - String newVersion = findNewerProjectDependencyVersion(ga.getGroupId(), ga.getArtifactId(), version, versionComparator, gradleProject, ctx); + String newVersion = findNewerProjectDependencyVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); if (newVersion == null) { - newVersion = findNewerPluginVersion(ga.getGroupId(), ga.getArtifactId(), version, versionComparator, gradleProject, ctx); + newVersion = findNewerPluginVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); } if (newVersion == null) { return a; @@ -387,49 +388,17 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ct } @Nullable - private String findNewerPluginVersion(String groupId, String artifactId, String version, VersionComparator versionComparator, - GradleProject gradleProject, ExecutionContext ctx) throws MavenDownloadingException { - return findNewerVersion(groupId, artifactId, version, versionComparator, gradleProject.getMavenPluginRepositories(), ctx); - } - - @Nullable - private String findNewerProjectDependencyVersion(String groupId, String artifactId, String version, VersionComparator versionComparator, - GradleProject gradleProject, ExecutionContext ctx) throws MavenDownloadingException { - return findNewerVersion(groupId, artifactId, version, versionComparator, gradleProject.getMavenRepositories(), ctx); + private String findNewerPluginVersion(String groupId, String artifactId, String version, GradleProject gradleProject, + ExecutionContext ctx) throws MavenDownloadingException { + return AddDependencyVisitor.resolveDependencyVersion(groupId, artifactId, version, newVersion, versionPattern, gradleProject.getMavenPluginRepositories(), metadataFailures, ctx) + .orElse(null); } @Nullable - private String findNewerVersion(String groupId, String artifactId, String version, VersionComparator versionComparator, - List<MavenRepository> repositories, ExecutionContext ctx) throws MavenDownloadingException { - // in the case of "latest.patch", a new version can only be derived if the - // current version is a semantic version - if (versionComparator instanceof LatestPatch && !versionComparator.isValid(version, version)) { - return null; - } - - if (versionComparator instanceof ExactVersion) { - return versionComparator.upgrade(version, Collections.singletonList(newVersion)).orElse(null); - } - - try { - MavenMetadata mavenMetadata = metadataFailures.insertRows(ctx, () -> downloadMetadata(groupId, artifactId, repositories, ctx)); - List<String> versions = new ArrayList<>(); - for (String v : mavenMetadata.getVersioning().getVersions()) { - if (versionComparator.isValid(version, v)) { - versions.add(v); - } - } - return versionComparator.upgrade(version, versions).orElse(null); - } catch (IllegalStateException e) { - // this can happen when we encounter exotic versions - return null; - } - } - - private MavenMetadata downloadMetadata(String groupId, String artifactId, List<MavenRepository> repositories, ExecutionContext ctx) throws MavenDownloadingException { - return new MavenPomDownloader(emptyMap(), ctx, null, null) - .downloadMetadata(new GroupArtifact(groupId, artifactId), null, - repositories); + private String findNewerProjectDependencyVersion(String groupId, String artifactId, String version, GradleProject gradleProject, + ExecutionContext ctx) throws MavenDownloadingException { + return AddDependencyVisitor.resolveDependencyVersion(groupId, artifactId, version, newVersion, versionPattern, gradleProject.getMavenRepositories(), metadataFailures, ctx) + .orElse(null); } static GradleProject replaceVersion(GradleProject gp, ExecutionContext ctx, GroupArtifactVersion gav) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java index 8f44c98d5dd..6cfe604af40 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java @@ -23,6 +23,7 @@ import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.semver.Semver; import java.util.Optional; @@ -35,7 +36,10 @@ public class AddBuildPlugin extends Recipe { String pluginId; @Option(displayName = "Plugin version", - description = "An exact version number or node-style semver selector used to select the version number.", + description = "An exact version number or node-style semver selector used to select the version number. " + + "You can also use `latest.release` for the latest available version and `latest.patch` if " + + "the current version is a valid semantic version. For more details, you can look at the documentation " + + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors).", example = "3.x", required = false) @Nullable @@ -59,6 +63,15 @@ public String getDescription() { return "Add a Gradle build plugin to `build.gradle(.kts)`."; } + @Override + public Validated<Object> validate() { + Validated<Object> validated = super.validate(); + if (version != null) { + validated = validated.and(Semver.validate(version, versionPattern)); + } + return validated; + } + @Override public TreeVisitor<?, ExecutionContext> getVisitor() { return Preconditions.check( diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java index 7959c58e72a..47c97ec7ee9 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java @@ -26,6 +26,7 @@ import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.style.IntelliJ; import org.openrewrite.java.style.TabsAndIndentsStyle; @@ -54,8 +55,14 @@ public class AddGradleEnterpriseGradlePlugin extends Recipe { MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this); @Option(displayName = "Plugin version", - description = "An exact version number or node-style semver selector used to select the plugin version.", - example = "3.x") + description = "An exact version number or node-style semver selector used to select the version number. " + + "You can also use `latest.release` for the latest available version and `latest.patch` if " + + "the current version is a valid semantic version. For more details, you can look at the documentation " + + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors). " + + "Defaults to `latest.release`.", + example = "3.x", + required = false) + @Nullable String version; @Option(displayName = "Server URL", @@ -114,6 +121,15 @@ public String getDescription() { return "Add the Gradle Enterprise Gradle plugin to settings.gradle files."; } + @Override + public Validated<Object> validate() { + Validated<Object> validated = super.validate(); + if (version != null) { + validated = validated.and(Semver.validate(version, null)); + } + return validated; + } + @Override public TreeVisitor<?, ExecutionContext> getVisitor() { return Preconditions.check(Preconditions.or(new IsBuildGradle<>(), new IsSettingsGradle<>()), new GroovyIsoVisitor<ExecutionContext>() { @@ -163,9 +179,8 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon private G.CompilationUnit withPlugin(G.CompilationUnit cu, String pluginId, VersionComparator versionComparator, List<MavenRepository> repositories, ExecutionContext ctx) { try { - Optional<String> maybeNewVersion = resolvePluginVersion(pluginId, "0", version, null, repositories, ctx); + Optional<String> maybeNewVersion = resolvePluginVersion(pluginId, "0", StringUtils.isBlank(version) ? "latest.release" : version, null, repositories, ctx); if (!maybeNewVersion.isPresent()) { - // shouldn't happen since a non-null version was passed to resolvePluginVersion return cu; } String newVersion = maybeNewVersion.get(); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java index 595997d62f4..bca683349d7 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java @@ -25,6 +25,7 @@ import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.search.FindMethods; @@ -37,10 +38,7 @@ import org.openrewrite.maven.tree.GroupArtifact; import org.openrewrite.maven.tree.MavenMetadata; import org.openrewrite.maven.tree.MavenRepository; -import org.openrewrite.semver.ExactVersion; -import org.openrewrite.semver.LatestPatch; -import org.openrewrite.semver.Semver; -import org.openrewrite.semver.VersionComparator; +import org.openrewrite.semver.*; import java.nio.file.Paths; import java.util.List; @@ -67,22 +65,19 @@ public class AddPluginVisitor extends GroovyIsoVisitor<ExecutionContext> { public static Optional<String> resolvePluginVersion(String pluginId, String currentVersion, @Nullable String newVersion, @Nullable String versionPattern, List<MavenRepository> repositories, ExecutionContext ctx) throws MavenDownloadingException { + VersionComparator versionComparator = StringUtils.isBlank(newVersion) ? + new LatestRelease(null) : + requireNonNull(Semver.validate(newVersion, versionPattern).getValue()); + Optional<String> version; - if (newVersion == null) { - version = Optional.empty(); + if (versionComparator instanceof ExactVersion) { + version = Optional.of(newVersion); + } else if (versionComparator instanceof LatestPatch && !versionComparator.isValid(currentVersion, currentVersion)) { + // in the case of "latest.patch", a new version can only be derived if the + // current version is a semantic version + return Optional.empty(); } else { - VersionComparator versionComparator = Semver.validate(newVersion, versionPattern).getValue(); - assert versionComparator != null; - - if (versionComparator instanceof ExactVersion) { - version = versionComparator.upgrade(currentVersion, singletonList(newVersion)); - } else if (versionComparator instanceof LatestPatch && !versionComparator.isValid(currentVersion, currentVersion)) { - // in the case of "latest.patch", a new version can only be derived if the - // current version is a semantic version - return Optional.empty(); - } else { - version = findNewerVersion(pluginId, pluginId + ".gradle.plugin", currentVersion, versionComparator, repositories, ctx); - } + version = findNewerVersion(pluginId, pluginId + ".gradle.plugin", currentVersion, versionComparator, repositories, ctx); } return version; } @@ -108,10 +103,15 @@ private static MavenMetadata downloadMetadata(String groupId, String artifactId, public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { if (FindPlugins.find(cu, pluginId).isEmpty()) { Optional<String> version; - try { - version = resolvePluginVersion(pluginId, "0", newVersion, versionPattern, repositories, ctx); - } catch (MavenDownloadingException e) { - return e.warn(cu); + if (newVersion == null) { + // We have been requested to add a versionless plugin + version = Optional.empty(); + } else { + try { + version = resolvePluginVersion(pluginId, "0", newVersion, versionPattern, repositories, ctx); + } catch (MavenDownloadingException e) { + return e.warn(cu); + } } AtomicInteger singleQuote = new AtomicInteger(); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java index fa79350f888..cc97a866b31 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java @@ -22,7 +22,9 @@ import org.openrewrite.gradle.marker.GradleSettings; import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; +import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.semver.Semver; import java.util.Optional; @@ -35,8 +37,14 @@ public class AddSettingsPlugin extends Recipe { String pluginId; @Option(displayName = "Plugin version", - description = "An exact version number or node-style semver selector used to select the version number.", - example = "3.x") + description = "An exact version number or node-style semver selector used to select the version number. " + + "You can also use `latest.release` for the latest available version and `latest.patch` if " + + "the current version is a valid semantic version. For more details, you can look at the documentation " + + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors). " + + "Defaults to `latest.release`.", + example = "3.x", + required = false) + @Nullable String version; @Option(displayName = "Version pattern", @@ -57,6 +65,15 @@ public String getDescription() { return "Add a Gradle settings plugin to `settings.gradle(.kts)`."; } + @Override + public Validated<Object> validate() { + Validated<Object> validated = super.validate(); + if (version != null) { + validated = validated.and(Semver.validate(version, versionPattern)); + } + return validated; + } + @Override public TreeVisitor<?, ExecutionContext> getVisitor() { return Preconditions.check( @@ -70,7 +87,7 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon } GradleSettings gradleSettings = maybeGradleSettings.get(); - return (G.CompilationUnit) new AddPluginVisitor(pluginId, version, versionPattern, gradleSettings.getPluginRepositories()).visitNonNull(cu, ctx); + return (G.CompilationUnit) new AddPluginVisitor(pluginId, StringUtils.isBlank(version) ? "latest.release" : version, versionPattern, gradleSettings.getPluginRepositories()).visitNonNull(cu, ctx); } }); } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/UpgradePluginVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/UpgradePluginVersion.java index 53993d5b501..0657d48fe91 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/UpgradePluginVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/UpgradePluginVersion.java @@ -50,8 +50,14 @@ public class UpgradePluginVersion extends Recipe { String pluginIdPattern; @Option(displayName = "New version", - description = "An exact version number or node-style semver selector used to select the version number.", - example = "29.X") + description = "An exact version number or node-style semver selector used to select the version number. " + + "You can also use `latest.release` for the latest available version and `latest.patch` if " + + "the current version is a valid semantic version. For more details, you can look at the documentation " + + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors). " + + "Defaults to `latest.release`.", + example = "29.X", + required = false) + @Nullable String newVersion; @Option(displayName = "Version pattern", @@ -74,7 +80,11 @@ public String getDescription() { @Override public Validated<Object> validate() { - return super.validate().and(Semver.validate(newVersion, versionPattern)); + Validated<Object> validated = super.validate(); + if (newVersion != null) { + validated = validated.and(Semver.validate(newVersion, versionPattern)); + } + return validated; } @Override diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java index 0f8359c353d..88a1e1c1338 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java @@ -24,6 +24,8 @@ import org.openrewrite.test.RewriteTest; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.gradle.Assertions.buildGradle; @@ -366,4 +368,46 @@ void matchesGlobs() { ) ); } + + @Test + void defaultsToLatestRelease() { + rewriteRun( + spec -> spec.recipe(new UpgradeDependencyVersion("com.google.guava", "guava", null, "-jre")), + buildGradle( + """ + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + implementation 'com.google.guava:guava:29.0-jre' + } + """, + spec -> spec.after(after -> { + Matcher versionMatcher = Pattern.compile("implementation 'com\\.google\\.guava:guava:(.*?)'").matcher(after); + assertThat(versionMatcher.find()).isTrue(); + String version = versionMatcher.group(1); + assertThat(version).isNotEqualTo("29.0-jre"); + + return """ + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + implementation 'com.google.guava:guava:%s' + } + """.formatted(version); + }) + ) + ); + } } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java index 8e5f560f981..868f56a6967 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java @@ -65,6 +65,26 @@ void addPluginWithoutVersionToExistingBlock() { ); } + @Test + void addThirdPartyPluginWithoutVersion() { + rewriteRun( + spec -> spec.recipe(new AddBuildPlugin("org.openrewrite.rewrite", null, null)), + buildGradle( + """ + plugins { + id "java" + } + """, + """ + plugins { + id "java" + id "org.openrewrite.rewrite" + } + """ + ) + ); + } + @Test void addPluginWithVersionToNewBlock() { rewriteRun( diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java index 80996a3cf4e..940494d8af0 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java @@ -20,6 +20,8 @@ import org.openrewrite.Issue; import org.openrewrite.groovy.tree.G.CompilationUnit; import org.openrewrite.marker.BuildTool; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpec; @@ -28,6 +30,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.Tree.randomId; import static org.openrewrite.gradle.Assertions.*; @@ -225,4 +228,31 @@ void withConfigurationOldInputCapture() { ) ); } + + @Test + void defaultsToLatestRelease() { + rewriteRun( + spec -> spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))) + .recipe(new AddGradleEnterpriseGradlePlugin(null, null, null, null, null, null)), + buildGradle( + "" + ), + settingsGradle( + "", + spec -> spec.after(after -> { + Matcher versionMatcher = Pattern.compile("id 'com\\.gradle\\.enterprise' version '(.*?)'").matcher(after); + assertThat(versionMatcher.find()).isTrue(); + String version = versionMatcher.group(1); + VersionComparator versionComparator = requireNonNull(Semver.validate("[3.14,)", null).getValue()); + assertThat(versionComparator.compare(null, "3.14", version)).isLessThanOrEqualTo(0); + + return """ + plugins { + id 'com.gradle.enterprise' version '%s' + } + """.formatted(version); + }) + ) + ); + } } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/UpgradePluginVersionTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/UpgradePluginVersionTest.java index 60922270021..cbd27a67ddc 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/UpgradePluginVersionTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/UpgradePluginVersionTest.java @@ -17,11 +17,17 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.gradle.Assertions.*; -import static org.openrewrite.gradle.Assertions.buildGradle; class UpgradePluginVersionTest implements RewriteTest { @Override @@ -108,4 +114,31 @@ void exactVersionDoesNotHaveToBeResolvable() { ) ); } + + @Test + void defaultsToLatestRelease() { + rewriteRun( + spec -> spec.recipe(new UpgradePluginVersion("org.openrewrite.rewrite", null, null)), + buildGradle( + """ + plugins { + id 'org.openrewrite.rewrite' version '5.34.0' + } + """, + spec -> spec.after(after -> { + Matcher versionMatcher = Pattern.compile("id 'org\\.openrewrite\\.rewrite' version '(.*?)'").matcher(after); + assertThat(versionMatcher.find()).isTrue(); + String version = versionMatcher.group(1); + VersionComparator versionComparator = requireNonNull(Semver.validate("[6.1.16,)", null).getValue()); + assertThat(versionComparator.compare(null, "6.1.16", version)).isLessThanOrEqualTo(0); + + return """ + plugins { + id 'org.openrewrite.rewrite' version '%s' + } + """.formatted(version); + }) + ) + ); + } } From 5cc8bb9a7f8b6acb49780d2507be9c32974a00eb Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 19 Jul 2023 20:20:12 -0700 Subject: [PATCH 078/447] Prevent JavaParser.runtimeClasspath() from attempting to turn paths into jars or remote URLs into Paths --- .../src/main/java/org/openrewrite/java/JavaParser.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index 1d96f386336..46a60c7a9b2 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -64,6 +64,7 @@ static List<Path> runtimeClasspath() { return new ClassGraph() .disableNestedJarScanning() .getClasspathURIs().stream() + .filter(uri -> "file".equals(uri.getScheme())) .map(Paths::get).collect(toList()); } @@ -194,6 +195,7 @@ static List<Path> dependenciesFromResources(ExecutionContext ctx, String... arti } if (!missingArtifactNames.isEmpty()) { + //noinspection ConstantValue throw new IllegalArgumentException("Unable to find classpath resource dependencies beginning with: " + missingArtifactNames.stream().map(a -> "'" + a + "'").sorted().collect(joining(", ", "", ".\n")) + "The caller is of type " + (caller == null ? "NO CALLER IDENTIFIED" : caller.getName()) + ".\n" + @@ -326,6 +328,7 @@ public B charset(Charset charset) { return (B) this; } + @SuppressWarnings("unused") public B dependsOn(Collection<Input> inputs) { this.dependsOn = inputs; return (B) this; @@ -348,6 +351,7 @@ public B classpath(String... classpath) { return (B) this; } + @SuppressWarnings("UnusedReturnValue") public B classpathFromResources(ExecutionContext ctx, String... classpath) { this.classpath = dependenciesFromResources(ctx, classpath); return (B) this; From 3cf61d3ea0f9a0fb097ca9c0f1a3106a00dcee1a Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Wed, 19 Jul 2023 22:28:07 -0500 Subject: [PATCH 079/447] Fixup some issues with map notation updates and forcibly change the version as coordinates have changed and invalidated the update path --- .../openrewrite/gradle/ChangeDependency.java | 118 +++++++++++++----- .../gradle/ChangeDependencyTest.java | 52 ++++---- 2 files changed, 113 insertions(+), 57 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index 4ed94692c3a..bb1ca307a77 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -27,17 +27,21 @@ import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; +import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.tree.GroupArtifactVersion; +import org.openrewrite.maven.tree.MavenRepository; import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; import org.openrewrite.semver.DependencyMatcher; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import static java.util.Objects.requireNonNull; @@ -69,7 +73,10 @@ public class ChangeDependency extends Recipe { String newArtifactId; @Option(displayName = "New version", - description = "An exact version number or node-style semver selector used to select the version number.", + description = "An exact version number or node-style semver selector used to select the version number. " + + "You can also use `latest.release` for the latest available version and `latest.patch` if " + + "the current version is a valid semantic version. For more details, you can look at the documentation " + + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors).", example = "29.X", required = false) @Nullable @@ -99,13 +106,20 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(oldGroupId + ":" + oldArtifactId).getValue()); final MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); + GradleProject gradleProject; + @Override public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { + Optional<GradleProject> maybeGp = cu.getMarkers().findFirst(GradleProject.class); + if (!maybeGp.isPresent()) { + return cu; + } + + gradleProject = maybeGp.get(); + G.CompilationUnit g = super.visitCompilationUnit(cu, ctx); if (g != cu) { - GradleProject gp = g.getMarkers().findFirst(GradleProject.class) - .orElseThrow(() -> new IllegalArgumentException("Gradle files are expected to have a GradleProject marker.")); - g = g.withMarkers(g.getMarkers().setByType(updateGradleModel(gp))); + g = g.withMarkers(g.getMarkers().setByType(updateGradleModel(gradleProject))); } return g; } @@ -119,17 +133,17 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu List<Expression> depArgs = m.getArguments(); if (depArgs.get(0) instanceof J.Literal || depArgs.get(0) instanceof G.GString || depArgs.get(0) instanceof G.MapEntry) { - m = updateDependency(m); + m = updateDependency(m, ctx); } else if (depArgs.get(0) instanceof J.MethodInvocation && (((J.MethodInvocation) depArgs.get(0)).getSimpleName().equals("platform") || ((J.MethodInvocation) depArgs.get(0)).getSimpleName().equals("enforcedPlatform"))) { - m = m.withArguments(ListUtils.mapFirst(depArgs, platform -> updateDependency((J.MethodInvocation) platform))); + m = m.withArguments(ListUtils.mapFirst(depArgs, platform -> updateDependency((J.MethodInvocation) platform, ctx))); } return m; } - private J.MethodInvocation updateDependency(J.MethodInvocation m) { + private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionContext ctx) { List<Expression> depArgs = m.getArguments(); if (depArgs.get(0) instanceof J.Literal) { String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); @@ -137,14 +151,26 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { Dependency original = DependencyStringNotationConverter.parse(gav); if (depMatcher.matches(original.getGroupId(), original.getArtifactId())) { Dependency updated = original; - if (newGroupId != null && !updated.getGroupId().equals(newGroupId)) { + if (!StringUtils.isBlank(newGroupId) && !updated.getGroupId().equals(newGroupId)) { updated = updated.withGroupId(newGroupId); } - if (newArtifactId != null && !updated.getArtifactId().equals(newArtifactId)) { + if (!StringUtils.isBlank(newArtifactId) && !updated.getArtifactId().equals(newArtifactId)) { updated = updated.withArtifactId(newArtifactId); } - if (newVersion != null) { - doAfterVisit(new UpgradeDependencyVersion(updated.getGroupId(), updated.getArtifactId(), newVersion, versionPattern).getVisitor()); + if (!StringUtils.isBlank(newVersion)) { + List<MavenRepository> repositories = "classpath".equals(m.getSimpleName()) ? + gradleProject.getMavenPluginRepositories() : + gradleProject.getMavenRepositories(); + String resolvedVersion; + try { + resolvedVersion = AddDependencyVisitor.resolveDependencyVersion(updated.getGroupId(), updated.getArtifactId(), "0", newVersion, versionPattern, repositories, null, ctx) + .orElse(null); + } catch (MavenDownloadingException e) { + return e.warn(m); + } + if (resolvedVersion != null && !resolvedVersion.equals(updated.getVersion())) { + updated = updated.withVersion(resolvedVersion); + } } if (original != updated) { String replacement = updated.toStringNotation(); @@ -159,14 +185,26 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { Dependency original = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); if (depMatcher.matches(original.getGroupId(), original.getArtifactId())) { Dependency updated = original; - if (newGroupId != null && !updated.getGroupId().equals(newGroupId)) { + if (!StringUtils.isBlank(newGroupId) && !updated.getGroupId().equals(newGroupId)) { updated = updated.withGroupId(newGroupId); } - if (newArtifactId != null && !updated.getArtifactId().equals(newArtifactId)) { + if (!StringUtils.isBlank(newArtifactId) && !updated.getArtifactId().equals(newArtifactId)) { updated = updated.withArtifactId(newArtifactId); } - if (newVersion != null) { - doAfterVisit(new UpgradeDependencyVersion(updated.getGroupId(), updated.getArtifactId(), newVersion, versionPattern).getVisitor()); + if (!StringUtils.isBlank(newVersion)) { + List<MavenRepository> repositories = "classpath".equals(m.getSimpleName()) ? + gradleProject.getMavenPluginRepositories() : + gradleProject.getMavenRepositories(); + String resolvedVersion; + try { + resolvedVersion = AddDependencyVisitor.resolveDependencyVersion(updated.getGroupId(), updated.getArtifactId(), "0", newVersion, versionPattern, repositories, null, ctx) + .orElse(null); + } catch (MavenDownloadingException e) { + return e.warn(m); + } + if (resolvedVersion != null && !resolvedVersion.equals(updated.getVersion())) { + updated = updated.withVersion(resolvedVersion); + } } if (original != updated) { String replacement = updated.toStringNotation(); @@ -180,10 +218,11 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { } else if (m.getArguments().get(0) instanceof G.MapEntry) { G.MapEntry groupEntry = null; G.MapEntry artifactEntry = null; + G.MapEntry versionEntry = null; String groupId = null; String artifactId = null; + String version = null; - String valueDelimiter = "'"; for (Expression e : depArgs) { if (!(e instanceof G.MapEntry)) { continue; @@ -200,48 +239,63 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m) { String keyValue = (String) key.getValue(); String valueValue = (String) value.getValue(); if ("group".equals(keyValue)) { - if (value.getValueSource() != null) { - valueDelimiter = value.getValueSource().substring(0, value.getValueSource().indexOf(valueValue)); - } groupEntry = arg; groupId = valueValue; } else if ("name".equals(keyValue)) { artifactEntry = arg; artifactId = valueValue; + } else if ("version".equals(keyValue)) { + versionEntry = arg; + version = valueValue; } } - if (groupId == null || artifactId == null) { + if (groupId == null || artifactId == null || version == null) { return m; } if (!depMatcher.matches(groupId, artifactId)) { return m; } String updatedGroupId = groupId; - if (newGroupId != null && !updatedGroupId.equals(newGroupId)) { + if (!StringUtils.isBlank(newGroupId) && !updatedGroupId.equals(newGroupId)) { updatedGroupId = newGroupId; } String updatedArtifactId = artifactId; - if (newArtifactId != null && !updatedArtifactId.equals(newArtifactId)) { + if (!StringUtils.isBlank(newArtifactId) && !updatedArtifactId.equals(newArtifactId)) { updatedArtifactId = newArtifactId; } - if (newVersion != null) { - doAfterVisit(new UpgradeDependencyVersion(updatedGroupId, updatedArtifactId, newVersion, versionPattern).getVisitor()); + String updatedVersion = version; + if (!StringUtils.isBlank(newVersion)) { + List<MavenRepository> repositories = "classpath".equals(m.getSimpleName()) ? + gradleProject.getMavenPluginRepositories() : + gradleProject.getMavenRepositories(); + String resolvedVersion; + try { + resolvedVersion = AddDependencyVisitor.resolveDependencyVersion(updatedGroupId, updatedArtifactId, "0", newVersion, versionPattern, repositories, null, ctx) + .orElse(null); + } catch (MavenDownloadingException e) { + return e.warn(m); + } + if (resolvedVersion != null && !resolvedVersion.equals(updatedVersion)) { + updatedVersion = resolvedVersion; + } } - if (!updatedGroupId.equals(groupId) || !updatedArtifactId.equals(artifactId)) { - String delimiter = valueDelimiter; + if (!updatedGroupId.equals(groupId) || !updatedArtifactId.equals(artifactId) || !updatedVersion.equals(version)) { G.MapEntry finalGroup = groupEntry; + String finalGroupIdValue = updatedGroupId; G.MapEntry finalArtifact = artifactEntry; + String finalArtifactIdValue = updatedArtifactId; + G.MapEntry finalVersion = versionEntry; + String finalVersionValue = updatedVersion; m = m.withArguments(ListUtils.map(m.getArguments(), arg -> { if (arg == finalGroup) { - return finalGroup.withValue(((J.Literal) finalGroup.getValue()) - .withValue(newGroupId) - .withValueSource(delimiter + newGroupId + delimiter)); + return finalGroup.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalGroup.getValue(), finalGroupIdValue)); } if (arg == finalArtifact) { - return finalArtifact.withValue(((J.Literal) finalArtifact.getValue()) - .withValue(newArtifactId) - .withValueSource(delimiter + newArtifactId + delimiter)); + return finalArtifact.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalArtifact.getValue(), finalArtifactIdValue)); + } + if (arg == finalVersion) { + return finalVersion.withValue(ChangeStringLiteral.withStringValue((J.Literal) finalVersion.getValue(), finalVersionValue)); } return arg; })); diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java index 94b7e333919..01f88f9dd6f 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java @@ -46,6 +46,7 @@ void relocateDependency() { dependencies { implementation "commons-lang:commons-lang:2.6" + implementation group: "commons-lang", name: "commons-lang", version: "2.6" } """, """ @@ -59,6 +60,7 @@ void relocateDependency() { dependencies { implementation "org.apache.commons:commons-lang3:3.11" + implementation group: "org.apache.commons", name: "commons-lang3", version: "3.11" } """ ) @@ -81,6 +83,7 @@ void changeGroupIdOnly() { dependencies { implementation "commons-lang:commons-lang:2.6" + implementation group: "commons-lang", name: "commons-lang", version: "2.6" } """, """ @@ -94,6 +97,7 @@ void changeGroupIdOnly() { dependencies { implementation "org.apache.commons:commons-lang:2.6" + implementation group: "org.apache.commons", name: "commons-lang", version: "2.6" } """ ) @@ -116,6 +120,7 @@ void changeArtifactIdOnly() { dependencies { implementation "commons-lang:commons-lang:2.6" + implementation group: "commons-lang", name: "commons-lang", version: "2.6" } """, """ @@ -129,6 +134,7 @@ void changeArtifactIdOnly() { dependencies { implementation "commons-lang:commons-lang3:2.6" + implementation group: "commons-lang", name: "commons-lang3", version: "2.6" } """ ) @@ -136,7 +142,7 @@ void changeArtifactIdOnly() { } @Test - void worksWithMapNotation() { + void worksWithPlatform() { rewriteRun( spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null)), buildGradle( @@ -150,7 +156,7 @@ void worksWithMapNotation() { } dependencies { - implementation group: "commons-lang", name: "commons-lang", version: "2.6" + implementation platform("commons-lang:commons-lang:2.6") } """, """ @@ -163,7 +169,7 @@ void worksWithMapNotation() { } dependencies { - implementation group: "org.apache.commons", name: "commons-lang3", version: "3.11" + implementation platform("org.apache.commons:commons-lang3:3.11") } """ ) @@ -171,34 +177,30 @@ void worksWithMapNotation() { } @Test - void worksWithPlatform() { + void changeDependencyWithLowerVersionAfter() { rewriteRun( - spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null)), + spec -> spec.recipe(new ChangeDependency("org.openrewrite", "plugin", "io.moderne", "moderne-gradle-plugin", "0.x", null)), buildGradle( """ - plugins { - id "java-library" - } - - repositories { - mavenCentral() - } - - dependencies { - implementation platform("commons-lang:commons-lang:2.6") + buildscript { + repositories { + gradlePluginPortal() + } + dependencies { + classpath "org.openrewrite:plugin:6.0.0" + classpath group: "org.openrewrite", name: "plugin", version: "6.0.0" + } } """, """ - plugins { - id "java-library" - } - - repositories { - mavenCentral() - } - - dependencies { - implementation platform("org.apache.commons:commons-lang3:3.11") + buildscript { + repositories { + gradlePluginPortal() + } + dependencies { + classpath "io.moderne:moderne-gradle-plugin:0.39.0" + classpath group: "io.moderne", name: "moderne-gradle-plugin", version: "0.39.0" + } } """ ) From 5131d10ae151181ca1f60b5428d8f767deebf486 Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Wed, 19 Jul 2023 22:28:31 -0500 Subject: [PATCH 080/447] Add ability to change a Gradle plugin --- .../gradle/plugins/ChangePlugin.java | 250 ++++++++++++++++++ .../gradle/plugins/ChangePluginTest.java | 128 +++++++++ 2 files changed, 378 insertions(+) create mode 100644 rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/ChangePlugin.java create mode 100644 rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/ChangePluginTest.java diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/ChangePlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/ChangePlugin.java new file mode 100644 index 00000000000..47ece8117b3 --- /dev/null +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/ChangePlugin.java @@ -0,0 +1,250 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.gradle.plugins; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.gradle.IsBuildGradle; +import org.openrewrite.gradle.IsSettingsGradle; +import org.openrewrite.gradle.marker.GradlePluginDescriptor; +import org.openrewrite.gradle.marker.GradleProject; +import org.openrewrite.gradle.marker.GradleSettings; +import org.openrewrite.gradle.util.ChangeStringLiteral; +import org.openrewrite.groovy.GroovyIsoVisitor; +import org.openrewrite.groovy.tree.G; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.maven.MavenDownloadingException; +import org.openrewrite.maven.tree.MavenRepository; +import org.openrewrite.semver.Semver; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +/** + * When changing a plugin id that uses the `apply` syntax or versionless plugins syntax, the version is will not be changed. + * At the time of this writing, we do not have a relationship between the plugin id and the jar that contains it that is + * required in order to update the version for the apply syntax. For the versionless plugins syntax, the version for a + * third party plugin must be defined in another file that is presently outside the scope of change for this recipe. + * If you are using either of these plugin styles, you should ensure that the plugin's version is appropriately updated. + */ +@Value +@EqualsAndHashCode(callSuper = true) +public class ChangePlugin extends Recipe { + @Option(displayName = "Plugin ID", + description = "The current Gradle plugin id.", + example = "org.openrewrite.rewrite") + String pluginId; + + @Option(displayName = "New Plugin ID", + description = "The new Gradle plugin id.", + example = "org.openrewrite.rewrite") + String newPluginId; + + @Option(displayName = "New Plugin Version", + description = "An exact version number or node-style semver selector used to select the version number. " + + "You can also use `latest.release` for the latest available version and `latest.patch` if " + + "the current version is a valid semantic version. For more details, you can look at the documentation " + + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors).", + example = "7.x", + required = false) + @Nullable + String newVersion; + + @Override + public String getDisplayName() { + return "Change a Gradle plugin"; + } + + @Override + public String getDescription() { + return "Changes the selected Gradle plugin to the new plugin."; + } + + @Override + public Validated<Object> validate() { + Validated<Object> validated = super.validate(); + if (newVersion != null) { + validated = validated.and(Semver.validate(newVersion, null)); + } + return validated; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + MethodMatcher pluginMatcher = new MethodMatcher("PluginSpec id(..)"); + MethodMatcher versionMatcher = new MethodMatcher("Plugin version(..)"); + MethodMatcher applyMatcher = new MethodMatcher("RewriteGradleProject apply(..)"); + return Preconditions.check( + Preconditions.or(new IsBuildGradle<>(), new IsSettingsGradle<>()), + new GroovyIsoVisitor<ExecutionContext>() { + GradleProject gradleProject; + GradleSettings gradleSettings; + + @Override + public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext executionContext) { + Optional<GradleProject> maybeGp = cu.getMarkers().findFirst(GradleProject.class); + Optional<GradleSettings> maybeGs = cu.getMarkers().findFirst(GradleSettings.class); + if (!maybeGp.isPresent() && !maybeGs.isPresent()) { + return cu; + } + + gradleProject = maybeGp.orElse(null); + gradleSettings = maybeGs.orElse(null); + + G.CompilationUnit g = super.visitCompilationUnit(cu, executionContext); + if (g != cu) { + if (gradleProject != null) { + GradleProject updatedGp = gradleProject.withPlugins(ListUtils.map(gradleProject.getPlugins(), plugin -> { + if (pluginId.equals(plugin.getId())) { + return new GradlePluginDescriptor("unknown", newPluginId); + } + return plugin; + })); + g = g.withMarkers(g.getMarkers().setByType(updatedGp)); + } else { + GradleSettings updatedGs = gradleSettings.withPlugins(ListUtils.map(gradleSettings.getPlugins(), plugin -> { + if (pluginId.equals(plugin.getId())) { + return new GradlePluginDescriptor("unknown", newPluginId); + } + return plugin; + })); + g = g.withMarkers(g.getMarkers().setByType(updatedGs)); + } + } + return g; + } + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + if (versionMatcher.matches(m) && + m.getSelect() instanceof J.MethodInvocation && + pluginMatcher.matches(m.getSelect())) { + m = maybeUpdateVersion(m, ctx); + } else if (pluginMatcher.matches(m)) { + m = maybeUpdatePluginSyntax(m); + } else if (applyMatcher.matches(m)) { + m = maybeUpdateApplySyntax(m); + } + return m; + } + + private J.MethodInvocation maybeUpdateVersion(J.MethodInvocation m, ExecutionContext ctx) { + if (!newPluginId.equals(((J.Literal) requireNonNull((J.MethodInvocation) m.getSelect()).getArguments().get(0)).getValue())) { + return m; + } + + List<Expression> args = m.getArguments(); + if (!(args.get(0) instanceof J.Literal)) { + return m; + } + + J.Literal versionLiteral = (J.Literal) args.get(0); + if (versionLiteral.getType() != JavaType.Primitive.String) { + return m; + } + + if (!StringUtils.isBlank(newVersion)) { + try { + String resolvedVersion = AddPluginVisitor.resolvePluginVersion(newPluginId, "0", newVersion, null, getPluginRepositories(), ctx) + .orElse(null); + if (resolvedVersion == null) { + return m; + } + + m = m.withArguments(Collections.singletonList(ChangeStringLiteral.withStringValue(versionLiteral, resolvedVersion))); + } catch (MavenDownloadingException e) { + return e.warn(m); + } + } + + return m; + } + + private J.MethodInvocation maybeUpdatePluginSyntax(J.MethodInvocation m) { + List<Expression> args = m.getArguments(); + if (!(args.get(0) instanceof J.Literal)) { + return m; + } + + J.Literal pluginIdLiteral = (J.Literal) args.get(0); + if (pluginIdLiteral.getType() != JavaType.Primitive.String) { + return m; + } + + String pluginIdValue = (String) pluginIdLiteral.getValue(); + if (!pluginId.equals(pluginIdValue)) { + return m; + } + + return m.withArguments(ListUtils.concat(ChangeStringLiteral.withStringValue(pluginIdLiteral, newPluginId), args.subList(1, args.size()))); + } + + private J.MethodInvocation maybeUpdateApplySyntax(J.MethodInvocation m) { + List<Expression> args = m.getArguments(); + if (!(args.get(0) instanceof G.MapEntry)) { + return m; + } + + G.MapEntry entry = (G.MapEntry) args.get(0); + if (!(entry.getKey() instanceof J.Literal) && !(entry.getValue() instanceof J.Literal)) { + return m; + } + + J.Literal keyLiteral = (J.Literal) entry.getKey(); + if (keyLiteral.getType() != JavaType.Primitive.String) { + return m; + } + + String keyValue = (String) keyLiteral.getValue(); + if (!"plugin".equals(keyValue)) { + return m; + } + + J.Literal valueLiteral = (J.Literal) entry.getValue(); + if (valueLiteral.getType() != JavaType.Primitive.String) { + return m; + } + + String valueValue = (String) valueLiteral.getValue(); + if (!pluginId.equals(valueValue)) { + return m; + } + + entry = entry.withValue(ChangeStringLiteral.withStringValue(valueLiteral, newPluginId)); + return m.withArguments(ListUtils.concat(entry, args.subList(1, args.size()))); + } + + private List<MavenRepository> getPluginRepositories() { + if (gradleProject != null) { + return gradleProject.getMavenPluginRepositories(); + } + return gradleSettings.getPluginRepositories(); + } + } + ); + } +} diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/ChangePluginTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/ChangePluginTest.java new file mode 100644 index 00000000000..25903083eb7 --- /dev/null +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/ChangePluginTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.gradle.plugins; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.gradle.ChangeDependency; +import org.openrewrite.gradle.marker.GradleProject; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.gradle.Assertions.*; + +class ChangePluginTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.beforeRecipe(withToolingApi()) + .recipe(new ChangePlugin("org.openrewrite.rewrite", "io.moderne.rewrite", "0.x")); + } + + @DocumentExample + @Test + void changePlugin() { + rewriteRun( + buildGradle( + """ + plugins { + id "org.openrewrite.rewrite" version "6.0.0" + } + """, + """ + plugins { + id "io.moderne.rewrite" version "0.39.0" + } + """, + spec -> spec.afterRecipe(g -> { + Optional<GradleProject> maybeGp = g.getMarkers().findFirst(GradleProject.class); + assertThat(maybeGp).isPresent() + .hasValueSatisfying(gp -> assertThat(gp.getPlugins()).filteredOn(plugin -> "org.openrewrite.rewrite".equals(plugin.getId())).isEmpty()) + .hasValueSatisfying(gp -> assertThat(gp.getPlugins()).filteredOn(plugin -> "io.moderne.rewrite".equals(plugin.getId())).hasSize(1)); + }) + ) + ); + } + + @Test + void changeApplyPluginSyntax() { + rewriteRun( + spec -> spec.recipes( + new ChangeDependency("org.openrewrite", "plugin", "io.moderne", "moderne-gradle-plugin", "0.x", null), + new ChangePlugin("org.openrewrite.rewrite", "io.moderne.rewrite", null) + ), + buildGradle( + """ + buildscript { + repositories { + gradlePluginPortal() + } + dependencies { + classpath "org.openrewrite:plugin:6.0.0" + } + } + apply plugin: "org.openrewrite.rewrite" + """, + """ + buildscript { + repositories { + gradlePluginPortal() + } + dependencies { + classpath "io.moderne:moderne-gradle-plugin:0.39.0" + } + } + apply plugin: "io.moderne.rewrite" + """ + ) + ); + } + + @Test + void defaultsToLatestRelease() { + rewriteRun( + spec -> spec.recipe(new ChangePlugin("org.openrewrite.rewrite", "io.moderne.rewrite", null)), + buildGradle( + """ + plugins { + id 'org.openrewrite.rewrite' version '6.0.0' + } + """, + spec -> spec.after(after -> { + Matcher versionMatcher = Pattern.compile("id 'io\\.moderne\\.rewrite' version '(.*?)'").matcher(after); + assertThat(versionMatcher.find()).isTrue(); + String version = versionMatcher.group(1); + VersionComparator versionComparator = requireNonNull(Semver.validate("[1.0.34,)", null).getValue()); + assertThat(versionComparator.compare(null, "1.0.34", version)).isLessThanOrEqualTo(0); + + //language=gradle + return """ + plugins { + id 'io.moderne.rewrite' version '%s' + } + """.formatted(version); + }) + ) + ); + } +} From caa2fc791b6be8bd5b097d9e60e4e0c50c95419b Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Thu, 20 Jul 2023 16:29:25 +0200 Subject: [PATCH 081/447] Turn YAML files with unicode into ParseErrors (#3427) --- .../yaml/FormatPreservingReader.java | 4 +-- .../org/openrewrite/yaml/YamlParserTest.java | 32 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java index e974792a8c2..3d1dd62b121 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java @@ -78,8 +78,8 @@ public int read(@NonNull char[] cbuf, int off, int len) throws IOException { buffer.ensureCapacity(buffer.size() + read); for (int i = 0; i < read; i++) { char e = cbuf[i]; - if (Character.UnicodeBlock.of(e) != Character.UnicodeBlock.BASIC_LATIN && i % 2 == 0) { - bufferIndex--; + if (Character.UnicodeBlock.of(e) != Character.UnicodeBlock.BASIC_LATIN) { + throw new IllegalArgumentException("Only ASCII characters are supported for now"); } buffer.add(e); } diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java index bcdf07ae339..00f2fa0995d 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java @@ -15,31 +15,26 @@ */ package org.openrewrite.yaml; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.openrewrite.SourceFile; import org.openrewrite.tree.ParseError; import org.openrewrite.yaml.tree.Yaml; +import java.util.List; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; class YamlParserTest { - @ParameterizedTest - @ValueSource(strings = { - "b", - "🛠", - "🛠🛠", - "🛠 🛠" - }) - void parseYamlWithUnicode(String input) { - Stream<SourceFile> yamlSources = YamlParser.builder().build().parse("a: %s\n".formatted(input)); - SourceFile sourceFile = yamlSources.findFirst().get(); - assertThat(sourceFile).isNotInstanceOf(ParseError.class); + @Test + void ascii() { + List<SourceFile> yamlSources = YamlParser.builder().build().parse("a: b\n").toList(); + assertThat(yamlSources).singleElement().isInstanceOf(Yaml.Documents.class); - Yaml.Documents documents = (Yaml.Documents) sourceFile; + Yaml.Documents documents = (Yaml.Documents) yamlSources.get(0); Yaml.Document document = documents.getDocuments().get(0); // Assert that end is parsed correctly @@ -49,7 +44,18 @@ void parseYamlWithUnicode(String input) { Yaml.Mapping mapping = (Yaml.Mapping) document.getBlock(); Yaml.Mapping.Entry entry = mapping.getEntries().get(0); Yaml.Scalar title = (Yaml.Scalar) entry.getValue(); - assertThat(title.getValue()).isEqualTo(input); + assertThat(title.getValue()).isEqualTo("b"); + } + + @ParameterizedTest + @ValueSource(strings = { + "🛠", + "🛠🛠", + "🛠 🛠" + }) + void unicodeParseError(String input ) { + Stream<SourceFile> yamlSources = YamlParser.builder().build().parse("a: %s\n".formatted(input)); + assertThat(yamlSources).singleElement().isInstanceOf(ParseError.class); } } From 387a405814ae0868d8ff8c9d21510e7f3d88b22a Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Thu, 20 Jul 2023 11:18:52 -0700 Subject: [PATCH 082/447] Remove 3 recipes (SimplifyBooleanReturn, SimplifyBooleanExpression, and UnnecessaryParentheses) and move them to rewrite-static-analysis --- .../SimplifyBooleanExpressionTest.java | 3 +- .../cleanup/SimplifyBooleanReturnTest.java | 214 ----------------- .../cleanup/UnnecessaryParenthesesTest.java | 4 +- .../recipes/MigrateRecipeToRewrite8Test.java | 6 +- .../recipes/SelectRecipeExamplesTest.java | 36 ++- .../org/openrewrite/java/InvertCondition.java | 4 +- .../openrewrite/java/RemoveObjectsIsNull.java | 4 +- ... => SimplifyBooleanExpressionVisitor.java} | 41 +--- .../java/cleanup/SimplifyBooleanReturn.java | 195 --------------- .../java/cleanup/UnnecessaryParentheses.java | 227 ------------------ .../UnnecessaryParenthesesVisitor.java | 199 +++++++++++++++ 11 files changed, 229 insertions(+), 704 deletions(-) delete mode 100644 rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanReturnTest.java rename rewrite-java/src/main/java/org/openrewrite/java/cleanup/{SimplifyBooleanExpression.java => SimplifyBooleanExpressionVisitor.java} (89%) delete mode 100644 rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanReturn.java delete mode 100644 rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParentheses.java create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionTest.java index 527602282a7..f0f0941d0fb 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionTest.java @@ -22,13 +22,14 @@ import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.RewriteTest.toRecipe; @SuppressWarnings("ALL") class SimplifyBooleanExpressionTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new SimplifyBooleanExpression()); + spec.recipe(toRecipe(() -> new SimplifyBooleanExpressionVisitor())); } @DocumentExample diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanReturnTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanReturnTest.java deleted file mode 100644 index c44cecd0e8e..00000000000 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanReturnTest.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2020 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.cleanup; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.java.Assertions.java; - -@SuppressWarnings("ALL") -class SimplifyBooleanReturnTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new SimplifyBooleanReturn()); - } - - @DocumentExample - @Test - void simplifyBooleanReturn() { - rewriteRun( - java( - """ - public class A { - boolean ifNoElse() { - if (isOddMillis()) { - return true; - } - return false; - } - - static boolean isOddMillis() { - boolean even = System.currentTimeMillis() % 2 == 0; - if (even == true) { - return false; - } - else { - return true; - } - } - } - """, - """ - public class A { - boolean ifNoElse() { - return isOddMillis(); - } - - static boolean isOddMillis() { - boolean even = System.currentTimeMillis() % 2 == 0; - return !(even == true); - } - } - """ - ) - ); - } - - @Test - void dontSimplifyToReturnUnlessLastStatement() { - rewriteRun( - java( - """ - public class A { - public boolean absurdEquals(Object o) { - if(this == o) { - return true; - } - if(this == o) { - return true; - } - return false; - } - } - """, - """ - public class A { - public boolean absurdEquals(Object o) { - if(this == o) { - return true; - } - return this == o; - } - } - """ - ) - ); - } - - @Test - void nestedIfsWithNoBlock() { - rewriteRun( - java( - """ - public class A { - public boolean absurdEquals(Object o) { - if(this == o) - if(this == null) - return true; - return false; - } - } - """ - ) - ); - } - - @Test - void dontAlterWhenElseIfPresent() { - rewriteRun( - java( - """ - public class A { - public boolean foo(int n) { - if (n == 1) { - return false; - } - else if (n == 2) { - return true; - } - else { - return false; - } - } - } - """ - ) - ); - } - - @Test - void dontAlterWhenElseContainsSomethingOtherThanReturn() { - rewriteRun( - java( - """ - public class A { - public boolean foo(int n) { - if (n == 1) { - return true; - } - else { - System.out.println("side effect"); - return false; - } - } - } - """ - ) - ); - } - - @Test - void onlySimplifyToReturnWhenLastStatement() { - rewriteRun( - java( - """ - import java.util.*; - public class A { - public static boolean deepEquals(List<byte[]> l, List<byte[]> r) { - for (int i = 0; i < l.size(); ++i) { - if (!Arrays.equals(l.get(i), r.get(i))) { - return false; - } - } - return true; - } - } - """ - ) - ); - } - - @Test - void wrapNotReturnsOfTernaryIfConditionsInParentheses() { - rewriteRun( - java( - """ - public class A { - Object failure; - public boolean equals(Object o) { - if (failure != null ? !failure.equals(this.failure) : this.failure != null) { - return false; - } - return true; - } - } - """, - """ - public class A { - Object failure; - public boolean equals(Object o) { - return !(failure != null ? !failure.equals(this.failure) : this.failure != null); - } - } - """ - ) - ); - } -} diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java index 24c7ad077e4..e1e40f6c567 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java @@ -32,6 +32,7 @@ import static java.util.Collections.emptySet; import static java.util.Collections.singletonList; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.RewriteTest.toRecipe; @SuppressWarnings({ "UnnecessaryLocalVariable", "ConstantConditions", "UnusedAssignment", "PointlessBooleanExpression", @@ -40,7 +41,7 @@ class UnnecessaryParenthesesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new UnnecessaryParentheses()); + spec.recipe(toRecipe(UnnecessaryParenthesesVisitor::new)); } private static Consumer<RecipeSpec> unnecessaryParentheses(UnaryOperator<UnnecessaryParenthesesStyle> with) { @@ -819,6 +820,7 @@ void test(String s) { ); } + @SuppressWarnings("all") @Test void unwrapWhileParens() { rewriteRun( diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateRecipeToRewrite8Test.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateRecipeToRewrite8Test.java index 6402a225646..6436a7a5fd1 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateRecipeToRewrite8Test.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateRecipeToRewrite8Test.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java.recipes; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.java.JavaParser; @@ -648,6 +649,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { ); } + @Disabled @Test void doAfterVisitRecipeIsRemoved() { // language=java @@ -686,7 +688,7 @@ protected JavaIsoVisitor<ExecutionContext> getVisitor() { @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - doAfterVisit(new SimplifyBooleanExpression()); + doAfterVisit(new ChangePackage("A", "B", true)); return m; } }; @@ -720,7 +722,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = super.visitMethodInvocation(method, ctx); // [Rewrite8 migration] TreeVisitor#doAfterVisit(Recipe) has been removed, it could be mistaken usage of `TreeVisitor#doAfterVisit(TreeVisitor<?, P> visitor)` here, please review code and see if it can be replaced. - doAfterVisit(new SimplifyBooleanExpression()); + doAfterVisit(new ChangePackage("A", "B", true)); return m; } }); diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/SelectRecipeExamplesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/SelectRecipeExamplesTest.java index d95d36267be..99889c1a12b 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/SelectRecipeExamplesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/SelectRecipeExamplesTest.java @@ -43,14 +43,13 @@ void selectFirstExample() { import org.openrewrite.Recipe; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; - + import static org.openrewrite.java.Assertions.java; - + class UnnecessaryParenthesesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - Recipe recipe = new UnnecessaryParentheses(); - spec.recipe(recipe); + spec.recipe(Recipe.noop()); } @Test @@ -90,14 +89,13 @@ void test2() { import org.openrewrite.Recipe; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; - + import static org.openrewrite.java.Assertions.java; - + class UnnecessaryParenthesesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - Recipe recipe = new UnnecessaryParentheses(); - spec.recipe(recipe); + spec.recipe(Recipe.noop()); } @DocumentExample @@ -145,14 +143,13 @@ void skipNotChangedTest() { import org.openrewrite.Recipe; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; - + import static org.openrewrite.java.Assertions.java; - + class UnnecessaryParenthesesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - Recipe recipe = new UnnecessaryParentheses(); - spec.recipe(recipe); + spec.recipe(Recipe.noop()); } @Test @@ -195,8 +192,7 @@ void test2() { class UnnecessaryParenthesesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - Recipe recipe = new UnnecessaryParentheses(); - spec.recipe(recipe); + spec.recipe(Recipe.noop()); } @Test @@ -248,8 +244,7 @@ void skipIssueAnnotatedTests() { class UnnecessaryParenthesesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - Recipe recipe = new UnnecessaryParentheses(); - spec.recipe(recipe); + spec.recipe(Recipe.noop()); } @Issue("https://github.com/openrewrite/rewrite/issues/x") @@ -297,8 +292,7 @@ void test2() { class UnnecessaryParenthesesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - Recipe recipe = new UnnecessaryParentheses(); - spec.recipe(recipe); + spec.recipe(Recipe.noop()); } @Issue("https://github.com/openrewrite/rewrite/issues/x") @@ -355,8 +349,7 @@ void skipDisabledAnnotatedTests() { class UnnecessaryParenthesesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - Recipe recipe = new UnnecessaryParentheses(); - spec.recipe(recipe); + spec.recipe(Recipe.noop()); } @Disabled("some reason") @@ -397,8 +390,7 @@ void ignoreIfHasAnnotated() { class UnnecessaryParenthesesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - Recipe recipe = new UnnecessaryParentheses(); - spec.recipe(recipe); + spec.recipe(Recipe.noop()); } @DocumentExample diff --git a/rewrite-java/src/main/java/org/openrewrite/java/InvertCondition.java b/rewrite-java/src/main/java/org/openrewrite/java/InvertCondition.java index d48e7232d4e..7df063a649b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/InvertCondition.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/InvertCondition.java @@ -20,7 +20,7 @@ import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Tree; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.cleanup.SimplifyBooleanExpression; +import org.openrewrite.java.cleanup.SimplifyBooleanExpressionVisitor; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; @@ -47,7 +47,7 @@ public J visit(@Nullable Tree tree, ExecutionContext ctx) { t = super.visit(tree, ctx); } - return (J) new SimplifyBooleanExpression().getVisitor().visit(t, ctx, getCursor().getParentOrThrow()); + return new SimplifyBooleanExpressionVisitor().visit(t, ctx, getCursor().getParentOrThrow()); } @Override diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveObjectsIsNull.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveObjectsIsNull.java index 2529f3e6d73..2a55e364d40 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveObjectsIsNull.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveObjectsIsNull.java @@ -19,7 +19,7 @@ import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.java.cleanup.UnnecessaryParentheses; +import org.openrewrite.java.cleanup.UnnecessaryParenthesesVisitor; import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; @@ -56,7 +56,7 @@ public Expression visitMethodInvocation(J.MethodInvocation method, ExecutionCont private Expression replace(ExecutionContext executionContext, J.MethodInvocation m, String pattern) { Expression e = m.getArguments().get(0); Expression replaced = JavaTemplate.apply(pattern, getCursor(), m.getCoordinates().replace(), e); - return (Expression) new UnnecessaryParentheses().getVisitor() + return (Expression) new UnnecessaryParenthesesVisitor() .visitNonNull(replaced, executionContext, getCursor()); } }); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpression.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java similarity index 89% rename from rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpression.java rename to rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java index dc87c4991a9..81b200fa775 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpression.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java @@ -26,37 +26,9 @@ import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.java.tree.Space; -import java.time.Duration; -import java.util.Collections; -import java.util.Set; - import static java.util.Objects.requireNonNull; -public class SimplifyBooleanExpression extends Recipe { - - @Override - public String getDisplayName() { - return "Simplify boolean expression"; - } - - @Override - public String getDescription() { - return "Checks for over-complicated boolean expressions. Finds code like `if (b == true)`, `b || true`, `!false`, etc."; - } - - @Override - public Set<String> getTags() { - return Collections.singleton("RSPEC-1125"); - } - - @Override - public Duration getEstimatedEffortPerOccurrence() { - return Duration.ofMinutes(5); - } - - @Override - public TreeVisitor<?, ExecutionContext> getVisitor() { - return new JavaVisitor<ExecutionContext>() { +public class SimplifyBooleanExpressionVisitor extends JavaVisitor<ExecutionContext> { private static final String MAYBE_AUTO_FORMAT_ME = "MAYBE_AUTO_FORMAT_ME"; @Override @@ -65,7 +37,7 @@ public J visit(@Nullable Tree tree, ExecutionContext ctx) { JavaSourceFile cu = (JavaSourceFile) requireNonNull(super.visit(tree, ctx)); if (tree != cu) { // recursive simplification - cu = (JavaSourceFile) getVisitor().visitNonNull(cu, ctx); + cu = (JavaSourceFile) visitNonNull(cu, ctx); } return cu; } @@ -188,11 +160,4 @@ public Space visitSpace(Space space, Space.Location loc, Integer integer) { } }.visit(j, 0); } - }; - } - - @Override - public boolean causesAnotherCycle() { - return true; - } -} + } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanReturn.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanReturn.java deleted file mode 100644 index 2c57b5b117b..00000000000 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanReturn.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2020 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.cleanup; - -import org.openrewrite.Cursor; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; -import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.DeleteStatement; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.tree.Expression; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.Statement; - -import java.time.Duration; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - -public class SimplifyBooleanReturn extends Recipe { - - @Override - public String getDisplayName() { - return "Simplify boolean return"; - } - - @Override - public String getDescription() { - return "Simplifies Boolean expressions by removing redundancies, e.g.: `a && true` simplifies to `a`."; - } - - @Override - public Set<String> getTags() { - return Collections.singleton("RSPEC-1126"); - } - - @Override - public Duration getEstimatedEffortPerOccurrence() { - return Duration.ofMinutes(2); - } - - @Override - public TreeVisitor<?, ExecutionContext> getVisitor() { - return new JavaVisitor<ExecutionContext>() { - private final JavaTemplate notIfConditionReturn = JavaTemplate.builder("return !(#{any(boolean)});") - .build(); - - @Override - public J visitIf(J.If iff, ExecutionContext ctx) { - J.If i = visitAndCast(iff, ctx, super::visitIf); - - Cursor parent = getCursor().getParentTreeCursor(); - - if (parent.getValue() instanceof J.Block && - parent.getParentOrThrow().getValue() instanceof J.MethodDeclaration && - thenHasOnlyReturnStatement(iff) && - elseWithOnlyReturn(i)) { - List<Statement> followingStatements = followingStatements(); - Optional<Expression> singleFollowingStatement = Optional.ofNullable(followingStatements.isEmpty() ? null : followingStatements.get(0)) - .flatMap(stat -> Optional.ofNullable(stat instanceof J.Return ? (J.Return) stat : null)) - .map(J.Return::getExpression); - - if (followingStatements.isEmpty() || singleFollowingStatement.map(r -> isLiteralFalse(r) || isLiteralTrue(r)).orElse(false)) { - J.Return return_ = getReturnIfOnlyStatementInThen(iff).orElse(null); - assert return_ != null; - - Expression ifCondition = i.getIfCondition().getTree(); - - if (isLiteralTrue(return_.getExpression())) { - if (singleFollowingStatement.map(this::isLiteralFalse).orElse(false) && i.getElsePart() == null) { - doAfterVisit(new DeleteStatement<>(followingStatements().get(0))); - return maybeAutoFormat(return_, return_.withExpression(ifCondition), ctx, parent); - } else if (!singleFollowingStatement.isPresent() && - getReturnExprIfOnlyStatementInElseThen(i).map(this::isLiteralFalse).orElse(false)) { - if (i.getElsePart() != null) { - doAfterVisit(new DeleteStatement<>(i.getElsePart().getBody())); - } - return maybeAutoFormat(return_, return_.withExpression(ifCondition), ctx, parent); - } - } else if (isLiteralFalse(return_.getExpression())) { - boolean returnThenPart = false; - - if (singleFollowingStatement.map(this::isLiteralTrue).orElse(false) && i.getElsePart() == null) { - doAfterVisit(new DeleteStatement<>(followingStatements().get(0))); - returnThenPart = true; - } else if (!singleFollowingStatement.isPresent() && getReturnExprIfOnlyStatementInElseThen(i) - .map(this::isLiteralTrue).orElse(false)) { - if (i.getElsePart() != null) { - doAfterVisit(new DeleteStatement<>(i.getElsePart().getBody())); - } - returnThenPart = true; - } - - if (returnThenPart) { - // we need to NOT the expression inside the if condition - return notIfConditionReturn.apply(updateCursor(i), i.getCoordinates().replace(), ifCondition); - } - } - } - } - - return i; - } - - private boolean elseWithOnlyReturn(J.If i) { - return i.getElsePart() == null || !(i.getElsePart().getBody() instanceof J.If); - } - - private boolean thenHasOnlyReturnStatement(J.If iff) { - return getReturnIfOnlyStatementInThen(iff) - .map(return_ -> isLiteralFalse(return_.getExpression()) || isLiteralTrue(return_.getExpression())) - .orElse(false); - } - - private List<Statement> followingStatements() { - J.Block block = getCursor().getParentTreeCursor().getValue(); - AtomicBoolean dropWhile = new AtomicBoolean(false); - return block.getStatements().stream() - .filter(s -> { - dropWhile.set(dropWhile.get() || s == getCursor().getValue()); - return dropWhile.get(); - }) - .skip(1) - .collect(Collectors.toList()); - } - - private boolean isLiteralTrue(@Nullable J tree) { - return tree instanceof J.Literal && ((J.Literal) tree).getValue() == Boolean.valueOf(true); - } - - private boolean isLiteralFalse(@Nullable J tree) { - return tree instanceof J.Literal && ((J.Literal) tree).getValue() == Boolean.valueOf(false); - } - - private Optional<J.Return> getReturnIfOnlyStatementInThen(J.If iff) { - if (iff.getThenPart() instanceof J.Return) { - return Optional.of((J.Return) iff.getThenPart()); - } - if (iff.getThenPart() instanceof J.Block) { - J.Block then = (J.Block) iff.getThenPart(); - if (then.getStatements().size() == 1 && then.getStatements().get(0) instanceof J.Return) { - return Optional.of((J.Return) then.getStatements().get(0)); - } - } - return Optional.empty(); - } - - private Optional<Expression> getReturnExprIfOnlyStatementInElseThen(J.If iff2) { - if (iff2.getElsePart() == null) { - return Optional.empty(); - } - - Statement else_ = iff2.getElsePart().getBody(); - if (else_ instanceof J.Return) { - return Optional.ofNullable(((J.Return) else_).getExpression()); - } - - if (else_ instanceof J.Block) { - List<Statement> statements = ((J.Block) else_).getStatements(); - if (statements.size() == 1) { - J statement = statements.get(0); - if (statement instanceof J.Return) { - return Optional.ofNullable(((J.Return) statement).getExpression()); - } - } - } - - return Optional.empty(); - } - }; - } - - @Override - public boolean causesAnotherCycle() { - return true; - } -} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParentheses.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParentheses.java deleted file mode 100644 index 1ec364d2d16..00000000000 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParentheses.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2020 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.cleanup; - -import org.openrewrite.*; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.UnwrapParentheses; -import org.openrewrite.java.style.Checkstyle; -import org.openrewrite.java.style.UnnecessaryParenthesesStyle; -import org.openrewrite.java.tree.*; - -import java.time.Duration; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; - -public class UnnecessaryParentheses extends Recipe { - - @Override - public String getDisplayName() { - return "Remove unnecessary parentheses"; - } - - @Override - public String getDescription() { - return "Removes unnecessary parentheses from code where extra parentheses pairs are redundant."; - } - - @Override - public Set<String> getTags() { - return new LinkedHashSet<>(Arrays.asList("RSPEC-1110", "RSPEC-1611")); - } - - @Override - public Duration getEstimatedEffortPerOccurrence() { - return Duration.ofMinutes(1); - } - - @Override - public TreeVisitor<?, ExecutionContext> getVisitor() { - return new JavaVisitor<ExecutionContext>() { - - @Override - public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionContext) { - // Causes problems on other languages like JavaScript - return sourceFile instanceof J.CompilationUnit; - } - - private static final String UNNECESSARY_PARENTHESES_MESSAGE = "unnecessaryParenthesesUnwrapTarget"; - - transient UnnecessaryParenthesesStyle style; - - private UnnecessaryParenthesesStyle getStyle() { - if (style == null) { - JavaSourceFile cu = getCursor().firstEnclosingOrThrow(JavaSourceFile.class); - style = ((SourceFile) cu).getStyle(UnnecessaryParenthesesStyle.class, Checkstyle.unnecessaryParentheses()); - } - return style; - } - - @Override - public <T extends J> J visitParentheses(J.Parentheses<T> parens, ExecutionContext ctx) { - J par = super.visitParentheses(parens, ctx); - Cursor c = getCursor().pollNearestMessage(UNNECESSARY_PARENTHESES_MESSAGE); - if (c != null && (c.getValue() instanceof J.Literal || c.getValue() instanceof J.Identifier)) { - par = new UnwrapParentheses<>((J.Parentheses<?>) par).visit(par, ctx, getCursor().getParentOrThrow()); - } - - assert par != null; - if (par instanceof J.Parentheses) { - if (getCursor().getParentTreeCursor().getValue() instanceof J.Parentheses) { - return ((J.Parentheses<?>) par).getTree().withPrefix(Space.EMPTY); - } - } - return par; - } - - @Override - public J visitIdentifier(J.Identifier ident, ExecutionContext ctx) { - J.Identifier i = (J.Identifier) super.visitIdentifier(ident, ctx); - if (getStyle().getIdent() && getCursor().getParentTreeCursor().getValue() instanceof J.Parentheses) { - getCursor().putMessageOnFirstEnclosing(J.Parentheses.class, UNNECESSARY_PARENTHESES_MESSAGE, getCursor()); - } - return i; - } - - @Override - public J visitLiteral(J.Literal literal, ExecutionContext ctx) { - J.Literal l = (J.Literal) super.visitLiteral(literal, ctx); - JavaType.Primitive type = l.getType(); - if ((getStyle().getNumInt() && type == JavaType.Primitive.Int) || - (getStyle().getNumDouble() && type == JavaType.Primitive.Double) || - (getStyle().getNumLong() && type == JavaType.Primitive.Long) || - (getStyle().getNumFloat() && type == JavaType.Primitive.Float) || - (getStyle().getStringLiteral() && type == JavaType.Primitive.String) || - (getStyle().getLiteralNull() && type == JavaType.Primitive.Null) || - (getStyle().getLiteralFalse() && type == JavaType.Primitive.Boolean && l.getValue() == Boolean.valueOf(false)) || - (getStyle().getLiteralTrue() && type == JavaType.Primitive.Boolean && l.getValue() == Boolean.valueOf(true))) { - if (getCursor().getParentTreeCursor().getValue() instanceof J.Parentheses) { - getCursor().putMessageOnFirstEnclosing(J.Parentheses.class, UNNECESSARY_PARENTHESES_MESSAGE, getCursor()); - } - } - return l; - } - - @Override - public J visitAssignmentOperation(J.AssignmentOperation assignOp, ExecutionContext ctx) { - J.AssignmentOperation a = (J.AssignmentOperation) super.visitAssignmentOperation(assignOp, ctx); - J.AssignmentOperation.Type op = a.getOperator(); - if (a.getAssignment() instanceof J.Parentheses && ((getStyle().getBitAndAssign() && op == J.AssignmentOperation.Type.BitAnd) || - (getStyle().getBitOrAssign() && op == J.AssignmentOperation.Type.BitOr) || - (getStyle().getBitShiftRightAssign() && op == J.AssignmentOperation.Type.UnsignedRightShift) || - (getStyle().getBitXorAssign() && op == J.AssignmentOperation.Type.BitXor) || - (getStyle().getShiftRightAssign() && op == J.AssignmentOperation.Type.RightShift) || - (getStyle().getShiftLeftAssign() && op == J.AssignmentOperation.Type.LeftShift) || - (getStyle().getMinusAssign() && op == J.AssignmentOperation.Type.Subtraction) || - (getStyle().getDivAssign() && op == J.AssignmentOperation.Type.Division) || - (getStyle().getPlusAssign() && op == J.AssignmentOperation.Type.Addition) || - (getStyle().getStarAssign() && op == J.AssignmentOperation.Type.Multiplication) || - (getStyle().getModAssign() && op == J.AssignmentOperation.Type.Modulo))) { - a = (J.AssignmentOperation) new UnwrapParentheses<>((J.Parentheses<?>) a.getAssignment()).visitNonNull(a, ctx, getCursor().getParentOrThrow()); - } - return a; - } - - @Override - public J visitAssignment(J.Assignment assignment, ExecutionContext ctx) { - J.Assignment a = visitAndCast(assignment, ctx, super::visitAssignment); - if (getStyle().getAssign() && a.getAssignment() instanceof J.Parentheses) { - a = (J.Assignment) new UnwrapParentheses<>((J.Parentheses<?>) a.getAssignment()).visitNonNull(a, ctx, getCursor().getParentOrThrow()); - } - return a; - } - - @Override - public J visitReturn(J.Return return_, ExecutionContext ctx) { - J.Return rtn = (J.Return) super.visitReturn(return_, ctx); - if (getStyle().getExpr() && rtn.getExpression() instanceof J.Parentheses) { - rtn = (J.Return) new UnwrapParentheses<>((J.Parentheses<?>) rtn.getExpression()).visitNonNull(rtn, ctx, getCursor().getParentOrThrow()); - } - return rtn; - } - - @Override - public J visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext ctx) { - J.VariableDeclarations.NamedVariable v = (J.VariableDeclarations.NamedVariable) super.visitVariable(variable, ctx); - if (getStyle().getAssign() && v.getInitializer() != null && v.getInitializer() instanceof J.Parentheses) { - v = (J.VariableDeclarations.NamedVariable) new UnwrapParentheses<>((J.Parentheses<?>) v.getInitializer()).visitNonNull(v, ctx, getCursor().getParentOrThrow()); - } - return v; - } - - @Override - public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { - J.Lambda l = (J.Lambda) super.visitLambda(lambda, ctx); - if (l.getParameters().getParameters().size() == 1 && - l.getParameters().isParenthesized() && - l.getParameters().getParameters().get(0) instanceof J.VariableDeclarations && - ((J.VariableDeclarations) l.getParameters().getParameters().get(0)).getTypeExpression() == null) { - l = l.withParameters(l.getParameters().withParenthesized(false)); - } - return l; - } - - @Override - public J visitIf(J.If iff, ExecutionContext ctx) { - J.If i = (J.If) super.visitIf(iff, ctx); - // Unwrap when if condition is a single parenthesized expression - Expression expression = i.getIfCondition().getTree(); - if (expression instanceof J.Parentheses) { - i = (J.If) new UnwrapParentheses<>((J.Parentheses<?>) expression).visitNonNull(i, ctx, getCursor().getParentOrThrow()); - } - return i; - } - - @Override - public J visitWhileLoop(J.WhileLoop whileLoop, ExecutionContext ctx) { - J.WhileLoop w = (J.WhileLoop) super.visitWhileLoop(whileLoop, ctx); - // Unwrap when while condition is a single parenthesized expression - Expression expression = w.getCondition().getTree(); - if (expression instanceof J.Parentheses) { - w = (J.WhileLoop) new UnwrapParentheses<>((J.Parentheses<?>) expression).visitNonNull(w, ctx, getCursor().getParentOrThrow()); - } - return w; - } - - @Override - public J visitDoWhileLoop(J.DoWhileLoop doWhileLoop, ExecutionContext ctx) { - J.DoWhileLoop dw = (J.DoWhileLoop) super.visitDoWhileLoop(doWhileLoop, ctx); - // Unwrap when while condition is a single parenthesized expression - Expression expression = dw.getWhileCondition().getTree(); - if (expression instanceof J.Parentheses) { - dw = (J.DoWhileLoop) new UnwrapParentheses<>((J.Parentheses<?>) expression).visitNonNull(dw, ctx, getCursor().getParentOrThrow()); - } - return dw; - } - - @Override - public J visitForControl(J.ForLoop.Control control, ExecutionContext ctx) { - J.ForLoop.Control fc = (J.ForLoop.Control) super.visitForControl(control, ctx); - Expression condition = fc.getCondition(); - if (condition instanceof J.Parentheses) { - fc = (J.ForLoop.Control) new UnwrapParentheses<>((J.Parentheses<?>) condition).visitNonNull(fc, ctx, getCursor().getParentOrThrow()); - } - return fc; - } - }; - } - - @Override - public boolean causesAnotherCycle() { - return true; - } -} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java new file mode 100644 index 00000000000..9747bbdb5c6 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java @@ -0,0 +1,199 @@ +/* + * Copyright 2020 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.cleanup; + +import org.openrewrite.*; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.UnwrapParentheses; +import org.openrewrite.java.style.Checkstyle; +import org.openrewrite.java.style.UnnecessaryParenthesesStyle; +import org.openrewrite.java.tree.*; + +public class UnnecessaryParenthesesVisitor extends JavaVisitor<ExecutionContext> { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionContext) { + // Causes problems on other languages like JavaScript + return sourceFile instanceof J.CompilationUnit; + } + + private static final String UNNECESSARY_PARENTHESES_MESSAGE = "unnecessaryParenthesesUnwrapTarget"; + + transient UnnecessaryParenthesesStyle style; + + private UnnecessaryParenthesesStyle getStyle() { + if (style == null) { + JavaSourceFile cu = getCursor().firstEnclosingOrThrow(JavaSourceFile.class); + style = ((SourceFile) cu).getStyle(UnnecessaryParenthesesStyle.class, Checkstyle.unnecessaryParentheses()); + } + return style; + } + + @Override + public <T extends J> J visitParentheses(J.Parentheses<T> parens, ExecutionContext ctx) { + J par = super.visitParentheses(parens, ctx); + Cursor c = getCursor().pollNearestMessage(UNNECESSARY_PARENTHESES_MESSAGE); + if (c != null && (c.getValue() instanceof J.Literal || c.getValue() instanceof J.Identifier)) { + par = new UnwrapParentheses<>((J.Parentheses<?>) par).visit(par, ctx, getCursor().getParentOrThrow()); + } + + assert par != null; + if (par instanceof J.Parentheses) { + if (getCursor().getParentTreeCursor().getValue() instanceof J.Parentheses) { + return ((J.Parentheses<?>) par).getTree().withPrefix(Space.EMPTY); + } + } + return par; + } + + @Override + public J visitIdentifier(J.Identifier ident, ExecutionContext ctx) { + J.Identifier i = (J.Identifier) super.visitIdentifier(ident, ctx); + if (getStyle().getIdent() && getCursor().getParentTreeCursor().getValue() instanceof J.Parentheses) { + getCursor().putMessageOnFirstEnclosing(J.Parentheses.class, UNNECESSARY_PARENTHESES_MESSAGE, getCursor()); + } + return i; + } + + @Override + public J visitLiteral(J.Literal literal, ExecutionContext ctx) { + J.Literal l = (J.Literal) super.visitLiteral(literal, ctx); + JavaType.Primitive type = l.getType(); + if ((getStyle().getNumInt() && type == JavaType.Primitive.Int) || + (getStyle().getNumDouble() && type == JavaType.Primitive.Double) || + (getStyle().getNumLong() && type == JavaType.Primitive.Long) || + (getStyle().getNumFloat() && type == JavaType.Primitive.Float) || + (getStyle().getStringLiteral() && type == JavaType.Primitive.String) || + (getStyle().getLiteralNull() && type == JavaType.Primitive.Null) || + (getStyle().getLiteralFalse() && type == JavaType.Primitive.Boolean && l.getValue() == Boolean.valueOf(false)) || + (getStyle().getLiteralTrue() && type == JavaType.Primitive.Boolean && l.getValue() == Boolean.valueOf(true))) { + if (getCursor().getParentTreeCursor().getValue() instanceof J.Parentheses) { + getCursor().putMessageOnFirstEnclosing(J.Parentheses.class, UNNECESSARY_PARENTHESES_MESSAGE, + getCursor()); + } + } + return l; + } + + @Override + public J visitAssignmentOperation(J.AssignmentOperation assignOp, ExecutionContext ctx) { + J.AssignmentOperation a = (J.AssignmentOperation) super.visitAssignmentOperation(assignOp, ctx); + J.AssignmentOperation.Type op = a.getOperator(); + if (a.getAssignment() instanceof J.Parentheses && ((getStyle().getBitAndAssign() && op == J.AssignmentOperation.Type.BitAnd) || + (getStyle().getBitOrAssign() && op == J.AssignmentOperation.Type.BitOr) || + (getStyle().getBitShiftRightAssign() && op == J.AssignmentOperation.Type.UnsignedRightShift) || + (getStyle().getBitXorAssign() && op == J.AssignmentOperation.Type.BitXor) || + (getStyle().getShiftRightAssign() && op == J.AssignmentOperation.Type.RightShift) || + (getStyle().getShiftLeftAssign() && op == J.AssignmentOperation.Type.LeftShift) || + (getStyle().getMinusAssign() && op == J.AssignmentOperation.Type.Subtraction) || + (getStyle().getDivAssign() && op == J.AssignmentOperation.Type.Division) || + (getStyle().getPlusAssign() && op == J.AssignmentOperation.Type.Addition) || + (getStyle().getStarAssign() && op == J.AssignmentOperation.Type.Multiplication) || + (getStyle().getModAssign() && op == J.AssignmentOperation.Type.Modulo))) { + a = (J.AssignmentOperation) new UnwrapParentheses<>((J.Parentheses<?>) a.getAssignment()).visitNonNull(a, + ctx, getCursor().getParentOrThrow()); + } + return a; + } + + @Override + public J visitAssignment(J.Assignment assignment, ExecutionContext ctx) { + J.Assignment a = visitAndCast(assignment, ctx, super::visitAssignment); + if (getStyle().getAssign() && a.getAssignment() instanceof J.Parentheses) { + a = (J.Assignment) new UnwrapParentheses<>((J.Parentheses<?>) a.getAssignment()).visitNonNull(a, ctx, + getCursor().getParentOrThrow()); + } + return a; + } + + @Override + public J visitReturn(J.Return return_, ExecutionContext ctx) { + J.Return rtn = (J.Return) super.visitReturn(return_, ctx); + if (getStyle().getExpr() && rtn.getExpression() instanceof J.Parentheses) { + rtn = (J.Return) new UnwrapParentheses<>((J.Parentheses<?>) rtn.getExpression()).visitNonNull(rtn, ctx, + getCursor().getParentOrThrow()); + } + return rtn; + } + + @Override + public J visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext ctx) { + J.VariableDeclarations.NamedVariable v = (J.VariableDeclarations.NamedVariable) super.visitVariable(variable, + ctx); + if (getStyle().getAssign() && v.getInitializer() != null && v.getInitializer() instanceof J.Parentheses) { + v = (J.VariableDeclarations.NamedVariable) new UnwrapParentheses<>((J.Parentheses<?>) v.getInitializer()).visitNonNull(v, ctx, getCursor().getParentOrThrow()); + } + return v; + } + + @Override + public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { + J.Lambda l = (J.Lambda) super.visitLambda(lambda, ctx); + if (l.getParameters().getParameters().size() == 1 && + l.getParameters().isParenthesized() && + l.getParameters().getParameters().get(0) instanceof J.VariableDeclarations && + ((J.VariableDeclarations) l.getParameters().getParameters().get(0)).getTypeExpression() == null) { + l = l.withParameters(l.getParameters().withParenthesized(false)); + } + return l; + } + + @Override + public J visitIf(J.If iff, ExecutionContext ctx) { + J.If i = (J.If) super.visitIf(iff, ctx); + // Unwrap when if condition is a single parenthesized expression + Expression expression = i.getIfCondition().getTree(); + if (expression instanceof J.Parentheses) { + i = (J.If) new UnwrapParentheses<>((J.Parentheses<?>) expression).visitNonNull(i, ctx, + getCursor().getParentOrThrow()); + } + return i; + } + + @Override + public J visitWhileLoop(J.WhileLoop whileLoop, ExecutionContext ctx) { + J.WhileLoop w = (J.WhileLoop) super.visitWhileLoop(whileLoop, ctx); + // Unwrap when while condition is a single parenthesized expression + Expression expression = w.getCondition().getTree(); + if (expression instanceof J.Parentheses) { + w = (J.WhileLoop) new UnwrapParentheses<>((J.Parentheses<?>) expression).visitNonNull(w, ctx, + getCursor().getParentOrThrow()); + } + return w; + } + + @Override + public J visitDoWhileLoop(J.DoWhileLoop doWhileLoop, ExecutionContext ctx) { + J.DoWhileLoop dw = (J.DoWhileLoop) super.visitDoWhileLoop(doWhileLoop, ctx); + // Unwrap when while condition is a single parenthesized expression + Expression expression = dw.getWhileCondition().getTree(); + if (expression instanceof J.Parentheses) { + dw = (J.DoWhileLoop) new UnwrapParentheses<>((J.Parentheses<?>) expression).visitNonNull(dw, ctx, + getCursor().getParentOrThrow()); + } + return dw; + } + + @Override + public J visitForControl(J.ForLoop.Control control, ExecutionContext ctx) { + J.ForLoop.Control fc = (J.ForLoop.Control) super.visitForControl(control, ctx); + Expression condition = fc.getCondition(); + if (condition instanceof J.Parentheses) { + fc = (J.ForLoop.Control) new UnwrapParentheses<>((J.Parentheses<?>) condition).visitNonNull(fc, ctx, + getCursor().getParentOrThrow()); + } + return fc; + } +} \ No newline at end of file From c541a843ee1e7f4f4ee4c8433d5f022702ba01eb Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Fri, 21 Jul 2023 08:52:24 +0200 Subject: [PATCH 083/447] Migrate moved recipes (#3428) * Migrate moved recipes Following https://github.com/openrewrite/rewrite/commit/387a405814ae0868d8ff8c9d21510e7f3d88b22a & https://github.com/openrewrite/rewrite-static-analysis/commit/38823f3a21c5c5ec681fc0c7555baeb140fd11dc * Remove now also migrated test * Fix additional test --- .../recipes/UpdateMovedPackageClassNameTest.java | 14 ++------------ .../recipes/UpdateStaticAnalysisPackageTest.java | 14 ++------------ .../resources/META-INF/rewrite/migrate-rewrite.yml | 9 +++++++++ 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateMovedPackageClassNameTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateMovedPackageClassNameTest.java index 77bae97c402..f428880f433 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateMovedPackageClassNameTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateMovedPackageClassNameTest.java @@ -37,7 +37,6 @@ void changeCleanUpToStaticanalysisForSpecificClassOnly() { import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaVisitor; - import org.openrewrite.java.cleanup.SimplifyBooleanExpression; import org.openrewrite.java.cleanup.UnnecessaryCatch; import org.openrewrite.java.tree.J; @@ -58,11 +57,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) // expect to change doAfterVisit(new UnnecessaryCatch(false).getVisitor()); - org.openrewrite.java.cleanup.UnnecessaryCatch v1 = new org.openrewrite.java.cleanup.UnnecessaryCatch(true); - - // expect no change - doAfterVisit(new SimplifyBooleanExpression().getVisitor()); - org.openrewrite.java.cleanup.SimplifyBooleanExpression v2 = new org.openrewrite.java.cleanup.SimplifyBooleanExpression(); + var v1 = new org.openrewrite.java.cleanup.UnnecessaryCatch(true); return m; } }; @@ -76,7 +71,6 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaVisitor; - import org.openrewrite.java.cleanup.SimplifyBooleanExpression; import org.openrewrite.java.tree.J; import org.openrewrite.staticanalysis.UnnecessaryCatch; @@ -97,11 +91,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) // expect to change doAfterVisit(new UnnecessaryCatch(false).getVisitor()); - org.openrewrite.staticanalysis.UnnecessaryCatch v1 = new org.openrewrite.staticanalysis.UnnecessaryCatch(true); - - // expect no change - doAfterVisit(new SimplifyBooleanExpression().getVisitor()); - org.openrewrite.java.cleanup.SimplifyBooleanExpression v2 = new org.openrewrite.java.cleanup.SimplifyBooleanExpression(); + var v1 = new org.openrewrite.staticanalysis.UnnecessaryCatch(true); return m; } }; diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateStaticAnalysisPackageTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateStaticAnalysisPackageTest.java index 01584f61bf4..82f76dc4922 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateStaticAnalysisPackageTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateStaticAnalysisPackageTest.java @@ -52,7 +52,6 @@ void changeCleanUpToStaticanalysisForSpecificClassOnly() { import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaVisitor; - import org.openrewrite.java.cleanup.SimplifyBooleanExpression; import org.openrewrite.java.cleanup.UnnecessaryCatch; import org.openrewrite.java.tree.J; @@ -73,11 +72,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) // expect to change doAfterVisit(new UnnecessaryCatch(false).getVisitor()); - org.openrewrite.java.cleanup.UnnecessaryCatch v1 = new org.openrewrite.java.cleanup.UnnecessaryCatch(true); - - // expect no change - doAfterVisit(new SimplifyBooleanExpression().getVisitor()); - org.openrewrite.java.cleanup.SimplifyBooleanExpression v2 = new org.openrewrite.java.cleanup.SimplifyBooleanExpression(); + var v1 = new org.openrewrite.java.cleanup.UnnecessaryCatch(true); return m; } }; @@ -91,7 +86,6 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaVisitor; - import org.openrewrite.java.cleanup.SimplifyBooleanExpression; import org.openrewrite.java.tree.J; import org.openrewrite.staticanalysis.UnnecessaryCatch; @@ -112,11 +106,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) // expect to change doAfterVisit(new UnnecessaryCatch(false).getVisitor()); - org.openrewrite.staticanalysis.UnnecessaryCatch v1 = new org.openrewrite.staticanalysis.UnnecessaryCatch(true); - - // expect no change - doAfterVisit(new SimplifyBooleanExpression().getVisitor()); - org.openrewrite.java.cleanup.SimplifyBooleanExpression v2 = new org.openrewrite.java.cleanup.SimplifyBooleanExpression(); + var v1 = new org.openrewrite.staticanalysis.UnnecessaryCatch(true); return m; } }; diff --git a/rewrite-java/src/main/resources/META-INF/rewrite/migrate-rewrite.yml b/rewrite-java/src/main/resources/META-INF/rewrite/migrate-rewrite.yml index a22ee76b7fb..cc00d3c3be5 100644 --- a/rewrite-java/src/main/resources/META-INF/rewrite/migrate-rewrite.yml +++ b/rewrite-java/src/main/resources/META-INF/rewrite/migrate-rewrite.yml @@ -297,6 +297,12 @@ recipeList: - org.openrewrite.java.recipes.UpdateMovedRecipe: oldRecipeFullyQualifiedClassName: org.openrewrite.java.cleanup.ReplaceValidateNotNullHavingVarargsWithObjectsRequireNonNull newRecipeFullyQualifiedClassName: org.openrewrite.staticanalysis.ReplaceValidateNotNullHavingVarargsWithObjectsRequireNonNull + - org.openrewrite.java.recipes.UpdateMovedRecipe: + oldRecipeFullyQualifiedClassName: org.openrewrite.java.cleanup.SimplifyBooleanExpression + newRecipeFullyQualifiedClassName: org.openrewrite.staticanalysis.SimplifyBooleanExpression + - org.openrewrite.java.recipes.UpdateMovedRecipe: + oldRecipeFullyQualifiedClassName: org.openrewrite.java.cleanup.SimplifyBooleanReturn + newRecipeFullyQualifiedClassName: org.openrewrite.staticanalysis.SimplifyBooleanReturn - org.openrewrite.java.recipes.UpdateMovedRecipe: oldRecipeFullyQualifiedClassName: org.openrewrite.java.cleanup.SimplifyCompoundStatement newRecipeFullyQualifiedClassName: org.openrewrite.staticanalysis.SimplifyCompoundStatement @@ -330,6 +336,9 @@ recipeList: - org.openrewrite.java.recipes.UpdateMovedRecipe: oldRecipeFullyQualifiedClassName: org.openrewrite.java.cleanup.UnnecessaryPrimitiveAnnotations newRecipeFullyQualifiedClassName: org.openrewrite.staticanalysis.UnnecessaryPrimitiveAnnotations + - org.openrewrite.java.recipes.UpdateMovedRecipe: + oldRecipeFullyQualifiedClassName: org.openrewrite.java.cleanup.UnnecessaryParentheses + newRecipeFullyQualifiedClassName: org.openrewrite.staticanalysis.UnnecessaryParentheses - org.openrewrite.java.recipes.UpdateMovedRecipe: oldRecipeFullyQualifiedClassName: org.openrewrite.java.cleanup.UnnecessaryThrows newRecipeFullyQualifiedClassName: org.openrewrite.staticanalysis.UnnecessaryThrows From 64bd2c0664dac97db8b8f2953797f019c4f234f9 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Fri, 21 Jul 2023 12:52:09 +0200 Subject: [PATCH 084/447] Improve SimplifyMethodChain documentation --- .../org/openrewrite/java/SimplifyMethodChainTest.java | 2 ++ .../java/org/openrewrite/java/SimplifyMethodChain.java | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/SimplifyMethodChainTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/SimplifyMethodChainTest.java index d0de68b3089..8f613e9d647 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/SimplifyMethodChainTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/SimplifyMethodChainTest.java @@ -16,6 +16,7 @@ package org.openrewrite.java; import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; import org.openrewrite.test.RewriteTest; import java.util.Arrays; @@ -25,6 +26,7 @@ class SimplifyMethodChainTest implements RewriteTest { @Test + @DocumentExample void simplify() { rewriteRun( spec -> spec.recipe(new SimplifyMethodChain( diff --git a/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java b/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java index dfa7910a69c..5b85ef3af3d 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java @@ -31,16 +31,19 @@ @EqualsAndHashCode(callSuper = true) public class SimplifyMethodChain extends Recipe { @Option(displayName = "Method pattern chain", - description = "A list of method patterns that are called in sequence") + description = "A list of method patterns that are called in sequence", + example = "['java.util.Map keySet()', 'java.util.Set contains(..)']") List<String> methodPatternChain; @Option(displayName = "New method name", - description = "The method name that will replace the existing name. The new method name target is assumed to have the same arguments as the last method in the chain.") + description = "The method name that will replace the existing name. The new method name target is assumed to have the same arguments as the last method in the chain.", + example = "containsKey") String newMethodName; @Option(displayName = "Match on overrides", description = "When enabled, find methods that are overrides of the method pattern.", - required = false) + required = false, + example = "false") @Nullable Boolean matchOverrides; From e0881b4fffd43d53bd3cf53a4f8388877c49c92b Mon Sep 17 00:00:00 2001 From: Nate Danner <nate@moderne.io> Date: Fri, 21 Jul 2023 09:05:44 -0700 Subject: [PATCH 085/447] fix: RemoteFile/RemoteArchive can now handle multiple threads trying to download the same file without error. (#3423) * fix: RemoteFile/RemoteArchive can now handle multiple threads trying to download the same file without error. * don't use upper case locals. * fixup * prevent heap blow up. --- .../remote/LocalRemoteArtifactCache.java | 16 +++- .../remote/BlackHoleOutputStream.java | 25 +++++ .../openrewrite/remote/RemoteArchiveTest.java | 49 ++++++++-- .../openrewrite/remote/RemoteFileTest.java | 94 +++++++++++++++++++ .../test/resources/gradle-wrapper.properties | 22 +++++ 5 files changed, 195 insertions(+), 11 deletions(-) create mode 100644 rewrite-core/src/test/java/org/openrewrite/remote/BlackHoleOutputStream.java create mode 100644 rewrite-core/src/test/java/org/openrewrite/remote/RemoteFileTest.java create mode 100644 rewrite-core/src/test/resources/gradle-wrapper.properties diff --git a/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java b/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java index 51a6934a6af..543cd7f07e6 100644 --- a/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java +++ b/rewrite-core/src/main/java/org/openrewrite/remote/LocalRemoteArtifactCache.java @@ -25,6 +25,7 @@ import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.UUID; import java.util.function.Consumer; public class LocalRemoteArtifactCache implements RemoteArtifactCache { @@ -48,13 +49,20 @@ public Path get(URI uri) { @Nullable public Path put(URI uri, InputStream artifactInputStream, Consumer<Throwable> onError) { try { - Path artifact = cacheDir.resolve(hashUri(uri) + ".tmp"); + Path artifact = cacheDir.resolve(UUID.randomUUID() + ".tmp"); try (InputStream is = artifactInputStream) { Files.copy(is, artifact, StandardCopyOption.REPLACE_EXISTING); } - Files.move(artifact, cacheDir.resolve(hashUri(uri)), StandardCopyOption.ATOMIC_MOVE, - StandardCopyOption.REPLACE_EXISTING); - return cacheDir.resolve(hashUri(uri)); + Path cachedArtifact = cacheDir.resolve(hashUri(uri)); + synchronized (this) { + if (!Files.exists(cachedArtifact)) { + Files.move(artifact, cachedArtifact, StandardCopyOption.ATOMIC_MOVE, + StandardCopyOption.REPLACE_EXISTING); + } else { + Files.delete(artifact); + } + } + return cachedArtifact; } catch (Exception e) { onError.accept(e); return null; diff --git a/rewrite-core/src/test/java/org/openrewrite/remote/BlackHoleOutputStream.java b/rewrite-core/src/test/java/org/openrewrite/remote/BlackHoleOutputStream.java new file mode 100644 index 00000000000..f6c96bb0f53 --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/remote/BlackHoleOutputStream.java @@ -0,0 +1,25 @@ +/* + * Copyright 2022 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.remote; + +import java.io.OutputStream; + +public class BlackHoleOutputStream extends OutputStream { + @Override + public void write(int b) { + // Do nothing. This is used for testing length of input stream. See RemoteArchiveTest and RemoteFileTest + } +} diff --git a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java index 8191e52fa6c..d4735b7e5a1 100644 --- a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteArchiveTest.java @@ -22,11 +22,11 @@ import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.test.MockHttpSender; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.Paths; +import java.util.concurrent.*; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; @@ -48,15 +48,50 @@ void gradleWrapper(String version) throws Exception { ) .build("gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar"); - byte[] actual = readAll(remoteArchive.getInputStream(ctx)); - assertThat(actual).hasSizeGreaterThan(50_000); + long actual = getInputStreamSize(remoteArchive.getInputStream(ctx)); + assertThat(actual).isGreaterThan(50_000); } - private byte[] readAll(InputStream is) { + @ParameterizedTest + @ValueSource(strings = {"7.4.2", "7.5-rc-1", "7.6"}) + void gradleWrapperConcurrent(String version) throws Exception { + int executionCount = 5; + ExecutorService executorService = Executors.newFixedThreadPool(executionCount); + CompletionService<Long> completionService = new ExecutorCompletionService<>(executorService); + + for (int i = 0; i < executionCount; i++) { + completionService.submit(() -> { + URL distributionUrl = requireNonNull(RemoteArchiveTest.class.getClassLoader() + .getResource("gradle-" + version + "-bin.zip")); + + ExecutionContext ctx = new InMemoryExecutionContext(); + HttpSenderExecutionContextView.view(ctx) + .setLargeFileHttpSender(new MockHttpSender(distributionUrl::openStream)); + + RemoteArchive remoteArchive = Remote + .builder( + Paths.get("gradle/wrapper/gradle-wrapper.jar"), + distributionUrl.toURI() + ) + .build("gradle-[^\\/]+\\/(?:.*\\/)+gradle-wrapper-(?!shared).*\\.jar"); + + return getInputStreamSize(remoteArchive.getInputStream(ctx)); + }); + } + + for (int i = 0; i < executionCount; i++) { + Future<Long> result = completionService.take(); + Long actual = result.get(); + assertThat(actual).isGreaterThan(50_000); + } + + executorService.shutdown(); + } + + private Long getInputStreamSize(InputStream is) { + BlackHoleOutputStream out = new BlackHoleOutputStream(); try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - is.transferTo(baos); - return baos.toByteArray(); + return is.transferTo(out); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/rewrite-core/src/test/java/org/openrewrite/remote/RemoteFileTest.java b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteFileTest.java new file mode 100644 index 00000000000..8104d86a90e --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/remote/RemoteFileTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2022 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.remote; + +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.HttpSenderExecutionContextView; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.test.MockHttpSender; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Paths; +import java.util.concurrent.*; + +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; + +class RemoteFileTest { + + @Test + void gradleWrapperProperties() throws Exception { + URL distributionUrl = requireNonNull(RemoteFileTest.class.getClassLoader().getResource("gradle-wrapper.properties")); + ExecutionContext ctx = new InMemoryExecutionContext(); + HttpSenderExecutionContextView.view(ctx) + .setLargeFileHttpSender(new MockHttpSender(distributionUrl::openStream)); + + RemoteFile remoteFile = Remote + .builder( + Paths.get("gradle/wrapper/gradle-wrapper.properties"), + distributionUrl.toURI() + ) + .build(); + + long actual = getInputStreamSize(remoteFile.getInputStream(ctx)); + assertThat(actual).isGreaterThan(800); + } + + @Test + void gradleWrapperPropertiesConcurrent() throws Exception { + int executionCount = 5; + ExecutorService executorService = Executors.newFixedThreadPool(executionCount); + CompletionService<Long> completionService = new ExecutorCompletionService<>(executorService); + for (int i = 0; i < executionCount; i++) { + completionService.submit(() -> { + URL distributionUrl = requireNonNull(RemoteFileTest.class.getClassLoader().getResource("gradle-wrapper.properties")); + + ExecutionContext ctx = new InMemoryExecutionContext(); + HttpSenderExecutionContextView.view(ctx) + .setLargeFileHttpSender(new MockHttpSender(distributionUrl::openStream)); + + RemoteFile remoteFile = Remote + .builder( + Paths.get("gradle/wrapper/gradle-wrapper.properties"), + distributionUrl.toURI() + ) + .build(); + + return getInputStreamSize(remoteFile.getInputStream(ctx)); + }); + } + + for (int i = 0; i < executionCount; i++) { + Future<Long> result = completionService.take(); + Long actual = result.get(); + assertThat(actual).isGreaterThanOrEqualTo(800); + } + + executorService.shutdown(); + } + + private Long getInputStreamSize(InputStream is) { + BlackHoleOutputStream out = new BlackHoleOutputStream(); + try { + return is.transferTo(out); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/rewrite-core/src/test/resources/gradle-wrapper.properties b/rewrite-core/src/test/resources/gradle-wrapper.properties new file mode 100644 index 00000000000..044df9dda54 --- /dev/null +++ b/rewrite-core/src/test/resources/gradle-wrapper.properties @@ -0,0 +1,22 @@ +# +# Copyright 2023 the original author or authors. +# <p> +# 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 +# <p> +# https://www.apache.org/licenses/LICENSE-2.0 +# <p> +# 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. +# + +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists From df5478b8592d90bba65923878b08ae0a412fdbc9 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Fri, 21 Jul 2023 09:47:12 -0700 Subject: [PATCH 086/447] Add annotations field to identifier, add languageExtension type and keyword field to modifier --- .../main/java/org/openrewrite/java/tree/J.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index 2d4a9fbc3c2..312a657bd66 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -2361,6 +2361,8 @@ class Identifier implements J, TypeTree, Expression { @Getter Markers markers; + List<J.Annotation> annotations; + String simpleName; @Nullable @@ -3798,6 +3800,14 @@ public static boolean hasModifier(Collection<Modifier> modifiers, Modifier.Type @With Markers markers; + /** + * For languages other than Java the type will be Modifier.Type.LanguageExtension and its text will be this keyword. + * For all keywords which appear in Java this will be null. + */ + @With + @Nullable + String keyword; + @With Type type; @@ -3829,7 +3839,13 @@ public enum Type { Synchronized, Native, Strictfp, - Async + Async, + Reified, + Inline, + /** + * For modifiers not seen in Java this is used in conjunction with "keyword" + */ + LanguageExtension } } From dba1a11b68514a75cf6e0d7ffd5c3ccd9f8fb56e Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Fri, 21 Jul 2023 10:24:30 -0700 Subject: [PATCH 087/447] Update J.Identifier and J.Modifier constructor invocations to include new fields --- .../gradle/DependencyUseMapNotation.java | 2 +- .../gradle/UpdateJavaCompatibility.java | 21 +++---- .../groovy/GroovyParserVisitor.java | 60 ++++++++++--------- .../ReloadableJava11JavadocVisitor.java | 9 ++- .../ReloadableJava11ParserVisitor.java | 44 ++++++-------- .../ReloadableJava17JavadocVisitor.java | 9 ++- .../ReloadableJava17ParserVisitor.java | 36 +++++------ .../java/ReloadableJava8JavadocVisitor.java | 10 ++-- .../java/ReloadableJava8ParserVisitor.java | 32 +++++----- .../java/ImplementInterfaceTest.java | 2 + .../org/openrewrite/java/ChangeFieldName.java | 2 + .../org/openrewrite/java/ChangeFieldType.java | 3 + .../java/ChangeMethodAccessLevelVisitor.java | 2 +- .../java/ChangeMethodTargetToStatic.java | 2 + .../java/ChangeMethodTargetToVariable.java | 2 + .../org/openrewrite/java/ExtractField.java | 2 +- .../org/openrewrite/java/JavaPrinter.java | 2 + .../org/openrewrite/java/NoStaticImport.java | 3 + .../openrewrite/java/QualifyThisVisitor.java | 3 + .../org/openrewrite/java/tree/TypeTree.java | 5 +- 20 files changed, 141 insertions(+), 110 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java index b95c31919cb..df50fe8e8e9 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyUseMapNotation.java @@ -171,7 +171,7 @@ private static G.MapEntry mapEntry(String key, Expression e) { randomId(), Space.format(" "), Markers.EMPTY, - JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, key, null, null)), + JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), key, null, null)), e, null ); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateJavaCompatibility.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateJavaCompatibility.java index cba75a407bb..50b137e02cd 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateJavaCompatibility.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateJavaCompatibility.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.List; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; @Value @@ -252,8 +253,8 @@ private Expression changeExpression(Expression expression, @Nullable Declaration randomId(), literal.getPrefix(), literal.getMarkers(), - new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, "JavaVersion", JavaType.ShallowClass.build("org.gradle.api.JavaVersion"), null), - new JLeftPadded<>(Space.EMPTY, new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, name, null, null), Markers.EMPTY), + new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "JavaVersion", JavaType.ShallowClass.build("org.gradle.api.JavaVersion"), null), + new JLeftPadded<>(Space.EMPTY, new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), name, null, null), Markers.EMPTY), JavaType.ShallowClass.build("org.gradle.api.JavaVersion") ); } else if (style == DeclarationStyle.Number) { @@ -268,16 +269,16 @@ private Expression changeExpression(Expression expression, @Nullable Declaration J.FieldAccess fieldAccess = (J.FieldAccess) expression; if (style == DeclarationStyle.String) { String newVersion = version <= 8 ? "1." + version : String.valueOf(version); - expression = new J.Literal(randomId(), fieldAccess.getPrefix(), fieldAccess.getMarkers(), newVersion, "'" + newVersion + "'", Collections.emptyList(), JavaType.Primitive.String); + expression = new J.Literal(randomId(), fieldAccess.getPrefix(), fieldAccess.getMarkers(), newVersion, "'" + newVersion + "'", emptyList(), JavaType.Primitive.String); } else if (style == DeclarationStyle.Enum) { String name = version <= 8 ? "VERSION_1_" + version : "VERSION_" + version; expression = fieldAccess.withName(fieldAccess.getName().withSimpleName(name)); } else if (style == DeclarationStyle.Number) { if (version <= 8) { double doubleValue = Double.parseDouble("1." + version); - expression = new J.Literal(randomId(), fieldAccess.getPrefix(), fieldAccess.getMarkers(), doubleValue, String.valueOf(doubleValue), Collections.emptyList(), JavaType.Primitive.Double); + expression = new J.Literal(randomId(), fieldAccess.getPrefix(), fieldAccess.getMarkers(), doubleValue, String.valueOf(doubleValue), emptyList(), JavaType.Primitive.Double); } else { - expression = new J.Literal(randomId(), fieldAccess.getPrefix(), fieldAccess.getMarkers(), version, String.valueOf(version), Collections.emptyList(), JavaType.Primitive.Int); + expression = new J.Literal(randomId(), fieldAccess.getPrefix(), fieldAccess.getMarkers(), version, String.valueOf(version), emptyList(), JavaType.Primitive.Int); } } } else if (expression instanceof J.MethodInvocation && javaVersionToVersionMatcher.matches((J.MethodInvocation) expression)) { @@ -303,23 +304,23 @@ private Expression changeExpression(Expression expression, @Nullable Declaration })); } else if (style == DeclarationStyle.String) { String newVersion = version <= 8 ? "1." + version : String.valueOf(version); - expression = new J.Literal(randomId(), m.getPrefix(), m.getMarkers(), newVersion, "'" + newVersion + "'", Collections.emptyList(), JavaType.Primitive.String); + expression = new J.Literal(randomId(), m.getPrefix(), m.getMarkers(), newVersion, "'" + newVersion + "'", emptyList(), JavaType.Primitive.String); } else if (style == DeclarationStyle.Enum) { String name = version <= 8 ? "VERSION_1_" + version : "VERSION_" + version; expression = new J.FieldAccess( randomId(), m.getPrefix(), m.getMarkers(), - new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, "JavaVersion", JavaType.ShallowClass.build("org.gradle.api.JavaVersion"), null), - new JLeftPadded<>(Space.EMPTY, new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, name, null, null), Markers.EMPTY), + new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "JavaVersion", JavaType.ShallowClass.build("org.gradle.api.JavaVersion"), null), + new JLeftPadded<>(Space.EMPTY, new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), name, null, null), Markers.EMPTY), JavaType.ShallowClass.build("org.gradle.api.JavaVersion") ); } else if (style == DeclarationStyle.Number) { if (version <= 8) { double doubleValue = Double.parseDouble("1." + version); - expression = new J.Literal(randomId(), m.getPrefix(), m.getMarkers(), doubleValue, String.valueOf(doubleValue), Collections.emptyList(), JavaType.Primitive.Double); + expression = new J.Literal(randomId(), m.getPrefix(), m.getMarkers(), doubleValue, String.valueOf(doubleValue), emptyList(), JavaType.Primitive.Double); } else { - expression = new J.Literal(randomId(), m.getPrefix(), m.getMarkers(), version, String.valueOf(version), Collections.emptyList(), JavaType.Primitive.Int); + expression = new J.Literal(randomId(), m.getPrefix(), m.getMarkers(), version, String.valueOf(version), emptyList(), JavaType.Primitive.Int); } } } diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index e5314853e16..51924e21c79 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -226,7 +226,7 @@ public void visitClass(ClassNode clazz) { J.ClassDeclaration.Kind kind = new J.ClassDeclaration.Kind(randomId(), kindPrefix, Markers.EMPTY, emptyList(), kindType); Space namePrefix = whitespace(); String simpleName = name(); - J.Identifier name = new J.Identifier(randomId(), namePrefix, Markers.EMPTY, simpleName, typeMapping.type(clazz), null); + J.Identifier name = new J.Identifier(randomId(), namePrefix, Markers.EMPTY, emptyList(), simpleName, typeMapping.type(clazz), null); JContainer<J.TypeParameter> typeParameterContainer = null; if (clazz.isUsingGenerics() && clazz.getGenericsTypes() != null) { typeParameterContainer = visitTypeParameters(clazz.getGenericsTypes()); @@ -364,7 +364,7 @@ private void visitVariableField(FieldNode field) { TypeTree typeExpr = visitTypeTree(field.getOriginType()); J.Identifier name = new J.Identifier(randomId(), sourceBefore(field.getName()), Markers.EMPTY, - field.getName(), typeMapping.type(field.getOriginType()), typeMapping.variableType(field)); + emptyList(), field.getName(), typeMapping.type(field.getOriginType()), typeMapping.variableType(field)); J.VariableDeclarations.NamedVariable namedVariable = new J.VariableDeclarations.NamedVariable( randomId(), @@ -425,7 +425,7 @@ protected void visitAnnotation(AnnotationNode annotation) { cursor = saveCursor; } argPrefix = sourceBefore(arg.getKey()); - J.Identifier argName = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, arg.getKey(), null, null); + J.Identifier argName = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), arg.getKey(), null, null); J.Assignment assign = new J.Assignment(randomId(), argPrefix, Markers.EMPTY, argName, padLeft(sourceBefore("="), bodyVisitor.visit(arg.getValue())), null); @@ -457,6 +457,7 @@ public void visitMethod(MethodNode method) { J.Identifier name = new J.Identifier(randomId(), sourceBefore(method.getName()), Markers.EMPTY, + emptyList(), method.getName(), null, null); @@ -478,13 +479,13 @@ public void visitMethod(MethodNode method) { TypeTree paramType; if (param.isDynamicTyped()) { - paramType = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, "", JavaType.ShallowClass.build("java.lang.Object"), null); + paramType = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), "", JavaType.ShallowClass.build("java.lang.Object"), null); } else { paramType = visitTypeTree(param.getOriginType()); } JRightPadded<J.VariableDeclarations.NamedVariable> paramName = JRightPadded.build( new J.VariableDeclarations.NamedVariable(randomId(), EMPTY, Markers.EMPTY, - new J.Identifier(randomId(), whitespace(), Markers.EMPTY, param.getName(), null, null), + new J.Identifier(randomId(), whitespace(), Markers.EMPTY, emptyList(), param.getName(), null, null), emptyList(), null, null) ); cursor += param.getName().length(); @@ -902,7 +903,7 @@ public void visitCatchStatement(CatchStatement node) { if ("java.lang.Exception".equals(param.getType().getName()) && !source.startsWith("Exception", cursor) && !source.startsWith("java.lang.Exception", cursor)) { - paramType = new J.Identifier(randomId(), paramPrefix, Markers.EMPTY, "", + paramType = new J.Identifier(randomId(), paramPrefix, Markers.EMPTY, emptyList(), "", JavaType.ShallowClass.build("java.lang.Exception"), null); } else { paramType = visitTypeTree(param.getOriginType()).withPrefix(paramPrefix); @@ -910,7 +911,7 @@ public void visitCatchStatement(CatchStatement node) { JRightPadded<J.VariableDeclarations.NamedVariable> paramName = JRightPadded.build( new J.VariableDeclarations.NamedVariable(randomId(), whitespace(), Markers.EMPTY, - new J.Identifier(randomId(), EMPTY, Markers.EMPTY, param.getName(), null, null), + new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), param.getName(), null, null), emptyList(), null, null) ); cursor += param.getName().length(); @@ -935,7 +936,7 @@ public void visitBreakStatement(BreakStatement statement) { null : new J.Identifier(randomId(), sourceBefore(statement.getLabel()), - Markers.EMPTY, statement.getLabel(), null, null)) + Markers.EMPTY, emptyList(), statement.getLabel(), null, null)) ); } @@ -961,7 +962,7 @@ private J.Case visitDefaultCaseStatement(BlockStatement statement) { Markers.EMPTY, J.Case.Type.Statement, null, - JContainer.build(singletonList(JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, skip("default"), null, null)))), + JContainer.build(singletonList(JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null)))), JContainer.build(sourceBefore(":"), convertStatements(statement.getStatements(), t -> Space.EMPTY), Markers.EMPTY), null @@ -1007,7 +1008,7 @@ null, emptyList(), singletonList( JRightPadded.build( new J.VariableDeclarations.NamedVariable(randomId(), sourceBefore(p.getName()), Markers.EMPTY, - new J.Identifier(randomId(), EMPTY, Markers.EMPTY, p.getName(), type, null), + new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), p.getName(), type, null), emptyList(), null, typeMapping.variableType(p.getName(), staticType(p))) ) @@ -1219,7 +1220,7 @@ public void visitExpressionStatement(ExpressionStatement statement) { // Could iterate over those in reverse order, but feels safer to just take the count and go off source code alone for (int i = 0; i < statement.getStatementLabels().size(); i++) { labels.add(new J.Label(randomId(), whitespace(), Markers.EMPTY, JRightPadded.build( - new J.Identifier(randomId(), EMPTY, Markers.EMPTY, name(), null, null)).withAfter(sourceBefore(":")), + new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), name(), null, null)).withAfter(sourceBefore(":")), new J.Empty(randomId(), EMPTY, Markers.EMPTY))); } } @@ -1271,7 +1272,7 @@ public void visitForLoop(ForStatement forLoop) { visitTypeTree(param.getOriginType()) : null; JRightPadded<J.VariableDeclarations.NamedVariable> paramName = JRightPadded.build( new J.VariableDeclarations.NamedVariable(randomId(), whitespace(), Markers.EMPTY, - new J.Identifier(randomId(), EMPTY, Markers.EMPTY, param.getName(), null, null), + new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), param.getName(), null, null), emptyList(), null, null) ); cursor += param.getName().length(); @@ -1443,7 +1444,7 @@ public void visitMethodCallExpression(MethodCallExpression call) { J.Identifier name; if (call.getMethodAsString().equals(source.substring(cursor, cursor + call.getMethodAsString().length()))) { name = new J.Identifier(randomId(), sourceBefore(call.getMethodAsString()), Markers.EMPTY, - call.getMethodAsString(), null, null); + emptyList(), call.getMethodAsString(), null, null); } else if (select != null && select.getElement() instanceof J.Identifier) { name = (J.Identifier) select.getElement(); @@ -1562,7 +1563,7 @@ public void visitStaticMethodCallExpression(StaticMethodCallExpression call) { MethodNode methodNode = (MethodNode) call.getNodeMetaData().get(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET); JavaType.Method methodType = typeMapping.methodType(methodNode); J.Identifier name = new J.Identifier(randomId(), sourceBefore(call.getMethodAsString()), Markers.EMPTY, - call.getMethodAsString(), methodType, null); + emptyList(), call.getMethodAsString(), methodType, null); // Method invocations may have type information that can enrich the type information of its parameters Markers markers = Markers.EMPTY; @@ -1624,7 +1625,7 @@ public void visitPropertyExpression(PropertyExpression prop) { if (name instanceof J.Literal) { String nameStr = ((J.Literal) name).getValueSource(); assert nameStr != null; - name = new J.Identifier(randomId(), name.getPrefix(), Markers.EMPTY, nameStr, null, null); + name = new J.Identifier(randomId(), name.getPrefix(), Markers.EMPTY, emptyList(), nameStr, null, null); } if (prop.isSpreadSafe()) { name = name.withMarkers(name.getMarkers().add(new StarDot(randomId()))); @@ -1848,6 +1849,7 @@ public TypeTree visitVariableExpressionType(VariableExpression expression) { return new J.Identifier(randomId(), sourceBefore("def"), Markers.EMPTY, + emptyList(), "def", type, null); } @@ -1855,6 +1857,7 @@ public TypeTree visitVariableExpressionType(VariableExpression expression) { J.Identifier ident = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, + emptyList(), expression.getOriginType().getUnresolvedName(), type, null); if (type instanceof JavaType.Parameterized) { @@ -1875,6 +1878,7 @@ public void visitVariableExpression(VariableExpression expression) { queue.add(new J.Identifier(randomId(), sourceBefore(expression.getName()), Markers.EMPTY, + emptyList(), expression.getName(), type, null) ); @@ -1972,7 +1976,7 @@ private JRightPadded<Statement> convertTopLevelStatement(SourceUnit unit, ASTNod int endOfWhitespace = indexOfNextNonWhitespace(cursor, source); if (endOfWhitespace + 2 <= source.length() && "as".equals(source.substring(endOfWhitespace, endOfWhitespace + 2))) { String simpleName = importNode.getAlias(); - alias = padLeft(sourceBefore("as"), new J.Identifier(randomId(), sourceBefore(simpleName), Markers.EMPTY, simpleName, null, null)); + alias = padLeft(sourceBefore("as"), new J.Identifier(randomId(), sourceBefore(simpleName), Markers.EMPTY, emptyList(), simpleName, null, null)); } J.Import anImport = new J.Import(randomId(), prefix, Markers.EMPTY, statik, qualid, alias); @@ -2078,7 +2082,7 @@ private <T extends TypeTree & Expression> T typeTree(@Nullable ClassNode classNo String part = parts[i]; if (i == 0) { fullName = part; - expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, part, typeMapping.type(classNode), null); + expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), part, typeMapping.type(classNode), null); } else { fullName += "." + part; @@ -2095,7 +2099,7 @@ private <T extends TypeTree & Expression> T typeTree(@Nullable ClassNode classNo EMPTY, Markers.EMPTY, expr, - padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, part.trim(), null, null)), + padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, emptyList(), part.trim(), null, null)), (Character.isUpperCase(part.charAt(0)) || i == parts.length - 1) ? JavaType.ShallowClass.build(fullName) : null @@ -2176,7 +2180,7 @@ private TypeTree visitTypeTree(ClassNode classNode) { Space fmt = whitespace(); if (cursor < source.length() && source.startsWith("def", cursor)) { cursor += 3; - return new J.Identifier(randomId(), fmt, Markers.EMPTY, "def", + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), "def", JavaType.ShallowClass.build("java.lang.Object"), null); } else { cursor = saveCursor; @@ -2189,31 +2193,31 @@ private List<J.Modifier> visitModifiers(int modifiers) { List<J.Modifier> unorderedModifiers = new ArrayList<>(); if ((modifiers & Opcodes.ACC_ABSTRACT) != 0) { - unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, J.Modifier.Type.Abstract, emptyList())); + unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, null, J.Modifier.Type.Abstract, emptyList())); } if ((modifiers & Opcodes.ACC_FINAL) != 0) { - unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, J.Modifier.Type.Final, emptyList())); + unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, null, J.Modifier.Type.Final, emptyList())); } if ((modifiers & Opcodes.ACC_PRIVATE) != 0) { - unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, J.Modifier.Type.Private, emptyList())); + unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, null, J.Modifier.Type.Private, emptyList())); } if ((modifiers & Opcodes.ACC_PROTECTED) != 0) { - unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, J.Modifier.Type.Protected, emptyList())); + unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, null, J.Modifier.Type.Protected, emptyList())); } if ((modifiers & Opcodes.ACC_PUBLIC) != 0) { - unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, J.Modifier.Type.Public, emptyList())); + unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, null, J.Modifier.Type.Public, emptyList())); } if ((modifiers & Opcodes.ACC_STATIC) != 0) { - unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, J.Modifier.Type.Static, emptyList())); + unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, null, J.Modifier.Type.Static, emptyList())); } if ((modifiers & Opcodes.ACC_SYNCHRONIZED) != 0) { - unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, J.Modifier.Type.Synchronized, emptyList())); + unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, null, J.Modifier.Type.Synchronized, emptyList())); } if ((modifiers & Opcodes.ACC_TRANSIENT) != 0) { - unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, J.Modifier.Type.Transient, emptyList())); + unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, null, J.Modifier.Type.Transient, emptyList())); } if ((modifiers & Opcodes.ACC_VOLATILE) != 0) { - unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, J.Modifier.Type.Volatile, emptyList())); + unorderedModifiers.add(new J.Modifier(randomId(), EMPTY, Markers.EMPTY, null, J.Modifier.Type.Volatile, emptyList())); } List<J.Modifier> orderedModifiers = new ArrayList<>(unorderedModifiers.size()); diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java index bbc490b792e..d2abe9cb439 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java @@ -429,6 +429,7 @@ public J.Identifier visitIdentifier(com.sun.source.doctree.IdentifierTree node, randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), name, null, null @@ -593,7 +594,7 @@ public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { } else { qualifierType = typeMapping.type(enclosingClassType); if (source.charAt(cursor) == '#') { - qualifier = new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, "", qualifierType, null); + qualifier = new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "", qualifierType, null); cursor++; } else { qualifier = null; @@ -605,6 +606,7 @@ public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), ref.memberName.toString(), null, null @@ -1108,6 +1110,7 @@ public J visitMemberSelect(MemberSelectTree node, Space fmt) { JLeftPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), fieldAccess.name.toString(), null, null)), typeMapping.type(node)); } @@ -1117,7 +1120,7 @@ public J visitIdentifier(IdentifierTree node, Space fmt) { String name = node.getName().toString(); cursor += name.length(); JavaType type = typeMapping.type(node); - return new J.Identifier(randomId(), fmt, Markers.EMPTY, name, type, null); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, type, null); } @Override @@ -1125,7 +1128,7 @@ public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { JCTree.JCPrimitiveTypeTree primitiveType = (JCTree.JCPrimitiveTypeTree) node; String name = primitiveType.toString(); cursor += name.length(); - return new J.Identifier(randomId(), fmt, Markers.EMPTY, name, typeMapping.primitive(primitiveType.typetag), null); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, typeMapping.primitive(primitiveType.typetag), null); } @Override 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 7676c54e265..568187e36f8 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 @@ -338,7 +338,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - skip(node.getLabel().toString()), null, null); + emptyList(), skip(node.getLabel().toString()), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -351,7 +351,7 @@ public J visitCase(CaseTree node, Space fmt) { JContainer.build( node.getExpression() == null ? EMPTY : sourceBefore("case"), singletonList(node.getExpression() == null ? - JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, skip("default"), null, null)) : + JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null)) : JRightPadded.build(convertOrNull(node.getExpression())) ), Markers.EMPTY @@ -399,7 +399,7 @@ public J visitClass(ClassTree node, Space fmt) { } J.Identifier name = new J.Identifier(randomId(), sourceBefore(node.getSimpleName().toString()), - Markers.EMPTY, ((JCClassDecl) node).getSimpleName().toString(), typeMapping.type(node), null); + Markers.EMPTY, emptyList(), ((JCClassDecl) node).getSimpleName().toString(), typeMapping.type(node), null); JContainer<J.TypeParameter> typeParams = node.getTypeParameters().isEmpty() ? null : JContainer.build( sourceBefore("<"), @@ -599,7 +599,7 @@ public J visitContinue(ContinueTree node, Space fmt) { Name label = node.getLabel(); return new J.Continue(randomId(), fmt, Markers.EMPTY, label == null ? null : new J.Identifier(randomId(), sourceBefore(label.toString()), - Markers.EMPTY, label.toString(), null, null)); + Markers.EMPTY, emptyList(), label.toString(), null, null)); } @Override @@ -636,7 +636,7 @@ private J visitEnumVariable(VariableTree node, Space fmt) { skip(node.getName().toString()); } - J.Identifier name = new J.Identifier(randomId(), nameSpace, Markers.EMPTY, node.getName().toString(), typeMapping.type(node), null); + J.Identifier name = new J.Identifier(randomId(), nameSpace, Markers.EMPTY, emptyList(), node.getName().toString(), typeMapping.type(node), null); J.NewClass initializer = null; if (source.charAt(endPos(node) - 1) == ')' || source.charAt(endPos(node) - 1) == '}') { @@ -688,7 +688,7 @@ public J visitIdentifier(IdentifierTree node, Space fmt) { JCIdent ident = (JCIdent) node; JavaType type = typeMapping.type(node); - return new J.Identifier(randomId(), fmt, Markers.EMPTY, name, type, typeMapping.variableType(ident.sym)); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, type, typeMapping.variableType(ident.sym)); } @Override @@ -725,7 +725,7 @@ public J visitInstanceOf(InstanceOfTree node, Space fmt) { public J visitLabeledStatement(LabeledStatementTree node, Space fmt) { skip(node.getLabel().toString()); return new J.Label(randomId(), fmt, Markers.EMPTY, - padRight(new J.Identifier(randomId(), EMPTY, Markers.EMPTY, node.getLabel().toString(), null, null), sourceBefore(":")), + padRight(new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), node.getLabel().toString(), null, null), sourceBefore(":")), convert(node.getStatement())); } @@ -840,6 +840,7 @@ public J visitMemberReference(MemberReferenceTree node, Space fmt) { padLeft(whitespace(), new J.Identifier(randomId(), sourceBefore(referenceName), Markers.EMPTY, + emptyList(), referenceName, null, null)), typeMapping.type(node), @@ -856,7 +857,7 @@ public J visitMemberSelect(MemberSelectTree node, Space fmt) { convert(fieldAccess.selected), padLeft(sourceBefore("."), new J.Identifier(randomId(), sourceBefore(fieldAccess.name.toString()), Markers.EMPTY, - fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), + emptyList(), fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), type); } @@ -881,7 +882,7 @@ public J visitMethodInvocation(MethodInvocationTree node, Space fmt) { J.Identifier name; if (jcSelect instanceof JCFieldAccess) { String selectName = ((JCFieldAccess) jcSelect).name.toString(); - name = new J.Identifier(randomId(), sourceBefore(selectName), Markers.EMPTY, selectName, null, null); + name = new J.Identifier(randomId(), sourceBefore(selectName), Markers.EMPTY, emptyList(), selectName, null, null); } else { name = convert(jcSelect); } @@ -946,10 +947,11 @@ public J visitMethod(MethodTree node, Space fmt) { } else { owner = jcMethod.sym.owner.name.toString(); } - name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner), Markers.EMPTY, owner, null, null), returnType == null ? returnTypeAnnotations : Collections.emptyList()); + name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner), + Markers.EMPTY, emptyList(), owner, null, null), emptyList()); } else { name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(node.getName().toString(), null), Markers.EMPTY, - node.getName().toString(), null, null), returnType == null ? returnTypeAnnotations : Collections.emptyList()); + returnType == null ? returnTypeAnnotations : emptyList(), node.getName().toString(), null, null), emptyList()); } Space paramFmt = sourceBefore("("); @@ -1258,7 +1260,7 @@ private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) String part = parts[i]; if (i == 0) { fullName = part; - expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, part, null, null); + expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), part, null, null); } else { fullName += "." + part; @@ -1275,7 +1277,7 @@ private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) EMPTY, Markers.EMPTY, expr, - padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, part.trim(), null, null)), + padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, emptyList(), part.trim(), null, null)), (Character.isUpperCase(part.charAt(0)) || i == parts.length - 1) ? JavaType.ShallowClass.build(fullName) : null @@ -1374,7 +1376,7 @@ private J.VariableDeclarations visitVariables(List<VariableTree> nodes, Space fm // this is a lambda parameter with an inferred type expression typeExpr = null; } else { - typeExpr = new J.Identifier(randomId(), sourceBefore("var"), Markers.EMPTY, "var", typeMapping.type(vartype), null); + typeExpr = new J.Identifier(randomId(), sourceBefore("var"), Markers.EMPTY, emptyList(), "var", typeMapping.type(vartype), null); typeExpr = typeExpr.withMarkers(typeExpr.getMarkers().add(JavaVarKeyword.build())); } } else if (vartype instanceof JCArrayTypeTree) { @@ -1392,16 +1394,6 @@ private J.VariableDeclarations visitVariables(List<VariableTree> nodes, Space fm typeExpr = new J.AnnotatedType(randomId(), Space.EMPTY, Markers.EMPTY, typeExprAnnotations, typeExpr); } - Supplier<List<JLeftPadded<Space>>> dimensions = () -> { - Matcher matcher = Pattern.compile("\\G(\\s*)\\[(\\s*)]").matcher(source); - List<JLeftPadded<Space>> dims = new ArrayList<>(); - while (matcher.find(cursor)) { - cursor(matcher.end()); - dims.add(padLeft(format(matcher.group(1)), format(matcher.group(2)))); - } - return dims; - }; - List<JLeftPadded<Space>> beforeDimensions = arrayDimensions(); Space varargs = null; @@ -1424,7 +1416,7 @@ private J.VariableDeclarations visitVariables(List<VariableTree> nodes, Space fm Space namedVarPrefix = sourceBefore(n.getName().toString()); JavaType type = typeMapping.type(n); - J.Identifier name = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, n.getName().toString(), + J.Identifier name = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), n.getName().toString(), type instanceof JavaType.Variable ? ((JavaType.Variable) type).getType() : type, type instanceof JavaType.Variable ? (JavaType.Variable) type : null); List<JLeftPadded<Space>> dimensionsAfterName = arrayDimensions(); @@ -1938,7 +1930,7 @@ private J.Modifier mapModifier(Modifier mod, List<J.Annotation> annotations) { default: throw new IllegalArgumentException("Unexpected modifier " + mod); } - return new J.Modifier(randomId(), modFormat, Markers.EMPTY, type, annotations); + return new J.Modifier(randomId(), modFormat, Markers.EMPTY, null, type, annotations); } private List<J.Annotation> collectAnnotations(Map<Integer, JCAnnotation> annotationPosTable) { diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java index 6b57d92388a..8d289b3152c 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java @@ -429,6 +429,7 @@ public J.Identifier visitIdentifier(com.sun.source.doctree.IdentifierTree node, randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), name, null, null @@ -593,7 +594,7 @@ public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { } else { qualifierType = typeMapping.type(enclosingClassType); if (source.charAt(cursor) == '#') { - qualifier = new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, "", qualifierType, null); + qualifier = new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "", qualifierType, null); cursor++; } else { qualifier = null; @@ -606,6 +607,7 @@ public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), ref.memberName.toString(), null, null @@ -1109,6 +1111,7 @@ public J visitMemberSelect(MemberSelectTree node, Space fmt) { JLeftPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), fieldAccess.name.toString(), null, null)), typeMapping.type(node)); } @@ -1118,7 +1121,7 @@ public J visitIdentifier(IdentifierTree node, Space fmt) { String name = node.getName().toString(); cursor += name.length(); JavaType type = typeMapping.type(node); - return new J.Identifier(randomId(), fmt, Markers.EMPTY, name, type, null); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, type, null); } @Override @@ -1126,7 +1129,7 @@ public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { JCTree.JCPrimitiveTypeTree primitiveType = (JCTree.JCPrimitiveTypeTree) node; String name = primitiveType.toString(); cursor += name.length(); - return new J.Identifier(randomId(), fmt, Markers.EMPTY, name, typeMapping.primitive(primitiveType.typetag), null); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, typeMapping.primitive(primitiveType.typetag), null); } @Override 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 35231f14c3f..e700e43157f 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 @@ -342,7 +342,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - skip(node.getLabel().toString()), null, null); + emptyList(), skip(node.getLabel().toString()), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -359,7 +359,7 @@ public J visitCase(CaseTree node, Space fmt) { JContainer.build( node.getExpressions().isEmpty() ? EMPTY : sourceBefore("case"), node.getExpressions().isEmpty() ? - List.of(JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, skip("default"), null, null))) : + List.of(JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null))) : convertAll(node.getExpressions(), commaDelim, t -> EMPTY), Markers.EMPTY ), @@ -423,7 +423,7 @@ public J visitClass(ClassTree node, Space fmt) { } J.Identifier name = new J.Identifier(randomId(), sourceBefore(node.getSimpleName().toString()), - Markers.EMPTY, ((JCClassDecl) node).getSimpleName().toString(), typeMapping.type(node), null); + Markers.EMPTY, emptyList(), ((JCClassDecl) node).getSimpleName().toString(), typeMapping.type(node), null); JContainer<J.TypeParameter> typeParams = node.getTypeParameters().isEmpty() ? null : JContainer.build( sourceBefore("<"), @@ -656,7 +656,7 @@ public J visitContinue(ContinueTree node, Space fmt) { Name label = node.getLabel(); return new J.Continue(randomId(), fmt, Markers.EMPTY, label == null ? null : new J.Identifier(randomId(), sourceBefore(label.toString()), - Markers.EMPTY, label.toString(), null, null)); + Markers.EMPTY, emptyList(), label.toString(), null, null)); } @Override @@ -693,7 +693,7 @@ private J visitEnumVariable(VariableTree node, Space fmt) { skip(node.getName().toString()); } - J.Identifier name = new J.Identifier(randomId(), nameSpace, Markers.EMPTY, node.getName().toString(), typeMapping.type(node), null); + J.Identifier name = new J.Identifier(randomId(), nameSpace, Markers.EMPTY, emptyList(), node.getName().toString(), typeMapping.type(node), null); J.NewClass initializer = null; if (source.charAt(endPos(node) - 1) == ')' || source.charAt(endPos(node) - 1) == '}') { @@ -745,7 +745,7 @@ public J visitIdentifier(IdentifierTree node, Space fmt) { JCIdent ident = (JCIdent) node; JavaType type = typeMapping.type(node); - return new J.Identifier(randomId(), fmt, Markers.EMPTY, name, type, typeMapping.variableType(ident.sym)); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, type, typeMapping.variableType(ident.sym)); } @Override @@ -775,7 +775,7 @@ public J visitInstanceOf(InstanceOfTree node, Space fmt) { convert(node.getExpression(), t -> sourceBefore("instanceof")), convert(node.getType()), node.getPattern() instanceof JCBindingPattern b ? - new J.Identifier(randomId(), sourceBefore(b.getVariable().getName().toString()), Markers.EMPTY, b.getVariable().getName().toString(), + new J.Identifier(randomId(), sourceBefore(b.getVariable().getName().toString()), Markers.EMPTY, emptyList(), b.getVariable().getName().toString(), type, typeMapping.variableType(b.var.sym)) : null, type); } @@ -784,7 +784,7 @@ public J visitInstanceOf(InstanceOfTree node, Space fmt) { public J visitLabeledStatement(LabeledStatementTree node, Space fmt) { skip(node.getLabel().toString()); return new J.Label(randomId(), fmt, Markers.EMPTY, - padRight(new J.Identifier(randomId(), EMPTY, Markers.EMPTY, node.getLabel().toString(), null, null), sourceBefore(":")), + padRight(new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), node.getLabel().toString(), null, null), sourceBefore(":")), convert(node.getStatement())); } @@ -899,6 +899,7 @@ public J visitMemberReference(MemberReferenceTree node, Space fmt) { padLeft(whitespace(), new J.Identifier(randomId(), sourceBefore(referenceName), Markers.EMPTY, + emptyList(), referenceName, null, null)), typeMapping.type(node), @@ -915,7 +916,7 @@ public J visitMemberSelect(MemberSelectTree node, Space fmt) { convert(fieldAccess.selected), padLeft(sourceBefore("."), new J.Identifier(randomId(), sourceBefore(fieldAccess.name.toString()), Markers.EMPTY, - fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), + emptyList(), fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), type); } @@ -940,7 +941,7 @@ public J visitMethodInvocation(MethodInvocationTree node, Space fmt) { J.Identifier name; if (jcSelect instanceof JCFieldAccess) { String selectName = ((JCFieldAccess) jcSelect).name.toString(); - name = new J.Identifier(randomId(), sourceBefore(selectName), Markers.EMPTY, selectName, null, null); + name = new J.Identifier(randomId(), sourceBefore(selectName), Markers.EMPTY, emptyList(), selectName, null, null); } else { name = convert(jcSelect); } @@ -1005,10 +1006,11 @@ public J visitMethod(MethodTree node, Space fmt) { } else { owner = jcMethod.sym.owner.name.toString(); } - name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner), Markers.EMPTY, owner, null, null), returnType == null ? returnTypeAnnotations : Collections.emptyList()); + name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner), + Markers.EMPTY, returnType == null ? returnTypeAnnotations : emptyList(), owner, null, null), emptyList()); } else { name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(node.getName().toString(), null), Markers.EMPTY, - node.getName().toString(), null, null), returnType == null ? returnTypeAnnotations : Collections.emptyList()); + returnType == null ? returnTypeAnnotations : emptyList(), node.getName().toString(), null, null), emptyList()); } boolean isCompactConstructor = nodeSym != null && (nodeSym.flags() & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0; @@ -1334,7 +1336,7 @@ private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) String part = parts[i]; if (i == 0) { fullName = part; - expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, part, null, null); + expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), part, null, null); } else { fullName += "." + part; @@ -1351,7 +1353,7 @@ private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) EMPTY, Markers.EMPTY, expr, - padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, part.trim(), null, null)), + padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, emptyList(), part.trim(), null, null)), (Character.isUpperCase(part.charAt(0)) || i == parts.length - 1) ? JavaType.ShallowClass.build(fullName) : null @@ -1450,7 +1452,7 @@ private J.VariableDeclarations visitVariables(List<VariableTree> nodes, Space fm // this is a lambda parameter with an inferred type expression typeExpr = null; } else { - typeExpr = new J.Identifier(randomId(), sourceBefore("var"), Markers.EMPTY, "var", typeMapping.type(vartype), null); + typeExpr = new J.Identifier(randomId(), sourceBefore("var"), Markers.EMPTY, emptyList(), "var", typeMapping.type(vartype), null); typeExpr = typeExpr.withMarkers(typeExpr.getMarkers().add(JavaVarKeyword.build())); } } else if (vartype instanceof JCArrayTypeTree) { @@ -1486,7 +1488,7 @@ private J.VariableDeclarations visitVariables(List<VariableTree> nodes, Space fm Space namedVarPrefix = sourceBefore(n.getName().toString()); JavaType type = typeMapping.type(n); - J.Identifier name = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, n.getName().toString(), + J.Identifier name = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), n.getName().toString(), type instanceof JavaType.Variable ? ((JavaType.Variable) type).getType() : type, type instanceof JavaType.Variable ? (JavaType.Variable) type : null); List<JLeftPadded<Space>> dimensionsAfterName = arrayDimensions(); @@ -1997,7 +1999,7 @@ private J.Modifier mapModifier(Modifier mod, List<J.Annotation> annotations) { default: throw new IllegalArgumentException("Unexpected modifier " + mod); } - return new J.Modifier(randomId(), modFormat, Markers.EMPTY, type, annotations); + return new J.Modifier(randomId(), modFormat, Markers.EMPTY, null, type, annotations); } private List<J.Annotation> collectAnnotations(Map<Integer, JCAnnotation> annotationPosTable) { diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java index cb48bedb10b..536ce2151a7 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java @@ -425,6 +425,7 @@ public J.Identifier visitIdentifier(com.sun.source.doctree.IdentifierTree node, randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), name, null, null @@ -553,7 +554,7 @@ public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { } else { qualifierType = typeMapping.type(enclosingClassType); if (source.charAt(cursor) == '#') { - qualifier = new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, "", qualifierType, null); + qualifier = new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "", qualifierType, null); cursor++; } else { qualifier = null; @@ -565,6 +566,7 @@ public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), ref.memberName.toString(), null, null @@ -1032,7 +1034,7 @@ public J visitMemberSelect(MemberSelectTree node, Space fmt) { JLeftPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, - fieldAccess.name.toString(), null, null)), + emptyList(), fieldAccess.name.toString(), null, null)), typeMapping.type(node)); } @@ -1041,7 +1043,7 @@ public J visitIdentifier(IdentifierTree node, Space fmt) { String name = node.getName().toString(); cursor += name.length(); JavaType type = typeMapping.type(node); - return new J.Identifier(randomId(), fmt, Markers.EMPTY, name, type, null); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, type, null); } @Override @@ -1049,7 +1051,7 @@ public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { JCTree.JCPrimitiveTypeTree primitiveType = (JCTree.JCPrimitiveTypeTree) node; String name = primitiveType.toString(); cursor += name.length(); - return new J.Identifier(randomId(), fmt, Markers.EMPTY, name, typeMapping.primitive(primitiveType.typetag), null); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, typeMapping.primitive(primitiveType.typetag), null); } @Override 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 3208fe40963..868e98f9722 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 @@ -335,7 +335,7 @@ public J visitBreak(BreakTree node, Space fmt) { J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), sourceBefore(node.getLabel().toString()), Markers.EMPTY, - skip(node.getLabel().toString()), null, null); + emptyList(), skip(node.getLabel().toString()), null, null); return new J.Break(randomId(), fmt, Markers.EMPTY, label); } @@ -348,7 +348,7 @@ public J visitCase(CaseTree node, Space fmt) { JContainer.build( node.getExpression() == null ? EMPTY : sourceBefore("case"), singletonList(node.getExpression() == null ? - JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, skip("default"), null, null)) : + JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null)) : JRightPadded.build(convertOrNull(node.getExpression())) ), Markers.EMPTY @@ -396,7 +396,7 @@ public J visitClass(ClassTree node, Space fmt) { } J.Identifier name = new J.Identifier(randomId(), sourceBefore(node.getSimpleName().toString()), - Markers.EMPTY, ((JCClassDecl) node).getSimpleName().toString(), typeMapping.type(node), null); + Markers.EMPTY, emptyList(), ((JCClassDecl) node).getSimpleName().toString(), typeMapping.type(node), null); JContainer<J.TypeParameter> typeParams = node.getTypeParameters().isEmpty() ? null : JContainer.build( sourceBefore("<"), @@ -597,7 +597,7 @@ public J visitContinue(ContinueTree node, Space fmt) { Name label = node.getLabel(); return new J.Continue(randomId(), fmt, Markers.EMPTY, label == null ? null : new J.Identifier(randomId(), sourceBefore(label.toString()), - Markers.EMPTY, label.toString(), null, null)); + Markers.EMPTY, emptyList(), label.toString(), null, null)); } @Override @@ -634,7 +634,7 @@ private J visitEnumVariable(VariableTree node, Space fmt) { skip(node.getName().toString()); } - J.Identifier name = new J.Identifier(randomId(), nameSpace, Markers.EMPTY, node.getName().toString(), typeMapping.type(node), null); + J.Identifier name = new J.Identifier(randomId(), nameSpace, Markers.EMPTY, emptyList(), node.getName().toString(), typeMapping.type(node), null); J.NewClass initializer = null; if (source.charAt(endPos(node) - 1) == ')' || source.charAt(endPos(node) - 1) == '}') { @@ -686,7 +686,7 @@ public J visitIdentifier(IdentifierTree node, Space fmt) { JCIdent ident = (JCIdent) node; JavaType type = typeMapping.type(node); - return new J.Identifier(randomId(), fmt, Markers.EMPTY, name, type, typeMapping.variableType(ident.sym)); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, type, typeMapping.variableType(ident.sym)); } @Override @@ -723,7 +723,7 @@ public J visitInstanceOf(InstanceOfTree node, Space fmt) { public J visitLabeledStatement(LabeledStatementTree node, Space fmt) { skip(node.getLabel().toString()); return new J.Label(randomId(), fmt, Markers.EMPTY, - padRight(new J.Identifier(randomId(), EMPTY, Markers.EMPTY, node.getLabel().toString(), null, null), sourceBefore(":")), + padRight(new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), node.getLabel().toString(), null, null), sourceBefore(":")), convert(node.getStatement())); } @@ -838,6 +838,7 @@ public J visitMemberReference(MemberReferenceTree node, Space fmt) { padLeft(whitespace(), new J.Identifier(randomId(), sourceBefore(referenceName), Markers.EMPTY, + emptyList(), referenceName, null, null)), typeMapping.type(node), @@ -854,7 +855,7 @@ public J visitMemberSelect(MemberSelectTree node, Space fmt) { convert(fieldAccess.selected), padLeft(sourceBefore("."), new J.Identifier(randomId(), sourceBefore(fieldAccess.name.toString()), Markers.EMPTY, - fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), + emptyList(), fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), type); } @@ -879,7 +880,7 @@ public J visitMethodInvocation(MethodInvocationTree node, Space fmt) { J.Identifier name; if (jcSelect instanceof JCFieldAccess) { String selectName = ((JCFieldAccess) jcSelect).name.toString(); - name = new J.Identifier(randomId(), sourceBefore(selectName), Markers.EMPTY, selectName, null, null); + name = new J.Identifier(randomId(), sourceBefore(selectName), Markers.EMPTY, emptyList(), selectName, null, null); } else { name = convert(jcSelect); } @@ -943,10 +944,11 @@ public J visitMethod(MethodTree node, Space fmt) { } else { owner = jcMethod.sym.owner.name.toString(); } - name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner), Markers.EMPTY, owner, null, null), returnType == null ? returnTypeAnnotations : Collections.emptyList()); + name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner), + Markers.EMPTY, emptyList(), owner, null, null), returnType == null ? returnTypeAnnotations : emptyList()); } else { name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(node.getName().toString()), Markers.EMPTY, - node.getName().toString(), null, null), returnType == null ? returnTypeAnnotations : Collections.emptyList()); + returnType == null ? returnTypeAnnotations : emptyList(), node.getName().toString(), null, null), emptyList()); } Space paramFmt = sourceBefore("("); @@ -1249,7 +1251,7 @@ private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) String part = parts[i]; if (i == 0) { fullName = part; - expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, part, null, null); + expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), part, null, null); } else { fullName += "." + part; @@ -1266,7 +1268,7 @@ private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) EMPTY, Markers.EMPTY, expr, - padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, part.trim(), null, null)), + padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, emptyList(), part.trim(), null, null)), (Character.isUpperCase(part.charAt(0)) || i == parts.length - 1) ? JavaType.ShallowClass.build(fullName) : null @@ -1396,7 +1398,7 @@ private J.VariableDeclarations visitVariables(List<VariableTree> nodes, Space fm JCVariableDecl vd = (JCVariableDecl) n; JavaType type = typeMapping.type(n); - J.Identifier name = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, n.getName().toString(), + J.Identifier name = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), n.getName().toString(), type instanceof JavaType.Variable ? ((JavaType.Variable) type).getType() : type, type instanceof JavaType.Variable ? (JavaType.Variable) type : null); List<JLeftPadded<Space>> dimensionsAfterName = arrayDimensions(); @@ -1916,7 +1918,7 @@ private J.Modifier mapModifier(Modifier mod, List<J.Annotation> annotations) { default: throw new IllegalArgumentException("Unexpected modifier " + mod); } - return new J.Modifier(randomId(), modFormat, Markers.EMPTY, type, annotations); + return new J.Modifier(randomId(), modFormat, Markers.EMPTY, null, type, annotations); } private List<J.Annotation> collectAnnotations(Map<Integer, JCAnnotation> annotationPosTable) { diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ImplementInterfaceTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ImplementInterfaceTest.java index b25278d5573..ef6c9fee304 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ImplementInterfaceTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ImplementInterfaceTest.java @@ -108,6 +108,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), "String", ShallowClass.build("java.lang.String"), null @@ -116,6 +117,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex randomId(), Space.build(" ", emptyList()), Markers.EMPTY, + emptyList(), "LocalDate", ShallowClass.build("java.time.LocalDate"), null diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldName.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldName.java index ccffb692b54..7aa16252591 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldName.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldName.java @@ -20,6 +20,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.tree.*; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; @Value @@ -71,6 +72,7 @@ public J.Identifier visitIdentifier(J.Identifier ident, P p) { randomId(), i.getPrefix(), i.getMarkers(), + emptyList(), toName, i.getType(), new JavaType.Variable( diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldType.java index 04b518aee9e..41bfa2a812b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldType.java @@ -23,6 +23,8 @@ import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.marker.Markers; +import static java.util.Collections.emptyList; + @EqualsAndHashCode(callSuper = true) @Data public class ChangeFieldType<P> extends JavaIsoVisitor<P> { @@ -49,6 +51,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m new J.Identifier(mv.getTypeExpression().getId(), mv.getTypeExpression().getPrefix(), Markers.EMPTY, + emptyList(), newFieldType.getClassName(), newFieldType, null diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodAccessLevelVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodAccessLevelVisitor.java index 3d3ce563ad4..37ebb0fecb0 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodAccessLevelVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodAccessLevelVisitor.java @@ -70,7 +70,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, P // If current access level is package-private (no modifier), add the new modifier else if (currentMethodAccessLevel == null) { - J.Modifier mod = new J.Modifier(Tree.randomId(), Space.build(" ", emptyList()), Markers.EMPTY, newAccessLevel, Collections.emptyList()); + J.Modifier mod = new J.Modifier(Tree.randomId(), Space.build(" ", emptyList()), Markers.EMPTY, null, newAccessLevel, Collections.emptyList()); m = m.withModifiers(ListUtils.concat(mod, m.getModifiers())); if(method.getModifiers().isEmpty()) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodTargetToStatic.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodTargetToStatic.java index 22038cc890f..bec58575748 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodTargetToStatic.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodTargetToStatic.java @@ -27,6 +27,7 @@ import java.util.LinkedHashSet; import java.util.Set; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; @Value @@ -141,6 +142,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu Space.EMPTY : method.getSelect().getPrefix(), Markers.EMPTY, + emptyList(), classType.getClassName(), classType, null diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodTargetToVariable.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodTargetToVariable.java index c93ad94185b..1f0777b3bf2 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodTargetToVariable.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodTargetToVariable.java @@ -29,6 +29,7 @@ import java.util.LinkedHashSet; import java.util.Set; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; @Value @@ -109,6 +110,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu Space.EMPTY : m.getSelect().getPrefix(), Markers.EMPTY, + emptyList(), variableName, this.variableType, null) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ExtractField.java b/rewrite-java/src/main/java/org/openrewrite/java/ExtractField.java index eac2c6f26b2..3d43710353b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ExtractField.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ExtractField.java @@ -53,7 +53,7 @@ public J visitBlock(J.Block block, P p) { v.getSimpleName(), getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class).getType(), v.getType(), emptyList()))) ) - .withModifiers(singletonList(new J.Modifier(randomId(), Space.EMPTY, Markers.EMPTY, J.Modifier.Type.Private, emptyList()))), + .withModifiers(singletonList(new J.Modifier(randomId(), Space.EMPTY, Markers.EMPTY, null, J.Modifier.Type.Private, emptyList()))), p, getCursor() ); b = b.withStatements(ListUtils.concat(field, b.getStatements())); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java index 9f07fcf7692..799aacb6976 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java @@ -620,6 +620,8 @@ public J visitForEachLoop(ForEachLoop forEachLoop, PrintOutputCapture<P> p) { @Override public J visitIdentifier(Identifier ident, PrintOutputCapture<P> p) { beforeSyntax(ident, Space.Location.IDENTIFIER_PREFIX, p); + visitSpace(Space.EMPTY, Space.Location.ANNOTATIONS, p); + visit(ident.getAnnotations(), p); p.append(ident.getSimpleName()); afterSyntax(ident, p); return ident; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/NoStaticImport.java b/rewrite-java/src/main/java/org/openrewrite/java/NoStaticImport.java index 9df0052afc8..39fcf6c53a8 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/NoStaticImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/NoStaticImport.java @@ -24,6 +24,8 @@ import org.openrewrite.java.tree.Space; import org.openrewrite.marker.Markers; +import static java.util.Collections.emptyList; + @ToString @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter @@ -90,6 +92,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu m = m.withSelect(new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), receiverType.getClassName(), receiverType, null)); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/QualifyThisVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/QualifyThisVisitor.java index 1749ad8ae2e..4adcda701af 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/QualifyThisVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/QualifyThisVisitor.java @@ -24,6 +24,8 @@ import org.openrewrite.java.tree.Space; import org.openrewrite.marker.Markers; +import static java.util.Collections.emptyList; + public class QualifyThisVisitor extends JavaVisitor<ExecutionContext> { @Override public J visitIdentifier(J.Identifier ident, ExecutionContext executionContext) { @@ -39,6 +41,7 @@ public J visitIdentifier(J.Identifier ident, ExecutionContext executionContext) Tree.randomId(), Space.EMPTY, Markers.EMPTY, + emptyList(), type.getClassName(), type, null diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeTree.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeTree.java index e5b938ba294..79ad8b9076e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeTree.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeTree.java @@ -17,8 +17,10 @@ import org.openrewrite.marker.Markers; +import java.util.Collections; import java.util.Scanner; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; /** @@ -58,7 +60,7 @@ static <T extends TypeTree & Expression> T build(String fullyQualifiedName) { if (i == 0) { fullName = part; - expr = new Identifier(randomId(), Space.format(whitespaceBefore.toString()), Markers.EMPTY, part, null, null); + expr = new Identifier(randomId(), Space.format(whitespaceBefore.toString()), Markers.EMPTY, emptyList(), part, null, null); } else { fullName += "." + part; expr = new J.FieldAccess( @@ -72,6 +74,7 @@ static <T extends TypeTree & Expression> T build(String fullyQualifiedName) { randomId(), Space.format(whitespaceBefore.toString()), Markers.EMPTY, + emptyList(), part.trim(), null, null From 713cdb775f3436390a1fd2db18d2fa1b09a9b858 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Fri, 21 Jul 2023 13:01:24 -0700 Subject: [PATCH 088/447] Tweak annotation on identifier parsing/visiting/printing --- .../java/isolated/ReloadableJava11ParserVisitor.java | 4 ++-- .../java/isolated/ReloadableJava17ParserVisitor.java | 4 ++-- .../org/openrewrite/java/ReloadableJava8ParserVisitor.java | 4 ++-- .../src/main/java/org/openrewrite/java/JavaPrinter.java | 2 +- .../src/main/java/org/openrewrite/java/JavaVisitor.java | 1 + 5 files changed, 8 insertions(+), 7 deletions(-) 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 568187e36f8..c48da9444b9 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 @@ -948,10 +948,10 @@ public J visitMethod(MethodTree node, Space fmt) { owner = jcMethod.sym.owner.name.toString(); } name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner), - Markers.EMPTY, emptyList(), owner, null, null), emptyList()); + Markers.EMPTY, emptyList(), owner, null, null), returnType == null ? returnTypeAnnotations : emptyList()); } else { name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(node.getName().toString(), null), Markers.EMPTY, - returnType == null ? returnTypeAnnotations : emptyList(), node.getName().toString(), null, null), emptyList()); + emptyList(), node.getName().toString(), null, null), returnType == null ? returnTypeAnnotations : emptyList()); } Space paramFmt = sourceBefore("("); 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 e700e43157f..32a32893f37 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 @@ -1007,10 +1007,10 @@ public J visitMethod(MethodTree node, Space fmt) { owner = jcMethod.sym.owner.name.toString(); } name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner), - Markers.EMPTY, returnType == null ? returnTypeAnnotations : emptyList(), owner, null, null), emptyList()); + Markers.EMPTY, emptyList(), owner, null, null), returnType == null ? returnTypeAnnotations : emptyList()); } else { name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(node.getName().toString(), null), Markers.EMPTY, - returnType == null ? returnTypeAnnotations : emptyList(), node.getName().toString(), null, null), emptyList()); + emptyList(), node.getName().toString(), null, null), returnType == null ? returnTypeAnnotations : emptyList()); } boolean isCompactConstructor = nodeSym != null && (nodeSym.flags() & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0; 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 868e98f9722..c8c7ac5d89d 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 @@ -947,8 +947,8 @@ public J visitMethod(MethodTree node, Space fmt) { name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner), Markers.EMPTY, emptyList(), owner, null, null), returnType == null ? returnTypeAnnotations : emptyList()); } else { - name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(node.getName().toString()), Markers.EMPTY, - returnType == null ? returnTypeAnnotations : emptyList(), node.getName().toString(), null, null), emptyList()); + name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(node.getName().toString(), null), Markers.EMPTY, + emptyList(), node.getName().toString(), null, null), returnType == null ? returnTypeAnnotations : emptyList()); } Space paramFmt = sourceBefore("("); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java index 799aacb6976..0bbca854c1f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java @@ -619,9 +619,9 @@ public J visitForEachLoop(ForEachLoop forEachLoop, PrintOutputCapture<P> p) { @Override public J visitIdentifier(Identifier ident, PrintOutputCapture<P> p) { + visit(ident.getAnnotations(), p); beforeSyntax(ident, Space.Location.IDENTIFIER_PREFIX, p); visitSpace(Space.EMPTY, Space.Location.ANNOTATIONS, p); - visit(ident.getAnnotations(), p); p.append(ident.getSimpleName()); afterSyntax(ident, p); return ident; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index bfb54a7dba6..e656add186a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -629,6 +629,7 @@ public J visitForControl(J.ForLoop.Control control, P p) { public J visitIdentifier(J.Identifier ident, P p) { J.Identifier i = ident; + i = i.withAnnotations(ListUtils.map(i.getAnnotations(), a -> visitAndCast(a, p))); i = i.withPrefix(visitSpace(i.getPrefix(), Space.Location.IDENTIFIER_PREFIX, p)); i = i.withMarkers(visitMarkers(i.getMarkers(), p)); Expression temp = (Expression) visitExpression(i, p); From 7aa1232e9c0eecf9102f4ed6a13e14d6ef970a82 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Sat, 22 Jul 2023 22:33:18 +0200 Subject: [PATCH 089/447] Restore now deprecated constructors pending module migrations (#3433) * Restore now deprecated constructors pending module migrations * Add all args constructors too --- .../java/org/openrewrite/java/tree/J.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index 312a657bd66..b4bfd2638f8 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -2349,6 +2349,7 @@ public ForLoop withBody(JRightPadded<Statement> body) { @Value @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @AllArgsConstructor @With class Identifier implements J, TypeTree, Expression { @Getter @@ -2371,6 +2372,20 @@ class Identifier implements J, TypeTree, Expression { @Nullable JavaType.Variable fieldType; + /** + * @deprecated Use {@link #Identifier(UUID, Space, Markers, List, String, JavaType, JavaType.Variable)} instead. + */ + @Deprecated + public Identifier(UUID id, Space prefix, Markers markers, String simpleName, @Nullable JavaType type, @Nullable JavaType.Variable fieldType) { + this.id = id; + this.prefix = prefix; + this.markers = markers; + this.annotations = emptyList(); + this.simpleName = simpleName; + this.type = type; + this.fieldType = fieldType; + } + @Override public <P> J acceptJava(JavaVisitor<P> v, P p) { return v.visitIdentifier(this, p); @@ -3784,6 +3799,7 @@ public MethodInvocation withArguments(JContainer<Expression> arguments) { @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @AllArgsConstructor @Data final class Modifier implements J { public static boolean hasModifier(Collection<Modifier> modifiers, Modifier.Type modifier) { @@ -3815,6 +3831,19 @@ public static boolean hasModifier(Collection<Modifier> modifiers, Modifier.Type @Getter List<Annotation> annotations; + /** + * @deprecated Use {@link #Modifier(UUID, Space, Markers, String, Type, List)} instead. + */ + @Deprecated + public Modifier(UUID id, Space prefix, Markers markers, Type type, List<Annotation> annotations) { + this.id = id; + this.prefix = prefix; + this.markers = markers; + this.keyword = null; + this.type = type; + this.annotations = annotations; + } + @Override public String toString() { return type.toString().toLowerCase(); From 0ddbdad5529ea9fc307d02e01a61803a459b90fa Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Sun, 23 Jul 2023 00:37:36 -0500 Subject: [PATCH 090/447] The `classpath` configuration is not in the Gradle project configurations map, so we should just skip updating GAV coordinates for that configuration in particular (#3429) --- .../gradle/UpgradeDependencyVersion.java | 105 +++++++++++------- 1 file changed, 63 insertions(+), 42 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index 773da665eee..8c188c92bc0 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -120,7 +120,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { public J postVisit(J tree, ExecutionContext ctx) { if (tree instanceof JavaSourceFile) { JavaSourceFile cu = (JavaSourceFile) tree; - Map<String, GroupArtifact> variableNames = getCursor().getMessage(VERSION_VARIABLE_KEY); + Map<String, Map<GroupArtifact, Set<String>>> variableNames = getCursor().getMessage(VERSION_VARIABLE_KEY); if (variableNames != null) { Optional<GradleProject> maybeGp = cu.getMarkers() .findFirst(GradleProject.class); @@ -130,7 +130,7 @@ public J postVisit(J tree, ExecutionContext ctx) { cu = (JavaSourceFile) new UpdateVariable(variableNames, maybeGp.get()).visitNonNull(cu, ctx); } - Set<GroupArtifactVersion> versionUpdates = getCursor().getMessage(NEW_VERSION_KEY); + Map<GroupArtifactVersion, Set<String>> versionUpdates = getCursor().getMessage(NEW_VERSION_KEY); if (versionUpdates != null) { Optional<GradleProject> maybeGp = cu.getMarkers() .findFirst(GradleProject.class); @@ -138,8 +138,8 @@ public J postVisit(J tree, ExecutionContext ctx) { return cu; } GradleProject newGp = maybeGp.get(); - for (GroupArtifactVersion gav : versionUpdates) { - newGp = replaceVersion(newGp, ctx, gav); + for (Map.Entry<GroupArtifactVersion, Set<String>> gavToConfigurations : versionUpdates.entrySet()) { + newGp = replaceVersion(newGp, ctx, gavToConfigurations.getKey(), gavToConfigurations.getValue()); } cu = cu.withMarkers(cu.getMarkers().removeByType(GradleProject.class).add(newGp)); } @@ -181,8 +181,10 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte if (dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId())) { String versionVariableName = ((J.Identifier) versionValue.getTree()).getSimpleName(); getCursor().dropParentUntil(p -> p instanceof SourceFile) - .computeMessageIfAbsent(VERSION_VARIABLE_KEY, v -> new HashMap<String, GroupArtifact>()) - .put(versionVariableName, new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); + .computeMessageIfAbsent(VERSION_VARIABLE_KEY, v -> new HashMap<String, Map<GroupArtifact, Set<String>>>()) + .computeIfAbsent(versionVariableName, it -> new HashMap<>()) + .computeIfAbsent(new GroupArtifact(dep.getGroupId(), dep.getArtifactId()), it -> new HashSet<>()) + .add(m.getSimpleName()); } } else if (depArgs.get(0) instanceof J.Literal) { String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); @@ -206,8 +208,9 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte return m; } getCursor().dropParentUntil(p -> p instanceof SourceFile) - .computeMessageIfAbsent(NEW_VERSION_KEY, it -> new LinkedHashSet<GroupArtifactVersion>()) - .add(new GroupArtifactVersion(dep.getGroupId(), dep.getArtifactId(), newVersion)); + .computeMessageIfAbsent(NEW_VERSION_KEY, it -> new HashMap<GroupArtifactVersion, Set<String>>()) + .computeIfAbsent(new GroupArtifactVersion(dep.getGroupId(), dep.getArtifactId(), newVersion), it -> new HashSet<>()) + .add(m.getSimpleName()); return m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { J.Literal literal = (J.Literal) arg; @@ -272,8 +275,10 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte } else if (versionExp instanceof J.Identifier) { String versionVariableName = ((J.Identifier) versionExp).getSimpleName(); getCursor().dropParentUntil(p -> p instanceof SourceFile) - .computeMessageIfAbsent(VERSION_VARIABLE_KEY, v -> new HashMap<String, GroupArtifact>()) - .put(versionVariableName, new GroupArtifact((String) groupLiteral.getValue(), (String) artifactLiteral.getValue())); + .computeMessageIfAbsent(VERSION_VARIABLE_KEY, v -> new HashMap<String, Map<GroupArtifact, Set<String>>>()) + .computeIfAbsent(versionVariableName, it -> new HashMap<>()) + .computeIfAbsent(new GroupArtifact((String) groupLiteral.getValue(), (String) artifactLiteral.getValue()), it -> new HashSet<>()) + .add(m.getSimpleName()); } } @@ -285,18 +290,18 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte @Value @EqualsAndHashCode(callSuper = true) private class UpdateVariable extends GroovyIsoVisitor<ExecutionContext> { - Map<String, GroupArtifact> versionVariableNames; + Map<String, Map<GroupArtifact, Set<String>>> versionVariableNames; GradleProject gradleProject; @Override public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext ctx) { J.VariableDeclarations.NamedVariable v = super.visitVariable(variable, ctx); boolean noneMatch = true; - GroupArtifact ga = null; - for (Map.Entry<String, GroupArtifact> versionVariableNameEntry : versionVariableNames.entrySet()) { + Map<GroupArtifact, Set<String>> gaToConfigurations = null; + for (Map.Entry<String, Map<GroupArtifact, Set<String>>> versionVariableNameEntry : versionVariableNames.entrySet()) { if (versionVariableNameEntry.getKey().equals((v.getSimpleName()))) { noneMatch = false; - ga = versionVariableNameEntry.getValue(); + gaToConfigurations = versionVariableNameEntry.getValue(); break; } } @@ -316,19 +321,23 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations } try { - String newVersion = findNewerProjectDependencyVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); - if (newVersion == null) { - newVersion = findNewerPluginVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); - } - if (newVersion == null) { - return v; - } - getCursor().dropParentUntil(p -> p instanceof SourceFile) - .computeMessageIfAbsent(NEW_VERSION_KEY, m -> new LinkedHashSet<GroupArtifactVersion>()) - .add(new GroupArtifactVersion(ga.getGroupId(), ga.getArtifactId(), newVersion)); + for (Map.Entry<GroupArtifact, Set<String>> gaEntry : gaToConfigurations.entrySet()) { + GroupArtifact ga = gaEntry.getKey(); + String newVersion = findNewerProjectDependencyVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); + if (newVersion == null) { + newVersion = findNewerPluginVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); + } + if (newVersion == null) { + return v; + } + getCursor().dropParentUntil(p -> p instanceof SourceFile) + .computeMessageIfAbsent(NEW_VERSION_KEY, m -> new HashMap<GroupArtifactVersion, Set<String>>()) + .computeIfAbsent(new GroupArtifactVersion(ga.getGroupId(), ga.getArtifactId(), newVersion), it -> new HashSet<>()) + .addAll(gaEntry.getValue()); - J.Literal newVersionLiteral = ChangeStringLiteral.withStringValue(initializer, newVersion); - v = v.withInitializer(newVersionLiteral); + J.Literal newVersionLiteral = ChangeStringLiteral.withStringValue(initializer, newVersion); + v = v.withInitializer(newVersionLiteral); + } } catch (MavenDownloadingException e) { return e.warn(v); } @@ -342,12 +351,12 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ct return a; } J.Identifier identifier = (J.Identifier) a.getVariable(); - GroupArtifact ga = null; + Map<GroupArtifact, Set<String>> gaToConfigurations = null; boolean noneMatch = true; - for (Map.Entry<String, GroupArtifact> versionVariableNameEntry : versionVariableNames.entrySet()) { + for (Map.Entry<String, Map<GroupArtifact, Set<String>>> versionVariableNameEntry : versionVariableNames.entrySet()) { if (versionVariableNameEntry.getKey().equals(identifier.getSimpleName())) { noneMatch = false; - ga = versionVariableNameEntry.getValue(); + gaToConfigurations = versionVariableNameEntry.getValue(); break; } } @@ -367,19 +376,23 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ct } try { - String newVersion = findNewerProjectDependencyVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); - if (newVersion == null) { - newVersion = findNewerPluginVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); - } - if (newVersion == null) { - return a; - } - getCursor().dropParentUntil(p -> p instanceof SourceFile) - .computeMessageIfAbsent(NEW_VERSION_KEY, m -> new LinkedHashSet<GroupArtifactVersion>()) - .add(new GroupArtifactVersion(ga.getGroupId(), ga.getArtifactId(), newVersion)); + for (Map.Entry<GroupArtifact, Set<String>> gaEntry : gaToConfigurations.entrySet()) { + GroupArtifact ga = gaEntry.getKey(); + String newVersion = findNewerProjectDependencyVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); + if (newVersion == null) { + newVersion = findNewerPluginVersion(ga.getGroupId(), ga.getArtifactId(), version, gradleProject, ctx); + } + if (newVersion == null) { + return a; + } + getCursor().dropParentUntil(p -> p instanceof SourceFile) + .computeMessageIfAbsent(NEW_VERSION_KEY, m -> new HashMap<GroupArtifactVersion, Set<String>>()) + .computeIfAbsent(new GroupArtifactVersion(ga.getGroupId(), ga.getArtifactId(), newVersion), it -> new HashSet<>()) + .addAll(gaEntry.getValue()); - J.Literal newVersionLiteral = ChangeStringLiteral.withStringValue(literal, newVersion); - a = a.withAssignment(newVersionLiteral); + J.Literal newVersionLiteral = ChangeStringLiteral.withStringValue(literal, newVersion); + a = a.withAssignment(newVersionLiteral); + } } catch (MavenDownloadingException e) { return e.warn(a); } @@ -401,11 +414,19 @@ private String findNewerProjectDependencyVersion(String groupId, String artifact .orElse(null); } - static GradleProject replaceVersion(GradleProject gp, ExecutionContext ctx, GroupArtifactVersion gav) { + static GradleProject replaceVersion(GradleProject gp, ExecutionContext ctx, GroupArtifactVersion gav, Set<String> configurations) { try { if (gav.getGroupId() == null || gav.getArtifactId() == null) { return gp; } + + Set<String> remainingConfigurations = new HashSet<>(configurations); + remainingConfigurations.remove("classpath"); + + if (remainingConfigurations.isEmpty()) { + return gp; + } + MavenPomDownloader mpd = new MavenPomDownloader(emptyMap(), ctx, null, null); Pom pom = mpd.download(gav, null, null, gp.getMavenRepositories()); ResolvedPom resolvedPom = pom.resolve(emptyList(), mpd, gp.getMavenRepositories(), ctx); From c935581832f361061f1b33b42acd813903cdc15f Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Mon, 24 Jul 2023 11:38:04 +0200 Subject: [PATCH 091/447] Prevent NPE on null Space of unknown origin (#3436) Seen in CLI ``` Running recipe in github.com/Netflix/conductor@main org.openrewrite.internal.RecipeRunException: java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.Space.getComments()" because "space" is null at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:329) at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:184) at org.openrewrite.RecipeScheduler$RecipeRunCycle.lambda$editSources$3(RecipeScheduler.java:231) at io.micrometer.core.instrument.AbstractTimer.recordCallable(AbstractTimer.java:147) at org.openrewrite.table.RecipeRunStats.recordEdit(RecipeRunStats.java:56) at org.openrewrite.RecipeScheduler$RecipeRunCycle.lambda$editSources$4(RecipeScheduler.java:228) at org.openrewrite.RecipeScheduler$RecipeRunCycle.lambda$mapForRecipeRecursively$5(RecipeScheduler.java:330) at io.moderne.serialization.ModerneLargeSourceSet.edit(Unknown Source) at io.moderne.serialization.ModerneLargeSourceSet.edit(Unknown Source) at org.openrewrite.RecipeScheduler$RecipeRunCycle.mapForRecipeRecursively(RecipeScheduler.java:323) at org.openrewrite.RecipeScheduler$RecipeRunCycle.editSources(RecipeScheduler.java:201) at org.openrewrite.RecipeScheduler.scheduleRun(RecipeScheduler.java:76) at org.openrewrite.Recipe.run(Recipe.java:279) at org.openrewrite.Recipe.run(Recipe.java:275) at org.openrewrite.Recipe.run(Recipe.java:271) at io.moderne.modjava.commands.run.Run.call(Run.java:151) at io.moderne.modjava.commands.run.Run.call(Run.java:38) at picocli.CommandLine.executeUserObject(CommandLine.java:2041) at picocli.CommandLine.access$1500(CommandLine.java:148) at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2461) at picocli.CommandLine$RunLast.handle(CommandLine.java:2453) at picocli.CommandLine$RunLast.handle(CommandLine.java:2415) at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2273) at picocli.CommandLine$RunLast.execute(CommandLine.java:2417) at picocli.CommandLine.execute(CommandLine.java:2170) at io.moderne.modjava.ModJava.main(ModJava.java:28) Caused by: java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.Space.getComments()" because "space" is null at org.openrewrite.java.JavaVisitor.visitSpace(JavaVisitor.java:155) at org.openrewrite.java.JavaVisitor.visitRightPadded(JavaVisitor.java:1293) at org.openrewrite.java.JavaVisitor.visitCompilationUnit(JavaVisitor.java:463) at org.openrewrite.java.tree.J$CompilationUnit.acceptJava(J.java:1520) at org.openrewrite.java.tree.J.accept(J.java:59) at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:278) ... 25 more Error: java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.Space.getComments()" because "space" is null org.openrewrite.internal.RecipeRunException: java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.Space.getComments()" because "space" is null at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:329) at io.moderne.serialization.ModerneLargeSourceSet.a(Unknown Source) at io.moderne.serialization.ModerneLargeSourceSet.edit(Unknown Source) at io.moderne.serialization.ModerneLargeSourceSet.edit(Unknown Source) at org.openrewrite.RecipeScheduler$RecipeRunCycle.mapForRecipeRecursively(RecipeScheduler.java:323) at org.openrewrite.RecipeScheduler$RecipeRunCycle.editSources(RecipeScheduler.java:201) at org.openrewrite.RecipeScheduler.scheduleRun(RecipeScheduler.java:76) at org.openrewrite.Recipe.run(Recipe.java:279) at org.openrewrite.Recipe.run(Recipe.java:275) at org.openrewrite.Recipe.run(Recipe.java:271) at io.moderne.modjava.commands.run.Run.call(Run.java:151) at io.moderne.modjava.commands.run.Run.call(Run.java:38) at picocli.CommandLine.executeUserObject(CommandLine.java:2041) at picocli.CommandLine.access$1500(CommandLine.java:148) at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2461) at picocli.CommandLine$RunLast.handle(CommandLine.java:2453) at picocli.CommandLine$RunLast.handle(CommandLine.java:2415) at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2273) at picocli.CommandLine$RunLast.execute(CommandLine.java:2417) at picocli.CommandLine.execute(CommandLine.java:2170) at io.moderne.modjava.ModJava.main(ModJava.java:28) Caused by: java.lang.NullPointerException: Cannot invoke "org.openrewrite.java.tree.Space.getComments()" because "space" is null at org.openrewrite.java.JavaVisitor.visitSpace(JavaVisitor.java:155) at org.openrewrite.java.JavaVisitor.visitRightPadded(JavaVisitor.java:1293) at org.openrewrite.java.JavaVisitor.visitCompilationUnit(JavaVisitor.java:463) at org.openrewrite.java.tree.J$CompilationUnit.acceptJava(J.java:1520) at org.openrewrite.java.tree.J.accept(J.java:59) at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:278) ... 20 more ``` --- .../src/main/java/org/openrewrite/java/JavaVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index e656add186a..c65a51e9ace 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -151,7 +151,7 @@ public J visitStatement(Statement statement, P p) { @SuppressWarnings("unused") public Space visitSpace(Space space, Space.Location loc, P p) { - return space == Space.EMPTY || space == Space.SINGLE_SPACE ? space : + return space == Space.EMPTY || space == Space.SINGLE_SPACE || space == null ? space : space.withComments(ListUtils.map(space.getComments(), comment -> { if (comment instanceof Javadoc) { if (javadocVisitor == null) { From 084fbd8b28e0724d68e837de6baa3d7d96445851 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 24 Jul 2023 11:00:33 -0700 Subject: [PATCH 092/447] Fix serialization issues relating to J.Identifier and J.Modifier backwards-compatibilty constructors --- rewrite-java/build.gradle.kts | 2 +- rewrite-java/src/main/java/org/openrewrite/java/tree/J.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rewrite-java/build.gradle.kts b/rewrite-java/build.gradle.kts index 1e1f3353914..6e0ea97d926 100644 --- a/rewrite-java/build.gradle.kts +++ b/rewrite-java/build.gradle.kts @@ -69,5 +69,5 @@ tasks.withType<Javadoc> { // symbol: method onConstructor_() // location: @interface AllArgsConstructor // 1 error - exclude("**/JavaParser**", "**/ChangeMethodTargetToStatic**") + exclude("**/JavaParser**", "**/ChangeMethodTargetToStatic**", "**/J.java") } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index b4bfd2638f8..540ffe2b6b3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -2349,7 +2349,7 @@ public ForLoop withBody(JRightPadded<Statement> body) { @Value @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) - @AllArgsConstructor + @AllArgsConstructor(onConstructor_ = {@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)} ) @With class Identifier implements J, TypeTree, Expression { @Getter @@ -3799,7 +3799,7 @@ public MethodInvocation withArguments(JContainer<Expression> arguments) { @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) - @AllArgsConstructor + @AllArgsConstructor(onConstructor_ = {@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)} ) @Data final class Modifier implements J { public static boolean hasModifier(Collection<Modifier> modifiers, Modifier.Type modifier) { From e814d86cfa54e268140040325fcbc23f506960a4 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Tue, 25 Jul 2023 22:19:30 +0200 Subject: [PATCH 093/447] Added unpinned version test (#3438) * Added unpinned version test * added overrideManagedVersion parameter * explicity adding the JsonCreator constructor * Marked old constructor as deprecated and using the new one in the tests. * Marked old constructor as deprecated and using the new one in the tests. * fixed behavior for maps * fixed gstring --- .../openrewrite/gradle/ChangeDependency.java | 62 +++++-- .../gradle/ChangeDependencyTest.java | 161 +++++++++++++++++- .../gradle/plugins/ChangePluginTest.java | 2 +- 3 files changed, 201 insertions(+), 24 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index bb1ca307a77..4ccf1fbea6b 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -15,8 +15,8 @@ */ package org.openrewrite.gradle; -import lombok.EqualsAndHashCode; -import lombok.Value; +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.*; import org.openrewrite.*; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; @@ -38,10 +38,7 @@ import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; import org.openrewrite.semver.DependencyMatcher; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import static java.util.Objects.requireNonNull; @@ -90,6 +87,35 @@ public class ChangeDependency extends Recipe { @Nullable String versionPattern; + @Option(displayName = "Override managed version", + description = "If the old dependency has a managed version, this flag can be used to explicitly set the version on the new dependency. " + + "WARNING: No check is done on the NEW dependency to verify if it is managed, it relies on whether the OLD dependency had a managed version. " + + "The default for this flag is `false`.", + required = false) + @Nullable + Boolean overrideManagedVersion; + + + /** + * Keeping this constructor just for compatibility purposes + * @deprecated Use {@link ChangeDependency#ChangeDependency(String, String, String, String, String, String, Boolean)} + */ + @Deprecated + public ChangeDependency(String oldGroupId, String oldArtifactId, @Nullable String newGroupId, @Nullable String newArtifactId, @Nullable String newVersion, @Nullable String versionPattern) { + this(oldGroupId, oldArtifactId, newGroupId, newArtifactId, newVersion, versionPattern, null); + } + + @JsonCreator + public ChangeDependency(String oldGroupId, String oldArtifactId, @Nullable String newGroupId, @Nullable String newArtifactId, @Nullable String newVersion, @Nullable String versionPattern, @Nullable Boolean overrideManagedVersion) { + this.oldGroupId = oldGroupId; + this.oldArtifactId = oldArtifactId; + this.newGroupId = newGroupId; + this.newArtifactId = newArtifactId; + this.newVersion = newVersion; + this.versionPattern = versionPattern; + this.overrideManagedVersion = overrideManagedVersion; + } + @Override public String getDisplayName() { return "Change Gradle dependency"; @@ -157,7 +183,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte if (!StringUtils.isBlank(newArtifactId) && !updated.getArtifactId().equals(newArtifactId)) { updated = updated.withArtifactId(newArtifactId); } - if (!StringUtils.isBlank(newVersion)) { + if (!StringUtils.isBlank(newVersion) && (!StringUtils.isBlank(original.getVersion()) || Boolean.TRUE.equals(overrideManagedVersion))) { List<MavenRepository> repositories = "classpath".equals(m.getSimpleName()) ? gradleProject.getMavenPluginRepositories() : gradleProject.getMavenRepositories(); @@ -179,10 +205,11 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte } } } else if (m.getArguments().get(0) instanceof G.GString) { - List<J> strings = ((G.GString) depArgs.get(0)).getStrings(); - if (strings.size() >= 2 && - strings.get(0) instanceof J.Literal) { - Dependency original = DependencyStringNotationConverter.parse((String) ((J.Literal) strings.get(0)).getValue()); + G.GString gstring = (G.GString) depArgs.get(0); + List<J> strings = gstring.getStrings(); + if (strings.size() >= 2 && strings.get(0) instanceof J.Literal) { + J.Literal literal = (J.Literal) strings.get(0); + Dependency original = DependencyStringNotationConverter.parse((String)literal.getValue()); if (depMatcher.matches(original.getGroupId(), original.getArtifactId())) { Dependency updated = original; if (!StringUtils.isBlank(newGroupId) && !updated.getGroupId().equals(newGroupId)) { @@ -208,10 +235,9 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte } if (original != updated) { String replacement = updated.toStringNotation(); - m = m.withArguments(ListUtils.mapFirst(depArgs, arg -> { - G.GString gString = (G.GString) arg; - return gString.withStrings(ListUtils.mapFirst(gString.getStrings(), l -> ((J.Literal) l).withValue(replacement).withValueSource(replacement))); - })); + J.Literal newLiteral = literal.withValue(replacement) + .withValueSource(gstring.getDelimiter() + replacement + gstring.getDelimiter()); + m = m.withArguments(Collections.singletonList(newLiteral)); } } } @@ -249,7 +275,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte version = valueValue; } } - if (groupId == null || artifactId == null || version == null) { + if (groupId == null || artifactId == null) { return m; } if (!depMatcher.matches(groupId, artifactId)) { @@ -264,7 +290,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte updatedArtifactId = newArtifactId; } String updatedVersion = version; - if (!StringUtils.isBlank(newVersion)) { + if (!StringUtils.isBlank(newVersion) && (!StringUtils.isBlank(version) || Boolean.TRUE.equals(overrideManagedVersion))) { List<MavenRepository> repositories = "classpath".equals(m.getSimpleName()) ? gradleProject.getMavenPluginRepositories() : gradleProject.getMavenRepositories(); @@ -280,7 +306,7 @@ private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionConte } } - if (!updatedGroupId.equals(groupId) || !updatedArtifactId.equals(artifactId) || !updatedVersion.equals(version)) { + if (!updatedGroupId.equals(groupId) || !updatedArtifactId.equals(artifactId) || updatedVersion != null && !updatedVersion.equals(version)) { G.MapEntry finalGroup = groupEntry; String finalGroupIdValue = updatedGroupId; G.MapEntry finalArtifact = artifactEntry; diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java index 01f88f9dd6f..22adbb388a8 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyTest.java @@ -33,7 +33,7 @@ public void defaults(RecipeSpec spec) { @Test void relocateDependency() { rewriteRun( - spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null)), + spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null, null)), buildGradle( """ plugins { @@ -70,7 +70,7 @@ void relocateDependency() { @Test void changeGroupIdOnly() { rewriteRun( - spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", null, null, null)), + spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", null, null, null, null)), buildGradle( """ plugins { @@ -107,7 +107,7 @@ void changeGroupIdOnly() { @Test void changeArtifactIdOnly() { rewriteRun( - spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", null, "commons-lang3", null, null)), + spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", null, "commons-lang3", null, null, null)), buildGradle( """ plugins { @@ -144,7 +144,7 @@ void changeArtifactIdOnly() { @Test void worksWithPlatform() { rewriteRun( - spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null)), + spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null, null)), buildGradle( """ plugins { @@ -176,10 +176,47 @@ implementation platform("org.apache.commons:commons-lang3:3.11") ); } + @Test + void worksWithGString() { + rewriteRun( + spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null, null)), + buildGradle( + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + def version = '2.6' + dependencies { + implementation platform("commons-lang:commons-lang:${version}") + } + """, + """ + plugins { + id "java-library" + } + + repositories { + mavenCentral() + } + + def version = '2.6' + dependencies { + implementation platform("org.apache.commons:commons-lang3:3.11") + } + """ + ) + ); + } + @Test void changeDependencyWithLowerVersionAfter() { rewriteRun( - spec -> spec.recipe(new ChangeDependency("org.openrewrite", "plugin", "io.moderne", "moderne-gradle-plugin", "0.x", null)), + spec -> spec.recipe(new ChangeDependency("org.openrewrite", "plugin", "io.moderne", "moderne-gradle-plugin", "0.x", null, null)), buildGradle( """ buildscript { @@ -206,4 +243,118 @@ void changeDependencyWithLowerVersionAfter() { ) ); } + + @Test + void doNotPinWhenNotVersioned() { + rewriteRun( + spec -> spec.recipe(new ChangeDependency("mysql", "mysql-connector-java", "com.mysql", "mysql-connector-j", "8.0.x", null, null)), + buildGradle( + """ + plugins { + id 'java' + id 'org.springframework.boot' version '2.6.1' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + } + + repositories { + mavenCentral() + } + + dependencies { + runtimeOnly 'mysql:mysql-connector-java' + } + """, + """ + plugins { + id 'java' + id 'org.springframework.boot' version '2.6.1' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + } + + repositories { + mavenCentral() + } + + dependencies { + runtimeOnly 'com.mysql:mysql-connector-j' + } + """) + ); + } + + @Test + void doNotPinWhenNotVersionedOnMap() { + rewriteRun( + spec -> spec.recipe(new ChangeDependency("mysql", "mysql-connector-java", "com.mysql", "mysql-connector-j", "8.0.x", null, null)), + buildGradle( + """ + plugins { + id 'java' + id 'org.springframework.boot' version '2.6.1' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + } + + repositories { + mavenCentral() + } + + dependencies { + runtimeOnly group: 'mysql', name: 'mysql-connector-java' + } + """, + """ + plugins { + id 'java' + id 'org.springframework.boot' version '2.6.1' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + } + + repositories { + mavenCentral() + } + + dependencies { + runtimeOnly group: 'com.mysql', name: 'mysql-connector-j' + } + """) + ); + } + + @Test + void pinWhenOverrideManagedVersion() { + rewriteRun( + spec -> spec.recipe(new ChangeDependency("mysql", "mysql-connector-java", "com.mysql", "mysql-connector-j", "8.0.x", null, true)), + buildGradle( + """ + plugins { + id 'java' + id 'org.springframework.boot' version '2.6.1' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + } + + repositories { + mavenCentral() + } + + dependencies { + runtimeOnly 'mysql:mysql-connector-java' + } + """, + """ + plugins { + id 'java' + id 'org.springframework.boot' version '2.6.1' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + } + + repositories { + mavenCentral() + } + + dependencies { + runtimeOnly 'com.mysql:mysql-connector-j:8.0.33' + } + """) + ); + } } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/ChangePluginTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/ChangePluginTest.java index 25903083eb7..e48559cac13 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/ChangePluginTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/ChangePluginTest.java @@ -68,7 +68,7 @@ void changePlugin() { void changeApplyPluginSyntax() { rewriteRun( spec -> spec.recipes( - new ChangeDependency("org.openrewrite", "plugin", "io.moderne", "moderne-gradle-plugin", "0.x", null), + new ChangeDependency("org.openrewrite", "plugin", "io.moderne", "moderne-gradle-plugin", "0.x", null, null), new ChangePlugin("org.openrewrite.rewrite", "io.moderne.rewrite", null) ), buildGradle( From cd7158f0b458a10b5a3382e44fc5b85bf04aa8a2 Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Tue, 25 Jul 2023 20:31:50 -0500 Subject: [PATCH 094/447] Add plugin block after any buildscript block in `build.gradle` files. --- .../gradle/plugins/AddPluginVisitor.java | 15 +++++++++-- .../gradle/plugins/AddBuildPluginTest.java | 25 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java index bca683349d7..5322b9f312c 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java @@ -161,8 +161,19 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integ return cu.withStatements(ListUtils.concatAll(ListUtils.concat(cu.getStatements().get(0), Space.formatFirstPrefix(statements, leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace()))), Space.formatFirstPrefix(cu.getStatements().subList(1, cu.getStatements().size()), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())))); } else { - return cu.withStatements(ListUtils.concatAll(statements, - Space.formatFirstPrefix(cu.getStatements(), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())))); + int insertAtIdx = 0; + for (int i = 0; i < cu.getStatements().size(); i++) { + Statement statement = cu.getStatements().get(i); + if (statement instanceof J.MethodInvocation && ((J.MethodInvocation) statement).getSimpleName().equals("buildscript")) { + insertAtIdx = i + 1; + break; + } + } + if (insertAtIdx == 0) { + return cu.withStatements(ListUtils.insert(Space.formatFirstPrefix(cu.getStatements(), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())), statements.get(0), insertAtIdx)); + } else { + return cu.withStatements(ListUtils.insertAll(cu.getStatements(), insertAtIdx, Space.formatFirstPrefix(statements, Space.format("\n\n")))); + } } } else { MethodMatcher buildPluginsMatcher = new MethodMatcher("RewriteGradleProject plugins(groovy.lang.Closure)"); diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java index 868f56a6967..5e581723a2d 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java @@ -119,4 +119,29 @@ void addPluginWithVersionToExistingBlock() { ) ); } + + @Test + void addPluginAfterBuildscriptBlock() { + rewriteRun( + spec -> spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null)), + buildGradle( + """ + import java.util.List + + buildscript { + } + """, + """ + import java.util.List + + buildscript { + } + + plugins { + id 'com.jfrog.bintray' version '1.0' + } + """ + ) + ); + } } From c4c7b4c88dad6b429dcda6a44e4d5fd533c484d5 Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Tue, 25 Jul 2023 21:59:31 -0500 Subject: [PATCH 095/447] Add data table to capture Gradle wrappers in use --- .../gradle/search/FindGradleWrapper.java | 11 +++++ .../gradle/table/GradleWrappersInUse.java | 43 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 rewrite-gradle/src/main/java/org/openrewrite/gradle/table/GradleWrappersInUse.java diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleWrapper.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleWrapper.java index ebccb100160..2884fb0534f 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleWrapper.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleWrapper.java @@ -21,6 +21,7 @@ import org.openrewrite.Option; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.gradle.table.GradleWrappersInUse; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.SearchResult; @@ -38,6 +39,8 @@ @Value @EqualsAndHashCode(callSuper = true) public class FindGradleWrapper extends Recipe { + transient GradleWrappersInUse wrappersInUse = new GradleWrappersInUse(this); + private static final Pattern GRADLE_VERSION = Pattern.compile("gradle-(.*?)-(all|bin).zip"); @Option(displayName = "Version expression", @@ -96,6 +99,12 @@ public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { boolean requireVersion = !StringUtils.isNullOrEmpty(version); String currentDistribution = matcher.group(2); boolean requireMeta = !StringUtils.isNullOrEmpty(distribution); + + wrappersInUse.insertRow(ctx, new GradleWrappersInUse.Row( + currentVersion, + currentDistribution + )); + if (requireVersion) { VersionComparator versionComparator = Semver.validate(version, versionPattern).getValue(); if (versionComparator == null || versionComparator.isValid(null, currentVersion)) { @@ -111,6 +120,8 @@ public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { if (currentDistribution.matches(distribution)) { return SearchResult.found(entry); } + } else { + SearchResult.found(entry); } } return entry; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/table/GradleWrappersInUse.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/table/GradleWrappersInUse.java new file mode 100644 index 00000000000..54cb88e95f8 --- /dev/null +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/table/GradleWrappersInUse.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.gradle.table; + +import com.fasterxml.jackson.annotation.JsonIgnoreType; +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +@JsonIgnoreType +public class GradleWrappersInUse extends DataTable<GradleWrappersInUse.Row> { + public GradleWrappersInUse(Recipe recipe) { + super(recipe, Row.class, + GradleWrappersInUse.class.getName(), + "Gradle wrappers in use", + "Gradle wrappers in use."); + } + + @Value + public static class Row { + @Column(displayName = "Wrapper version", + description = "The version of the Gradle wrapper in use.") + String version; + + @Column(displayName = "Wrapper distribution", + description = "The distribution type of the Gradle wrapper in use.") + String distribution; + } +} From c0336273cef08caaf59af6fdbec8911f0d1a4a5c Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Wed, 26 Jul 2023 11:20:08 -0500 Subject: [PATCH 096/447] Add test case for correctness in finding any Gradle wrapper --- .../gradle/search/FindGradleWrapper.java | 2 +- .../gradle/search/FindGradleWrapperTest.java | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleWrapper.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleWrapper.java index 2884fb0534f..e78c9758d57 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleWrapper.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleWrapper.java @@ -121,7 +121,7 @@ public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { return SearchResult.found(entry); } } else { - SearchResult.found(entry); + return SearchResult.found(entry); } } return entry; diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindGradleWrapperTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindGradleWrapperTest.java index f4d0e5ef765..049117d3a32 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindGradleWrapperTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindGradleWrapperTest.java @@ -94,4 +94,28 @@ void findGradleWrapperVersionAndDistribution() { ) ); } + + @Test + void findWrapperDefaults() { + rewriteRun( + spec -> spec.recipe(new FindGradleWrapper(null, null, null)), + properties( + """ + distributionBase=GRADLE_USER_HOME + distributionPath=wrapper/dists + distributionUrl=https\\\\://services.gradle.org/distributions/gradle-7.4-all.zip + zipStoreBase=GRADLE_USER_HOME + zipStorePath=wrapper/dists + """, + """ + distributionBase=GRADLE_USER_HOME + distributionPath=wrapper/dists + ~~>distributionUrl=https\\\\://services.gradle.org/distributions/gradle-7.4-all.zip + zipStoreBase=GRADLE_USER_HOME + zipStorePath=wrapper/dists + """, + spec -> spec.path("gradle/wrapper/gradle-wrapper.properties") + ) + ); + } } From c6aea31a94ab572a12b05c4c193a8be89ffc20e4 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 24 Jul 2023 21:34:47 -0700 Subject: [PATCH 097/447] WIP --- .../org/openrewrite/internal/StringUtils.java | 33 ++ .../yaml/FormatPreservingReader.java | 47 ++ .../java/org/openrewrite/yaml/YamlParser.java | 435 +++++++----------- .../yaml/internal/YamlPrinter.java | 5 +- .../org/openrewrite/yaml/YamlParserTest.java | 26 +- .../openrewrite/yaml/tree/MappingTest.java | 2 +- .../openrewrite/yaml/tree/SequenceTest.java | 40 +- 7 files changed, 297 insertions(+), 291 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java index dc7fd5cc668..e7311dd8446 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java @@ -745,4 +745,37 @@ public static int indexOfNextNonWhitespace(int cursor, String source) { } return delimIndex; } + + /** + * Considering "#" to initiate a comment that lasts until a "\n" is seen, return the index of the next non-comment, + * non-whitespace character. + * + * Yaml, Properties, and Python use this comment style. + * + * @param cursor + * @param source + * @return + */ + public static int indexOfNextNonWhitespaceHashComments(int cursor, String source) { + boolean inSingleLineComment = false; + + int delimIndex = cursor; + for (; delimIndex < source.length(); delimIndex++) { + if (inSingleLineComment) { + if (source.charAt(delimIndex) == '\n') { + inSingleLineComment = false; + } + } else if (source.length() > delimIndex + 1 && source.charAt(delimIndex) == '#') { + inSingleLineComment = true; + delimIndex++; + continue; + } + if (!inSingleLineComment) { + if (!Character.isWhitespace(source.substring(delimIndex, delimIndex + 1).charAt(0))) { + return delimIndex; + } + } + } + return -1; + } } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java index 3d1dd62b121..9b40e3efd77 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java @@ -87,6 +87,53 @@ public int read(@NonNull char[] cbuf, int off, int len) throws IOException { return read; } + /** + * Processes a stream of bytes, and returns a Stream of Unicode codepoints + * associated with the characters derived from that byte stream. + * + * @param bais ByteArrayInputStream to be processed. + * @return A stream of Unicode codepoints derived from UTF-8 characters in the supplied stream. + */ +// private static Stream<Integer> processByteStream(ByteArrayInputStream bais) { +// +// int nextByte = 0; +// byte b = 0; +// byte[] utf8Bytes = null; +// int byteCount = 0; +// List<Integer> codePoints = new ArrayList<>(); +// +// while ((nextByte = bais.read()) != -1) { +// b = (byte) nextByte; +// byteCount = Main.getByteCount(b); +// utf8Bytes = new byte[byteCount]; +// utf8Bytes[0] = (byte) nextByte; +// for (int i = 1; i < byteCount; i++) { // Get any subsequent bytes for this UTF-8 character. +// nextByte = bais.read(); +// utf8Bytes[i] = (byte) nextByte; +// } +// int codePoint = new String(utf8Bytes, StandardCharsets.UTF_8).codePointAt(0); +// codePoints.add(codePoint); +// } +// return codePoints.stream(); +// } + + /** + * Returns the number of bytes in a UTF-8 character based on the bit pattern + * of the supplied byte. The only valid values are 1, 2 3 or 4. If the + * byte has an invalid bit pattern an IllegalArgumentException is thrown. + * + * @param b The first byte of a UTF-8 character. + * @return The number of bytes for this UTF-* character. + * @throws IllegalArgumentException if the bit pattern is invalid. + */ + private static int getByteCount(byte b) throws IllegalArgumentException { + if ((b >= 0)) return 1; // Pattern is 0xxxxxxx. + if ((b >= (byte) 0b11000000) && (b <= (byte) 0b11011111)) return 2; // Pattern is 110xxxxx. + if ((b >= (byte) 0b11100000) && (b <= (byte) 0b11101111)) return 3; // Pattern is 1110xxxx. + if ((b >= (byte) 0b11110000) && (b <= (byte) 0b11110111)) return 4; // Pattern is 11110xxx. + throw new IllegalArgumentException(); // Invalid first byte for UTF-8 character. + } + @Override public void close() throws IOException { delegate.close(); diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index 5de0129d96f..abe6e1ba27c 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -15,11 +15,12 @@ */ package org.openrewrite.yaml; -import lombok.Getter; +import lombok.*; import org.intellij.lang.annotations.Language; import org.openrewrite.*; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; import org.openrewrite.tree.ParseError; @@ -35,9 +36,7 @@ import org.yaml.snakeyaml.scanner.Scanner; import org.yaml.snakeyaml.scanner.ScannerImpl; -import java.io.IOException; import java.io.StringReader; -import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.*; import java.util.regex.Matcher; @@ -47,6 +46,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.openrewrite.Tree.randomId; +import static org.openrewrite.internal.StringUtils.indexOfNextNonWhitespaceHashComments; public class YamlParser implements org.openrewrite.Parser { private static final Pattern VARIABLE_PATTERN = Pattern.compile(":\\s*(@[^\n\r@]+@)"); @@ -71,7 +71,6 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat return ParseError.build(this, sourceFile, relativeTo, ctx, t); } }) - .map(this::unwrapPrefixedMappings) .map(sourceFile -> { if (sourceFile instanceof Yaml.Documents) { Yaml.Documents docs = (Yaml.Documents) sourceFile; @@ -86,103 +85,122 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat }); } - private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStream source) { - String yamlSource = source.readFully(); + private int cursor = 0; + private String source = ""; + private String whitespace() { + int lastIndex = source.length() -1; + int start = Math.min(lastIndex, cursor); + int i = Math.min(lastIndex, indexOfNextNonWhitespaceHashComments(start, source)); + String whitespace; + if(i == -1) { + whitespace = source.substring(cursor); + cursor = lastIndex; + } else { + whitespace = source.substring(start, i); + cursor += whitespace.length(); + } + return whitespace; + } + private boolean isNext(char c) { + int lastIndex = source.length() - 1; + boolean result = source.charAt(Math.min(lastIndex, cursor)) == c; + if(result) { + cursor++; + } + return result; + } + private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStream is) { + cursor = 0; + source = is.readFully(); Map<String, String> variableByUuid = new HashMap<>(); StringBuilder yamlSourceWithVariablePlaceholders = new StringBuilder(); - Matcher variableMatcher = VARIABLE_PATTERN.matcher(yamlSource); + Matcher variableMatcher = VARIABLE_PATTERN.matcher(source); int pos = 0; - while (pos < yamlSource.length() && variableMatcher.find(pos)) { - yamlSourceWithVariablePlaceholders.append(yamlSource, pos, variableMatcher.start(1)); + while (pos < source.length() && variableMatcher.find(pos)) { + yamlSourceWithVariablePlaceholders.append(source, pos, variableMatcher.start(1)); String uuid = UUID.randomUUID().toString(); variableByUuid.put(uuid, variableMatcher.group(1)); yamlSourceWithVariablePlaceholders.append(uuid); pos = variableMatcher.end(1); } - if (pos < yamlSource.length() - 1) { - yamlSourceWithVariablePlaceholders.append(yamlSource, pos, yamlSource.length()); + if (pos < source.length() - 1) { + yamlSourceWithVariablePlaceholders.append(source, pos, source.length()); } - try (StringReader stringReader = new StringReader(yamlSourceWithVariablePlaceholders.toString()); - FormatPreservingReader reader = new FormatPreservingReader(stringReader)) { - StreamReader streamReader = new StreamReader(reader); + try (StringReader stringReader = new StringReader(yamlSourceWithVariablePlaceholders.toString())) { + StreamReader streamReader = new StreamReader(stringReader); Scanner scanner = new ScannerImpl(streamReader, new LoaderOptions()); Parser parser = new ParserImpl(scanner); - int lastEnd = 0; - List<Yaml.Document> documents = new ArrayList<>(); // https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases, section: 3.2.2.2. Anchors and Aliases. // An anchor key should always replace the previous value, since an alias refers to the most recent anchor key. Map<String, Yaml.Anchor> anchors = new HashMap<>(); Yaml.Document document = null; Stack<BlockBuilder> blockStack = new Stack<>(); - String newLine = ""; for (Event event = parser.getEvent(); event != null; event = parser.getEvent()) { + System.out.println(event.getEventId().name()); switch (event.getEventId()) { case DocumentEnd: { - String fmt = newLine + reader.prefix(lastEnd, event); - newLine = ""; - assert document != null; documents.add(document.withEnd(new Yaml.Document.End( randomId(), - fmt, + whitespace(), Markers.EMPTY, ((DocumentEndEvent) event).getExplicit() ))); - lastEnd = event.getEndMark().getIndex(); break; } case DocumentStart: { - String fmt = newLine + reader.prefix(lastEnd, event); - newLine = ""; document = new Yaml.Document( randomId(), - fmt, + whitespace(), Markers.EMPTY, ((DocumentStartEvent) event).getExplicit(), new Yaml.Mapping(randomId(), Markers.EMPTY, null, emptyList(), null, null), null ); - lastEnd = event.getEndMark().getIndex(); break; } case MappingStart: { - String fmt = newLine + reader.prefix(lastEnd, event); - newLine = ""; - + String fmt = whitespace(); + boolean dash = isNext('-'); // mapping may be within a sequence MappingStartEvent mappingStartEvent = (MappingStartEvent) event; Yaml.Anchor anchor = null; if (mappingStartEvent.getAnchor() != null) { - anchor = buildYamlAnchor(reader, lastEnd, fmt, mappingStartEvent.getAnchor(), event.getEndMark().getIndex(), false); - anchors.put(mappingStartEvent.getAnchor(), anchor); - - lastEnd = lastEnd + mappingStartEvent.getAnchor().length() + fmt.length() + 1; - fmt = reader.readStringFromBuffer(lastEnd, event.getEndMark().getIndex()); - int dashPrefixIndex = commentAwareIndexOf('-', fmt); - if (dashPrefixIndex > -1) { - fmt = fmt.substring(0, dashPrefixIndex); - } + //TODO + throw new RuntimeException("not implemented"); +// String fmt = whitespace(); +// anchor = buildYamlAnchor(source, cursor, fmt, mappingStartEvent.getAnchor(), event.getEndMark().getIndex(), false); +// anchors.put(mappingStartEvent.getAnchor(), anchor); +// +// cursor += mappingStartEvent.getAnchor().length(); +// fmt = source.substring(cursor, event.getEndMark().getIndex()); +// int dashPrefixIndex = commentAwareIndexOf('-', fmt); +// if (dashPrefixIndex > -1) { +// fmt = fmt.substring(0, dashPrefixIndex); +// } } - String fullPrefix = reader.readStringFromBuffer(lastEnd, event.getEndMark().getIndex() - 1); String startBracePrefix = null; - int openingBraceIndex = commentAwareIndexOf('{', fullPrefix); - if (openingBraceIndex != -1) { - int startIndex = commentAwareIndexOf(':', fullPrefix) + 1; - startBracePrefix = fullPrefix.substring(startIndex, openingBraceIndex); - lastEnd = event.getEndMark().getIndex(); + if(isNext('{')) { + startBracePrefix = fmt; + fmt = ""; } - blockStack.push(new MappingBuilder(fmt, startBracePrefix, anchor)); + + blockStack.push(new MappingBuilder(dash, fmt, startBracePrefix, anchor)); break; } case Scalar: { - String fmt = newLine + reader.prefix(lastEnd, event); - newLine = ""; + String fmt = whitespace(); + String dashPrefix = null; + if(isNext('-')) { + dashPrefix = fmt; + fmt = whitespace(); + } ScalarEvent scalar = (ScalarEvent) event; String scalarValue = scalar.getValue(); @@ -192,9 +210,14 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr Yaml.Anchor anchor = null; if (scalar.getAnchor() != null) { - anchor = buildYamlAnchor(reader, lastEnd, fmt, scalar.getAnchor(), event.getEndMark().getIndex(), true); + int saveCursor = cursor; + cursor += scalar.getAnchor().length() + 1; + anchor = new Yaml.Anchor(randomId(), "", whitespace(), Markers.EMPTY, scalar.getAnchor()); anchors.put(scalar.getAnchor(), anchor); + cursor = saveCursor; } + // Use event length rather than scalarValue.length() to account for escape characters and quotes + cursor += event.getEndMark().getIndex() - event.getStartMark().getIndex(); Yaml.Scalar.Style style; switch (scalar.getScalarStyle()) { @@ -205,40 +228,33 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr style = Yaml.Scalar.Style.SINGLE_QUOTED; break; case LITERAL: + //TODO style = Yaml.Scalar.Style.LITERAL; - scalarValue = reader.readStringFromBuffer(event.getStartMark().getIndex() + 1, event.getEndMark().getIndex() - 1); + scalarValue = source.substring(event.getStartMark().getIndex() + 1, event.getEndMark().getIndex() - 1); if (scalarValue.endsWith("\n")) { - newLine = "\n"; +// newLine = "\n"; scalarValue = scalarValue.substring(0, scalarValue.length() - 1); } break; case FOLDED: style = Yaml.Scalar.Style.FOLDED; - scalarValue = reader.readStringFromBuffer(event.getStartMark().getIndex() + 1, event.getEndMark().getIndex() - 1); + scalarValue = source.substring(event.getStartMark().getIndex() + 1, event.getEndMark().getIndex() - 1); break; case PLAIN: default: style = Yaml.Scalar.Style.PLAIN; break; } - BlockBuilder builder = blockStack.isEmpty() ? null : blockStack.peek(); - if (builder instanceof SequenceBuilder) { - // Inline sequences like [1, 2] need to keep track of any whitespace between the element - // and its trailing comma. - SequenceBuilder sequenceBuilder = (SequenceBuilder) builder; - String betweenEvents = reader.readStringFromBuffer(event.getEndMark().getIndex(), parser.peekEvent().getStartMark().getIndex() - 1); - int commaIndex = commentAwareIndexOf(',', betweenEvents); - String commaPrefix = null; - if (commaIndex != -1) { - commaPrefix = betweenEvents.substring(0, commaIndex); + BlockBuilder builder = blockStack.isEmpty() ? null : blockStack.peek(); + if (builder != null) { + int saveCursor = cursor; + String commaPrefix = whitespace(); + if(!isNext(',')) { + cursor = saveCursor; + commaPrefix = null; } - lastEnd = event.getEndMark().getIndex() + commaIndex + 1; - sequenceBuilder.push(new Yaml.Scalar(randomId(), fmt, Markers.EMPTY, style, anchor, scalarValue), commaPrefix); - - } else if (builder != null) { - builder.push(new Yaml.Scalar(randomId(), fmt, Markers.EMPTY, style, anchor, scalarValue)); - lastEnd = event.getEndMark().getIndex(); + builder.push(dashPrefix, new Yaml.Scalar(randomId(), fmt, Markers.EMPTY, style, anchor, scalarValue), commaPrefix); } break; } @@ -248,61 +264,54 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr if (mappingOrSequence instanceof Yaml.Sequence) { Yaml.Sequence seq = (Yaml.Sequence) mappingOrSequence; if (seq.getOpeningBracketPrefix() != null) { - String s = reader.readStringFromBuffer(lastEnd, event.getStartMark().getIndex()); - int closingBracketIndex = commentAwareIndexOf(']', s); - lastEnd = lastEnd + closingBracketIndex + 1; - mappingOrSequence = seq.withClosingBracketPrefix(s.substring(0, closingBracketIndex)); + mappingOrSequence = seq.withClosingBracketPrefix(whitespace()); + cursor++; } } if (mappingOrSequence instanceof Yaml.Mapping) { Yaml.Mapping map = (Yaml.Mapping) mappingOrSequence; if (map.getOpeningBracePrefix() != null) { - String s = reader.readStringFromBuffer(lastEnd, event.getStartMark().getIndex()); - int closingBraceIndex = commentAwareIndexOf('}', s); - lastEnd = lastEnd + closingBraceIndex + 1; - mappingOrSequence = map.withClosingBracePrefix(s.substring(0, closingBraceIndex)); + mappingOrSequence = map.withClosingBracePrefix(whitespace()); + cursor++; } } if (blockStack.isEmpty()) { assert document != null; document = document.withBlock(mappingOrSequence); } else { - blockStack.peek().push(mappingOrSequence); + blockStack.peek().push(null, mappingOrSequence, null); } break; } case SequenceStart: { - String fmt = newLine + reader.prefix(lastEnd, event); - newLine = ""; - + String fmt = whitespace(); + boolean dash = isNext('-'); SequenceStartEvent sse = (SequenceStartEvent) event; Yaml.Anchor anchor = null; if (sse.getAnchor() != null) { - anchor = buildYamlAnchor(reader, lastEnd, fmt, sse.getAnchor(), event.getEndMark().getIndex(), false); - anchors.put(sse.getAnchor(), anchor); - - lastEnd = lastEnd + sse.getAnchor().length() + fmt.length() + 1; - fmt = reader.readStringFromBuffer(lastEnd, event.getEndMark().getIndex()); - int dashPrefixIndex = commentAwareIndexOf('-', fmt); - if (dashPrefixIndex > -1) { - fmt = fmt.substring(0, dashPrefixIndex); - } + throw new RuntimeException("TODO"); +// anchor = buildYamlAnchor(source, cursor, fmt, sse.getAnchor(), event.getEndMark().getIndex(), false); +// anchors.put(sse.getAnchor(), anchor); +// +// cursor = cursor + sse.getAnchor().length() + fmt.length() + 1; +// fmt = source.substring(cursor, event.getEndMark().getIndex()); +// int dashPrefixIndex = commentAwareIndexOf('-', fmt); +// if (dashPrefixIndex > -1) { +// fmt = fmt.substring(0, dashPrefixIndex); +// } } - String fullPrefix = reader.readStringFromBuffer(lastEnd, event.getEndMark().getIndex()); String startBracketPrefix = null; - int openingBracketIndex = commentAwareIndexOf('[', fullPrefix); - if (openingBracketIndex != -1) { - int startIndex = commentAwareIndexOf(':', fullPrefix) + 1; - startBracketPrefix = fullPrefix.substring(startIndex, openingBracketIndex); - lastEnd = event.getEndMark().getIndex(); + if(isNext('[')) { + startBracketPrefix = fmt; + fmt = ""; } - blockStack.push(new SequenceBuilder(fmt, startBracketPrefix, anchor)); + + blockStack.push(new SequenceBuilder(dash, fmt, startBracketPrefix, anchor)); break; } case Alias: { - String fmt = newLine + reader.prefix(lastEnd, event); - newLine = ""; + String fmt = whitespace(); AliasEvent alias = (AliasEvent) event; Yaml.Anchor anchor = anchors.get(alias.getAnchor()); @@ -310,12 +319,12 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr throw new UnsupportedOperationException("Unknown anchor: " + alias.getAnchor()); } BlockBuilder builder = blockStack.peek(); - builder.push(new Yaml.Alias(randomId(), fmt, Markers.EMPTY, anchor)); - lastEnd = event.getEndMark().getIndex(); + cursor += alias.getAnchor().length() + 1; + builder.push(null, new Yaml.Alias(randomId(), fmt, Markers.EMPTY, anchor), null); break; } case StreamEnd: { - String fmt = newLine + reader.prefix(lastEnd, event); + String fmt = whitespace(); if (document == null && !fmt.isEmpty()) { documents.add( new Yaml.Document( @@ -331,55 +340,11 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr } } - return new Yaml.Documents(randomId(), Markers.EMPTY, sourceFile, FileAttributes.fromPath(sourceFile), source.getCharset().name(), source.isCharsetBomMarked(), null, documents); - } catch (IOException e) { - throw new UncheckedIOException(e); + return new Yaml.Documents(randomId(), Markers.EMPTY, sourceFile, FileAttributes.fromPath(sourceFile), + is.getCharset().name(), is.isCharsetBomMarked(), null, documents); } } - private Yaml.Anchor buildYamlAnchor(FormatPreservingReader reader, int lastEnd, String eventPrefix, String anchorKey, int eventEndIndex, boolean isForScalar) { - int anchorLength = isForScalar ? anchorKey.length() + 1 : anchorKey.length(); - String whitespaceAndScalar = reader.prefix( - lastEnd + eventPrefix.length() + anchorLength, eventEndIndex); - - StringBuilder postFix = new StringBuilder(); - for (char c : whitespaceAndScalar.toCharArray()) { - if (c != ' ' && c != '\t') { - break; - } - postFix.append(c); - } - - int prefixStart = commentAwareIndexOf(':', eventPrefix); - String prefix = ""; - if (!isForScalar) { - prefix = (prefixStart > -1 && eventPrefix.length() > prefixStart + 1) ? eventPrefix.substring(prefixStart + 1) : ""; - } - return new Yaml.Anchor(randomId(), prefix, postFix.toString(), Markers.EMPTY, anchorKey); - } - - /** - * Return the index of the target character if it appears in a non-comment portion of the String, or -1 if it does not appear. - */ - private static int commentAwareIndexOf(char target, String s) { - boolean inComment = false; - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (inComment) { - if (c == '\n') { - inComment = false; - } - } else { - if (c == target) { - return i; - } else if (c == '#') { - inComment = true; - } - } - } - return -1; - } - @Override public boolean accept(Path path) { String fileName = path.toString(); @@ -393,11 +358,11 @@ public Path sourcePathFromSourceText(Path prefix, String sourceCode) { private interface BlockBuilder { Yaml.Block build(); - - void push(Yaml.Block block); + void push(@Nullable String dashPrefix, Yaml.Block block, @Nullable String commaPrefix); } - private static class MappingBuilder implements BlockBuilder { + private class MappingBuilder implements BlockBuilder { + boolean dash; private final String prefix; @Nullable @@ -411,47 +376,57 @@ private static class MappingBuilder implements BlockBuilder { @Nullable private YamlKey key; - private MappingBuilder(String prefix, @Nullable String startBracePrefix, @Nullable Yaml.Anchor anchor) { + private String keyValueSeparatorPrefix = ""; + + private MappingBuilder(boolean dash, String prefix, @Nullable String startBracePrefix, @Nullable Yaml.Anchor anchor) { + this.dash = dash; this.prefix = prefix; this.startBracePrefix = startBracePrefix; this.anchor = anchor; } @Override - public void push(Yaml.Block block) { - if (key == null && block instanceof Yaml.Scalar) { - key = (Yaml.Scalar) block; - } else if (key == null && block instanceof Yaml.Alias) { - key = (Yaml.Alias) block; + public void push(@Nullable String dashPrefix, Yaml.Block block, @Nullable String commaPrefix) { + if (key == null && (block instanceof Yaml.Scalar || block instanceof Yaml.Alias)) { + key = (YamlKey) block; + keyValueSeparatorPrefix = whitespace(); + cursor += 1; // pass over ':' } else { - String keySuffix = block.getPrefix(); - block = block.withPrefix(keySuffix.substring(commentAwareIndexOf(':', keySuffix) + 1)); - - // Begin moving whitespace from the key to the entry that contains the key - String originalKeyPrefix = key.getPrefix(); + if(key == null) { + throw new IllegalStateException("Expected key to be set"); + } + // Follow the convention of placing whitespace on the outermost possible element + String entryPrefix; + if(entries.isEmpty()) { + // By convention Yaml.Mapping have no prefix of their own, donating it to their first entry + entryPrefix = prefix; + if(dash) { + // If this mapping is part of a sequence, put the dash _back_ into its prefix for now. + // SequenceBuilder will remove it later and put it in the appropriate place. + entryPrefix += '-'; + } + entryPrefix += key.getPrefix(); + } else { + entryPrefix = key.getPrefix(); + } key = key.withPrefix(""); - - // When a dash, indicating the beginning of a sequence, is present whitespace before it will be handled by the SequenceEntry - // Similarly if the prefix includes a ':', it will be owned by the mapping that contains this mapping - // So this entry's prefix begins after any such delimiter - int entryPrefixStartIndex = Math.max( - commentAwareIndexOf('-', originalKeyPrefix), - commentAwareIndexOf(':', originalKeyPrefix)) + 1; - String entryPrefix = originalKeyPrefix.substring(entryPrefixStartIndex); - String beforeMappingValueIndicator = keySuffix.substring(0, - Math.max(commentAwareIndexOf(':', keySuffix), 0)); - entries.add(new Yaml.Mapping.Entry(randomId(), entryPrefix, Markers.EMPTY, key, beforeMappingValueIndicator, block)); + Yaml.Mapping.Entry entry = new Yaml.Mapping.Entry(randomId(), entryPrefix, Markers.EMPTY, key, keyValueSeparatorPrefix, block); + entries.add(entry); key = null; } } @Override - public MappingWithPrefix build() { - return new MappingWithPrefix(prefix, startBracePrefix, entries, null, anchor); + public Yaml.Mapping build() { + return new Yaml.Mapping(randomId(), Markers.EMPTY, startBracePrefix, entries, null, anchor); } } - private static class SequenceBuilder implements BlockBuilder { + @SuppressWarnings("InnerClassMayBeStatic") + private class SequenceBuilder implements BlockBuilder { + + private final boolean dash; + private final String prefix; @Nullable private final String startBracketPrefix; @@ -461,106 +436,46 @@ private static class SequenceBuilder implements BlockBuilder { private final List<Yaml.Sequence.Entry> entries = new ArrayList<>(); - private SequenceBuilder(String prefix, @Nullable String startBracketPrefix, @Nullable Yaml.Anchor anchor) { + private SequenceBuilder(@Nullable boolean dash, String prefix, @Nullable String startBracketPrefix, @Nullable Yaml.Anchor anchor) { + this.dash = dash; this.prefix = prefix; this.startBracketPrefix = startBracketPrefix; this.anchor = anchor; } @Override - public void push(Yaml.Block block) { - push(block, null); - } - - public void push(Yaml.Block block, @Nullable String commaPrefix) { - String rawPrefix = block.getPrefix(); - int dashIndex = commentAwareIndexOf('-', rawPrefix); - String entryPrefix; - String blockPrefix; - boolean hasDash = dashIndex != -1; - if (hasDash) { - entryPrefix = rawPrefix.substring(0, dashIndex); - blockPrefix = rawPrefix.substring(dashIndex + 1); + public void push(@Nullable String dashPrefix, Yaml.Block block, @Nullable String commaPrefix) { + String entryPrefix = ""; + boolean thisDash = dash; + if(dashPrefix == null) { + // If the dash has ended up inside the entryPrefix of the element, bring it back out + if(block instanceof Yaml.Mapping) { + Yaml.Mapping mapping = (Yaml.Mapping) block; + Yaml.Mapping.Entry entry = mapping.getEntries().get(0); + String mappingEntryPrefix = entry.getPrefix(); + int maybeDashIndex = StringUtils.indexOfNextNonWhitespaceHashComments(0, mappingEntryPrefix); + if(maybeDashIndex >= 0 && mappingEntryPrefix.charAt(maybeDashIndex) == '-') { + thisDash = true; + entryPrefix = mappingEntryPrefix.substring(0, maybeDashIndex); + String afterDash = mappingEntryPrefix.substring(maybeDashIndex + 1); + block = mapping.withEntries(ListUtils.concat(entry.withPrefix(afterDash), mapping.getEntries().subList(1, mapping.getEntries().size()))); + } + } } else { - entryPrefix = ""; - blockPrefix = rawPrefix; + entryPrefix = dashPrefix; } - entries.add(new Yaml.Sequence.Entry(randomId(), entryPrefix, Markers.EMPTY, block.withPrefix(blockPrefix), hasDash, commaPrefix)); - } - - @Override - public SequenceWithPrefix build() { - return new SequenceWithPrefix(prefix, startBracketPrefix, entries, null, anchor); - } - } - - @Getter - private static class MappingWithPrefix extends Yaml.Mapping { - private String prefix; - - public MappingWithPrefix(String prefix, @Nullable String startBracePrefix, List<Yaml.Mapping.Entry> entries, @Nullable String endBracePrefix, @Nullable Anchor anchor) { - super(randomId(), Markers.EMPTY, startBracePrefix, entries, endBracePrefix, anchor); - this.prefix = prefix; - } - - @Override - public Mapping withPrefix(String prefix) { - this.prefix = prefix; - return this; - } - } - - @Getter - private static class SequenceWithPrefix extends Yaml.Sequence { - private String prefix; - - public SequenceWithPrefix(String prefix, @Nullable String startBracketPrefix, List<Yaml.Sequence.Entry> entries, @Nullable String endBracketPrefix, @Nullable Anchor anchor) { - super(randomId(), Markers.EMPTY, startBracketPrefix, entries, endBracketPrefix, anchor); - this.prefix = prefix; + if(entries.isEmpty()) { + entryPrefix = prefix + entryPrefix; + } + entries.add(new Yaml.Sequence.Entry(randomId(), entryPrefix, Markers.EMPTY, block, thisDash, commaPrefix)); } @Override - public Sequence withPrefix(String prefix) { - this.prefix = prefix; - return this; + public Yaml.Sequence build() { + return new Yaml.Sequence(randomId(), Markers.EMPTY, startBracketPrefix, entries, null, anchor); } } - private SourceFile unwrapPrefixedMappings(SourceFile y) { - if (!(y instanceof Yaml.Documents)) { - return y; - } - //noinspection ConstantConditions - return (Yaml.Documents) new YamlIsoVisitor<Integer>() { - @Override - public Yaml.Sequence visitSequence(Yaml.Sequence sequence, Integer p) { - if (sequence instanceof SequenceWithPrefix) { - SequenceWithPrefix sequenceWithPrefix = (SequenceWithPrefix) sequence; - return super.visitSequence( - new Yaml.Sequence( - sequenceWithPrefix.getId(), - sequenceWithPrefix.getMarkers(), - sequenceWithPrefix.getOpeningBracketPrefix(), - ListUtils.mapFirst(sequenceWithPrefix.getEntries(), e -> e.withPrefix(sequenceWithPrefix.getPrefix())), - sequenceWithPrefix.getClosingBracketPrefix(), - sequenceWithPrefix.getAnchor() - ), p); - } - return super.visitSequence(sequence, p); - } - - @Override - public Yaml.Mapping visitMapping(Yaml.Mapping mapping, Integer p) { - if (mapping instanceof MappingWithPrefix) { - MappingWithPrefix mappingWithPrefix = (MappingWithPrefix) mapping; - return super.visitMapping(new Yaml.Mapping(mappingWithPrefix.getId(), - mappingWithPrefix.getMarkers(), mappingWithPrefix.getOpeningBracePrefix(), mappingWithPrefix.getEntries(), null, mappingWithPrefix.getAnchor()), p); - } - return super.visitMapping(mapping, p); - } - }.visit(y, 0); - } - public static Builder builder() { return new Builder(); } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java index 9a0426a9a2b..6ee5318d36f 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java @@ -94,7 +94,7 @@ public Yaml visitMappingEntry(Yaml.Mapping.Entry entry, PrintOutputCapture<P> p) @Override public Yaml visitMapping(Yaml.Mapping mapping, PrintOutputCapture<P> p) { - visitMarkers(mapping.getMarkers(), p); + beforeSyntax(mapping, p); if (mapping.getAnchor() != null) { visit(mapping.getAnchor(), p); } @@ -161,8 +161,7 @@ public Yaml visitScalar(Yaml.Scalar scalar, PrintOutputCapture<P> p) { } public Yaml visitAnchor(Yaml.Anchor anchor, PrintOutputCapture<P> p) { - visitMarkers(anchor.getMarkers(), p); - p.append(anchor.getPrefix()); + beforeSyntax(anchor, p); p.append("&"); p.append(anchor.getKey()); p.append(anchor.getPostfix()); diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java index 00f2fa0995d..231b8e38930 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java @@ -16,18 +16,16 @@ package org.openrewrite.yaml; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import org.openrewrite.SourceFile; -import org.openrewrite.tree.ParseError; +import org.openrewrite.test.RewriteTest; import org.openrewrite.yaml.tree.Yaml; import java.util.List; -import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.yaml.Assertions.yaml; -class YamlParserTest { +class YamlParserTest implements RewriteTest { @Test void ascii() { @@ -47,15 +45,13 @@ void ascii() { assertThat(title.getValue()).isEqualTo("b"); } - @ParameterizedTest - @ValueSource(strings = { - "🛠", - "🛠🛠", - "🛠 🛠" - }) - void unicodeParseError(String input ) { - Stream<SourceFile> yamlSources = YamlParser.builder().build().parse("a: %s\n".formatted(input)); - assertThat(yamlSources).singleElement().isInstanceOf(ParseError.class); + @Test + void unicodeCharacterSpanningMultipleBytes() { + rewriteRun( + yaml(""" + # 🛠 + 🛠: "🛠" + """) + ); } - } diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java index c6b78abe98c..91c38b1d961 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java @@ -42,7 +42,7 @@ void flowStyleMapping() { """ { "data": { - "prometheus.yml": "global:\\n scrape_interval: 10s\\n scrape_timeout: 9s" + "prometheus.yml" : "global:\\n scrape_interval: 10s\\n scrape_timeout: 9s" } } """ diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java index 5c7ed8316f9..1737ea4206a 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java @@ -28,7 +28,9 @@ void blockSequence() { rewriteRun( yaml( """ - - apples + + - apples # apple + # orange - oranges """, spec -> spec.afterRecipe(y -> { @@ -46,10 +48,10 @@ void blockSequenceOfMappings() { rewriteRun( yaml( """ - - name: Fred - age: 45 - - name: Barney - age: 25 + - name: Fred + age: 45 # Fred is 45 years old + - name: Barney + age: 25 """ ) ); @@ -61,15 +63,21 @@ void multiLineInlineSequenceWithFunnyIndentation() { yaml( """ [ - a, - b, - c, + a , + b , + c , ] """ ) ); } + @Test + void emptySequence() { + rewriteRun(yaml(" [ ] ")); + } + + @Test void sequenceOfEmptyInlineSequence() { rewriteRun(yaml("- []")); @@ -110,13 +118,21 @@ void sequenceOfMixedSequences() { """ - [] - [ 1 ] - - foo: [] - - bar: - - baz: [ - a] """ ) ); +// rewriteRun( +// yaml( +// """ +// - [] +// - [ 1 ] +// - foo: [] +// - bar: +// - baz: [ +// a] +// """ +// ) +// ); } @Test From 2b9bb72692ea434d31de194cb30e1c2b50fc3cc4 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 26 Jul 2023 11:15:28 -0700 Subject: [PATCH 098/447] Fix LstProvenanceTable description --- .../main/java/org/openrewrite/table/LstProvenanceTable.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java index 9d37fb9e084..a1991b7c954 100644 --- a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java +++ b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java @@ -27,8 +27,8 @@ public class LstProvenanceTable extends DataTable<LstProvenanceTable.Row> { public LstProvenanceTable(Recipe recipe) { super(recipe, - "Parser failures", - "A list of files that failed to parse along with stack traces of their failures."); + "LST Provenance", + "Table showing which tools were used to produce LSTs."); } @Value From 77c03a0bf4dcceb3c8bd11bfaf4d5a9014e4f7a0 Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Wed, 26 Jul 2023 14:38:54 -0500 Subject: [PATCH 099/447] Apply user-defined styles to plugin applications --- .../gradle/plugins/AddPluginVisitor.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java index 5322b9f312c..76ab0355250 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java @@ -138,7 +138,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integ }.visitCompilationUnit(cu, 0); String delimiter = singleQuote.get() < doubleQuote.get() ? "\"" : "'"; - List<Statement> statements = GradleParser.builder().build() + Statement statement = GradleParser.builder().build() .parseInputs( singletonList( Parser.Input.fromString("plugins {\n" + @@ -150,7 +150,8 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integ .findFirst() .map(G.CompilationUnit.class::cast) .orElseThrow(() -> new IllegalArgumentException("Could not parse")) - .getStatements(); + .getStatements() + .get(0); if (FindMethods.find(cu, "RewriteGradleProject plugins(..)").isEmpty() && FindMethods.find(cu, "RewriteSettings plugins(..)").isEmpty()) { Space leadingSpace = Space.firstPrefix(cu.getStatements()); @@ -158,21 +159,20 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integ && !cu.getStatements().isEmpty() && cu.getStatements().get(0) instanceof J.MethodInvocation && ((J.MethodInvocation) cu.getStatements().get(0)).getSimpleName().equals("pluginManagement")) { - return cu.withStatements(ListUtils.concatAll(ListUtils.concat(cu.getStatements().get(0), Space.formatFirstPrefix(statements, leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace()))), - Space.formatFirstPrefix(cu.getStatements().subList(1, cu.getStatements().size()), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())))); + return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), 1)); } else { int insertAtIdx = 0; for (int i = 0; i < cu.getStatements().size(); i++) { - Statement statement = cu.getStatements().get(i); - if (statement instanceof J.MethodInvocation && ((J.MethodInvocation) statement).getSimpleName().equals("buildscript")) { + Statement existingStatement = cu.getStatements().get(i); + if (existingStatement instanceof J.MethodInvocation && ((J.MethodInvocation) existingStatement).getSimpleName().equals("buildscript")) { insertAtIdx = i + 1; break; } } if (insertAtIdx == 0) { - return cu.withStatements(ListUtils.insert(Space.formatFirstPrefix(cu.getStatements(), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())), statements.get(0), insertAtIdx)); + return cu.withStatements(ListUtils.insert(Space.formatFirstPrefix(cu.getStatements(), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())), autoFormat(statement, ctx, getCursor()), insertAtIdx)); } else { - return cu.withStatements(ListUtils.insertAll(cu.getStatements(), insertAtIdx, Space.formatFirstPrefix(statements, Space.format("\n\n")))); + return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), insertAtIdx)); } } } else { @@ -182,7 +182,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integ if (stat instanceof J.MethodInvocation) { J.MethodInvocation m = (J.MethodInvocation) stat; if (buildPluginsMatcher.matches(m) || settingsPluginsMatcher.matches(m)) { - J.MethodInvocation pluginDef = (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) ((J.MethodInvocation) statements.get(0)).getArguments().get(0)).getBody()).getStatements().get(0)).getExpression(); + J.MethodInvocation pluginDef = (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) ((J.MethodInvocation) autoFormat(statement, ctx, getCursor())).getArguments().get(0)).getBody()).getStatements().get(0)).getExpression(); m = m.withArguments(ListUtils.map(m.getArguments(), a -> { if (a instanceof J.Lambda) { J.Lambda l = (J.Lambda) a; From 094bce49f5a13b89af5164c0007c26dcab4e95c7 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 26 Jul 2023 13:57:46 -0700 Subject: [PATCH 100/447] Revert "WIP" This reverts commit c6aea31a94ab572a12b05c4c193a8be89ffc20e4. --- .../org/openrewrite/internal/StringUtils.java | 33 -- .../yaml/FormatPreservingReader.java | 47 -- .../java/org/openrewrite/yaml/YamlParser.java | 435 +++++++++++------- .../yaml/internal/YamlPrinter.java | 5 +- .../org/openrewrite/yaml/YamlParserTest.java | 26 +- .../openrewrite/yaml/tree/MappingTest.java | 2 +- .../openrewrite/yaml/tree/SequenceTest.java | 40 +- 7 files changed, 291 insertions(+), 297 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java index e7311dd8446..dc7fd5cc668 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java @@ -745,37 +745,4 @@ public static int indexOfNextNonWhitespace(int cursor, String source) { } return delimIndex; } - - /** - * Considering "#" to initiate a comment that lasts until a "\n" is seen, return the index of the next non-comment, - * non-whitespace character. - * - * Yaml, Properties, and Python use this comment style. - * - * @param cursor - * @param source - * @return - */ - public static int indexOfNextNonWhitespaceHashComments(int cursor, String source) { - boolean inSingleLineComment = false; - - int delimIndex = cursor; - for (; delimIndex < source.length(); delimIndex++) { - if (inSingleLineComment) { - if (source.charAt(delimIndex) == '\n') { - inSingleLineComment = false; - } - } else if (source.length() > delimIndex + 1 && source.charAt(delimIndex) == '#') { - inSingleLineComment = true; - delimIndex++; - continue; - } - if (!inSingleLineComment) { - if (!Character.isWhitespace(source.substring(delimIndex, delimIndex + 1).charAt(0))) { - return delimIndex; - } - } - } - return -1; - } } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java index 9b40e3efd77..3d1dd62b121 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java @@ -87,53 +87,6 @@ public int read(@NonNull char[] cbuf, int off, int len) throws IOException { return read; } - /** - * Processes a stream of bytes, and returns a Stream of Unicode codepoints - * associated with the characters derived from that byte stream. - * - * @param bais ByteArrayInputStream to be processed. - * @return A stream of Unicode codepoints derived from UTF-8 characters in the supplied stream. - */ -// private static Stream<Integer> processByteStream(ByteArrayInputStream bais) { -// -// int nextByte = 0; -// byte b = 0; -// byte[] utf8Bytes = null; -// int byteCount = 0; -// List<Integer> codePoints = new ArrayList<>(); -// -// while ((nextByte = bais.read()) != -1) { -// b = (byte) nextByte; -// byteCount = Main.getByteCount(b); -// utf8Bytes = new byte[byteCount]; -// utf8Bytes[0] = (byte) nextByte; -// for (int i = 1; i < byteCount; i++) { // Get any subsequent bytes for this UTF-8 character. -// nextByte = bais.read(); -// utf8Bytes[i] = (byte) nextByte; -// } -// int codePoint = new String(utf8Bytes, StandardCharsets.UTF_8).codePointAt(0); -// codePoints.add(codePoint); -// } -// return codePoints.stream(); -// } - - /** - * Returns the number of bytes in a UTF-8 character based on the bit pattern - * of the supplied byte. The only valid values are 1, 2 3 or 4. If the - * byte has an invalid bit pattern an IllegalArgumentException is thrown. - * - * @param b The first byte of a UTF-8 character. - * @return The number of bytes for this UTF-* character. - * @throws IllegalArgumentException if the bit pattern is invalid. - */ - private static int getByteCount(byte b) throws IllegalArgumentException { - if ((b >= 0)) return 1; // Pattern is 0xxxxxxx. - if ((b >= (byte) 0b11000000) && (b <= (byte) 0b11011111)) return 2; // Pattern is 110xxxxx. - if ((b >= (byte) 0b11100000) && (b <= (byte) 0b11101111)) return 3; // Pattern is 1110xxxx. - if ((b >= (byte) 0b11110000) && (b <= (byte) 0b11110111)) return 4; // Pattern is 11110xxx. - throw new IllegalArgumentException(); // Invalid first byte for UTF-8 character. - } - @Override public void close() throws IOException { delegate.close(); diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index abe6e1ba27c..5de0129d96f 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -15,12 +15,11 @@ */ package org.openrewrite.yaml; -import lombok.*; +import lombok.Getter; import org.intellij.lang.annotations.Language; import org.openrewrite.*; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; import org.openrewrite.tree.ParseError; @@ -36,7 +35,9 @@ import org.yaml.snakeyaml.scanner.Scanner; import org.yaml.snakeyaml.scanner.ScannerImpl; +import java.io.IOException; import java.io.StringReader; +import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.*; import java.util.regex.Matcher; @@ -46,7 +47,6 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.openrewrite.Tree.randomId; -import static org.openrewrite.internal.StringUtils.indexOfNextNonWhitespaceHashComments; public class YamlParser implements org.openrewrite.Parser { private static final Pattern VARIABLE_PATTERN = Pattern.compile(":\\s*(@[^\n\r@]+@)"); @@ -71,6 +71,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat return ParseError.build(this, sourceFile, relativeTo, ctx, t); } }) + .map(this::unwrapPrefixedMappings) .map(sourceFile -> { if (sourceFile instanceof Yaml.Documents) { Yaml.Documents docs = (Yaml.Documents) sourceFile; @@ -85,122 +86,103 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat }); } - private int cursor = 0; - private String source = ""; - private String whitespace() { - int lastIndex = source.length() -1; - int start = Math.min(lastIndex, cursor); - int i = Math.min(lastIndex, indexOfNextNonWhitespaceHashComments(start, source)); - String whitespace; - if(i == -1) { - whitespace = source.substring(cursor); - cursor = lastIndex; - } else { - whitespace = source.substring(start, i); - cursor += whitespace.length(); - } - return whitespace; - } - private boolean isNext(char c) { - int lastIndex = source.length() - 1; - boolean result = source.charAt(Math.min(lastIndex, cursor)) == c; - if(result) { - cursor++; - } - return result; - } - private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStream is) { - cursor = 0; - source = is.readFully(); + private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStream source) { + String yamlSource = source.readFully(); Map<String, String> variableByUuid = new HashMap<>(); StringBuilder yamlSourceWithVariablePlaceholders = new StringBuilder(); - Matcher variableMatcher = VARIABLE_PATTERN.matcher(source); + Matcher variableMatcher = VARIABLE_PATTERN.matcher(yamlSource); int pos = 0; - while (pos < source.length() && variableMatcher.find(pos)) { - yamlSourceWithVariablePlaceholders.append(source, pos, variableMatcher.start(1)); + while (pos < yamlSource.length() && variableMatcher.find(pos)) { + yamlSourceWithVariablePlaceholders.append(yamlSource, pos, variableMatcher.start(1)); String uuid = UUID.randomUUID().toString(); variableByUuid.put(uuid, variableMatcher.group(1)); yamlSourceWithVariablePlaceholders.append(uuid); pos = variableMatcher.end(1); } - if (pos < source.length() - 1) { - yamlSourceWithVariablePlaceholders.append(source, pos, source.length()); + if (pos < yamlSource.length() - 1) { + yamlSourceWithVariablePlaceholders.append(yamlSource, pos, yamlSource.length()); } - try (StringReader stringReader = new StringReader(yamlSourceWithVariablePlaceholders.toString())) { - StreamReader streamReader = new StreamReader(stringReader); + try (StringReader stringReader = new StringReader(yamlSourceWithVariablePlaceholders.toString()); + FormatPreservingReader reader = new FormatPreservingReader(stringReader)) { + StreamReader streamReader = new StreamReader(reader); Scanner scanner = new ScannerImpl(streamReader, new LoaderOptions()); Parser parser = new ParserImpl(scanner); + int lastEnd = 0; + List<Yaml.Document> documents = new ArrayList<>(); // https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases, section: 3.2.2.2. Anchors and Aliases. // An anchor key should always replace the previous value, since an alias refers to the most recent anchor key. Map<String, Yaml.Anchor> anchors = new HashMap<>(); Yaml.Document document = null; Stack<BlockBuilder> blockStack = new Stack<>(); + String newLine = ""; for (Event event = parser.getEvent(); event != null; event = parser.getEvent()) { - System.out.println(event.getEventId().name()); switch (event.getEventId()) { case DocumentEnd: { + String fmt = newLine + reader.prefix(lastEnd, event); + newLine = ""; + assert document != null; documents.add(document.withEnd(new Yaml.Document.End( randomId(), - whitespace(), + fmt, Markers.EMPTY, ((DocumentEndEvent) event).getExplicit() ))); + lastEnd = event.getEndMark().getIndex(); break; } case DocumentStart: { + String fmt = newLine + reader.prefix(lastEnd, event); + newLine = ""; document = new Yaml.Document( randomId(), - whitespace(), + fmt, Markers.EMPTY, ((DocumentStartEvent) event).getExplicit(), new Yaml.Mapping(randomId(), Markers.EMPTY, null, emptyList(), null, null), null ); + lastEnd = event.getEndMark().getIndex(); break; } case MappingStart: { - String fmt = whitespace(); - boolean dash = isNext('-'); // mapping may be within a sequence + String fmt = newLine + reader.prefix(lastEnd, event); + newLine = ""; + MappingStartEvent mappingStartEvent = (MappingStartEvent) event; Yaml.Anchor anchor = null; if (mappingStartEvent.getAnchor() != null) { - //TODO - throw new RuntimeException("not implemented"); -// String fmt = whitespace(); -// anchor = buildYamlAnchor(source, cursor, fmt, mappingStartEvent.getAnchor(), event.getEndMark().getIndex(), false); -// anchors.put(mappingStartEvent.getAnchor(), anchor); -// -// cursor += mappingStartEvent.getAnchor().length(); -// fmt = source.substring(cursor, event.getEndMark().getIndex()); -// int dashPrefixIndex = commentAwareIndexOf('-', fmt); -// if (dashPrefixIndex > -1) { -// fmt = fmt.substring(0, dashPrefixIndex); -// } + anchor = buildYamlAnchor(reader, lastEnd, fmt, mappingStartEvent.getAnchor(), event.getEndMark().getIndex(), false); + anchors.put(mappingStartEvent.getAnchor(), anchor); + + lastEnd = lastEnd + mappingStartEvent.getAnchor().length() + fmt.length() + 1; + fmt = reader.readStringFromBuffer(lastEnd, event.getEndMark().getIndex()); + int dashPrefixIndex = commentAwareIndexOf('-', fmt); + if (dashPrefixIndex > -1) { + fmt = fmt.substring(0, dashPrefixIndex); + } } + String fullPrefix = reader.readStringFromBuffer(lastEnd, event.getEndMark().getIndex() - 1); String startBracePrefix = null; - if(isNext('{')) { - startBracePrefix = fmt; - fmt = ""; + int openingBraceIndex = commentAwareIndexOf('{', fullPrefix); + if (openingBraceIndex != -1) { + int startIndex = commentAwareIndexOf(':', fullPrefix) + 1; + startBracePrefix = fullPrefix.substring(startIndex, openingBraceIndex); + lastEnd = event.getEndMark().getIndex(); } - - blockStack.push(new MappingBuilder(dash, fmt, startBracePrefix, anchor)); + blockStack.push(new MappingBuilder(fmt, startBracePrefix, anchor)); break; } case Scalar: { - String fmt = whitespace(); - String dashPrefix = null; - if(isNext('-')) { - dashPrefix = fmt; - fmt = whitespace(); - } + String fmt = newLine + reader.prefix(lastEnd, event); + newLine = ""; ScalarEvent scalar = (ScalarEvent) event; String scalarValue = scalar.getValue(); @@ -210,14 +192,9 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr Yaml.Anchor anchor = null; if (scalar.getAnchor() != null) { - int saveCursor = cursor; - cursor += scalar.getAnchor().length() + 1; - anchor = new Yaml.Anchor(randomId(), "", whitespace(), Markers.EMPTY, scalar.getAnchor()); + anchor = buildYamlAnchor(reader, lastEnd, fmt, scalar.getAnchor(), event.getEndMark().getIndex(), true); anchors.put(scalar.getAnchor(), anchor); - cursor = saveCursor; } - // Use event length rather than scalarValue.length() to account for escape characters and quotes - cursor += event.getEndMark().getIndex() - event.getStartMark().getIndex(); Yaml.Scalar.Style style; switch (scalar.getScalarStyle()) { @@ -228,33 +205,40 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr style = Yaml.Scalar.Style.SINGLE_QUOTED; break; case LITERAL: - //TODO style = Yaml.Scalar.Style.LITERAL; - scalarValue = source.substring(event.getStartMark().getIndex() + 1, event.getEndMark().getIndex() - 1); + scalarValue = reader.readStringFromBuffer(event.getStartMark().getIndex() + 1, event.getEndMark().getIndex() - 1); if (scalarValue.endsWith("\n")) { -// newLine = "\n"; + newLine = "\n"; scalarValue = scalarValue.substring(0, scalarValue.length() - 1); } break; case FOLDED: style = Yaml.Scalar.Style.FOLDED; - scalarValue = source.substring(event.getStartMark().getIndex() + 1, event.getEndMark().getIndex() - 1); + scalarValue = reader.readStringFromBuffer(event.getStartMark().getIndex() + 1, event.getEndMark().getIndex() - 1); break; case PLAIN: default: style = Yaml.Scalar.Style.PLAIN; break; } - BlockBuilder builder = blockStack.isEmpty() ? null : blockStack.peek(); - if (builder != null) { - int saveCursor = cursor; - String commaPrefix = whitespace(); - if(!isNext(',')) { - cursor = saveCursor; - commaPrefix = null; + if (builder instanceof SequenceBuilder) { + // Inline sequences like [1, 2] need to keep track of any whitespace between the element + // and its trailing comma. + SequenceBuilder sequenceBuilder = (SequenceBuilder) builder; + String betweenEvents = reader.readStringFromBuffer(event.getEndMark().getIndex(), parser.peekEvent().getStartMark().getIndex() - 1); + int commaIndex = commentAwareIndexOf(',', betweenEvents); + String commaPrefix = null; + if (commaIndex != -1) { + commaPrefix = betweenEvents.substring(0, commaIndex); + } - builder.push(dashPrefix, new Yaml.Scalar(randomId(), fmt, Markers.EMPTY, style, anchor, scalarValue), commaPrefix); + lastEnd = event.getEndMark().getIndex() + commaIndex + 1; + sequenceBuilder.push(new Yaml.Scalar(randomId(), fmt, Markers.EMPTY, style, anchor, scalarValue), commaPrefix); + + } else if (builder != null) { + builder.push(new Yaml.Scalar(randomId(), fmt, Markers.EMPTY, style, anchor, scalarValue)); + lastEnd = event.getEndMark().getIndex(); } break; } @@ -264,54 +248,61 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr if (mappingOrSequence instanceof Yaml.Sequence) { Yaml.Sequence seq = (Yaml.Sequence) mappingOrSequence; if (seq.getOpeningBracketPrefix() != null) { - mappingOrSequence = seq.withClosingBracketPrefix(whitespace()); - cursor++; + String s = reader.readStringFromBuffer(lastEnd, event.getStartMark().getIndex()); + int closingBracketIndex = commentAwareIndexOf(']', s); + lastEnd = lastEnd + closingBracketIndex + 1; + mappingOrSequence = seq.withClosingBracketPrefix(s.substring(0, closingBracketIndex)); } } if (mappingOrSequence instanceof Yaml.Mapping) { Yaml.Mapping map = (Yaml.Mapping) mappingOrSequence; if (map.getOpeningBracePrefix() != null) { - mappingOrSequence = map.withClosingBracePrefix(whitespace()); - cursor++; + String s = reader.readStringFromBuffer(lastEnd, event.getStartMark().getIndex()); + int closingBraceIndex = commentAwareIndexOf('}', s); + lastEnd = lastEnd + closingBraceIndex + 1; + mappingOrSequence = map.withClosingBracePrefix(s.substring(0, closingBraceIndex)); } } if (blockStack.isEmpty()) { assert document != null; document = document.withBlock(mappingOrSequence); } else { - blockStack.peek().push(null, mappingOrSequence, null); + blockStack.peek().push(mappingOrSequence); } break; } case SequenceStart: { - String fmt = whitespace(); - boolean dash = isNext('-'); + String fmt = newLine + reader.prefix(lastEnd, event); + newLine = ""; + SequenceStartEvent sse = (SequenceStartEvent) event; Yaml.Anchor anchor = null; if (sse.getAnchor() != null) { - throw new RuntimeException("TODO"); -// anchor = buildYamlAnchor(source, cursor, fmt, sse.getAnchor(), event.getEndMark().getIndex(), false); -// anchors.put(sse.getAnchor(), anchor); -// -// cursor = cursor + sse.getAnchor().length() + fmt.length() + 1; -// fmt = source.substring(cursor, event.getEndMark().getIndex()); -// int dashPrefixIndex = commentAwareIndexOf('-', fmt); -// if (dashPrefixIndex > -1) { -// fmt = fmt.substring(0, dashPrefixIndex); -// } + anchor = buildYamlAnchor(reader, lastEnd, fmt, sse.getAnchor(), event.getEndMark().getIndex(), false); + anchors.put(sse.getAnchor(), anchor); + + lastEnd = lastEnd + sse.getAnchor().length() + fmt.length() + 1; + fmt = reader.readStringFromBuffer(lastEnd, event.getEndMark().getIndex()); + int dashPrefixIndex = commentAwareIndexOf('-', fmt); + if (dashPrefixIndex > -1) { + fmt = fmt.substring(0, dashPrefixIndex); + } } + String fullPrefix = reader.readStringFromBuffer(lastEnd, event.getEndMark().getIndex()); String startBracketPrefix = null; - if(isNext('[')) { - startBracketPrefix = fmt; - fmt = ""; + int openingBracketIndex = commentAwareIndexOf('[', fullPrefix); + if (openingBracketIndex != -1) { + int startIndex = commentAwareIndexOf(':', fullPrefix) + 1; + startBracketPrefix = fullPrefix.substring(startIndex, openingBracketIndex); + lastEnd = event.getEndMark().getIndex(); } - - blockStack.push(new SequenceBuilder(dash, fmt, startBracketPrefix, anchor)); + blockStack.push(new SequenceBuilder(fmt, startBracketPrefix, anchor)); break; } case Alias: { - String fmt = whitespace(); + String fmt = newLine + reader.prefix(lastEnd, event); + newLine = ""; AliasEvent alias = (AliasEvent) event; Yaml.Anchor anchor = anchors.get(alias.getAnchor()); @@ -319,12 +310,12 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr throw new UnsupportedOperationException("Unknown anchor: " + alias.getAnchor()); } BlockBuilder builder = blockStack.peek(); - cursor += alias.getAnchor().length() + 1; - builder.push(null, new Yaml.Alias(randomId(), fmt, Markers.EMPTY, anchor), null); + builder.push(new Yaml.Alias(randomId(), fmt, Markers.EMPTY, anchor)); + lastEnd = event.getEndMark().getIndex(); break; } case StreamEnd: { - String fmt = whitespace(); + String fmt = newLine + reader.prefix(lastEnd, event); if (document == null && !fmt.isEmpty()) { documents.add( new Yaml.Document( @@ -340,11 +331,55 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr } } - return new Yaml.Documents(randomId(), Markers.EMPTY, sourceFile, FileAttributes.fromPath(sourceFile), - is.getCharset().name(), is.isCharsetBomMarked(), null, documents); + return new Yaml.Documents(randomId(), Markers.EMPTY, sourceFile, FileAttributes.fromPath(sourceFile), source.getCharset().name(), source.isCharsetBomMarked(), null, documents); + } catch (IOException e) { + throw new UncheckedIOException(e); } } + private Yaml.Anchor buildYamlAnchor(FormatPreservingReader reader, int lastEnd, String eventPrefix, String anchorKey, int eventEndIndex, boolean isForScalar) { + int anchorLength = isForScalar ? anchorKey.length() + 1 : anchorKey.length(); + String whitespaceAndScalar = reader.prefix( + lastEnd + eventPrefix.length() + anchorLength, eventEndIndex); + + StringBuilder postFix = new StringBuilder(); + for (char c : whitespaceAndScalar.toCharArray()) { + if (c != ' ' && c != '\t') { + break; + } + postFix.append(c); + } + + int prefixStart = commentAwareIndexOf(':', eventPrefix); + String prefix = ""; + if (!isForScalar) { + prefix = (prefixStart > -1 && eventPrefix.length() > prefixStart + 1) ? eventPrefix.substring(prefixStart + 1) : ""; + } + return new Yaml.Anchor(randomId(), prefix, postFix.toString(), Markers.EMPTY, anchorKey); + } + + /** + * Return the index of the target character if it appears in a non-comment portion of the String, or -1 if it does not appear. + */ + private static int commentAwareIndexOf(char target, String s) { + boolean inComment = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (inComment) { + if (c == '\n') { + inComment = false; + } + } else { + if (c == target) { + return i; + } else if (c == '#') { + inComment = true; + } + } + } + return -1; + } + @Override public boolean accept(Path path) { String fileName = path.toString(); @@ -358,11 +393,11 @@ public Path sourcePathFromSourceText(Path prefix, String sourceCode) { private interface BlockBuilder { Yaml.Block build(); - void push(@Nullable String dashPrefix, Yaml.Block block, @Nullable String commaPrefix); + + void push(Yaml.Block block); } - private class MappingBuilder implements BlockBuilder { - boolean dash; + private static class MappingBuilder implements BlockBuilder { private final String prefix; @Nullable @@ -376,57 +411,47 @@ private class MappingBuilder implements BlockBuilder { @Nullable private YamlKey key; - private String keyValueSeparatorPrefix = ""; - - private MappingBuilder(boolean dash, String prefix, @Nullable String startBracePrefix, @Nullable Yaml.Anchor anchor) { - this.dash = dash; + private MappingBuilder(String prefix, @Nullable String startBracePrefix, @Nullable Yaml.Anchor anchor) { this.prefix = prefix; this.startBracePrefix = startBracePrefix; this.anchor = anchor; } @Override - public void push(@Nullable String dashPrefix, Yaml.Block block, @Nullable String commaPrefix) { - if (key == null && (block instanceof Yaml.Scalar || block instanceof Yaml.Alias)) { - key = (YamlKey) block; - keyValueSeparatorPrefix = whitespace(); - cursor += 1; // pass over ':' + public void push(Yaml.Block block) { + if (key == null && block instanceof Yaml.Scalar) { + key = (Yaml.Scalar) block; + } else if (key == null && block instanceof Yaml.Alias) { + key = (Yaml.Alias) block; } else { - if(key == null) { - throw new IllegalStateException("Expected key to be set"); - } - // Follow the convention of placing whitespace on the outermost possible element - String entryPrefix; - if(entries.isEmpty()) { - // By convention Yaml.Mapping have no prefix of their own, donating it to their first entry - entryPrefix = prefix; - if(dash) { - // If this mapping is part of a sequence, put the dash _back_ into its prefix for now. - // SequenceBuilder will remove it later and put it in the appropriate place. - entryPrefix += '-'; - } - entryPrefix += key.getPrefix(); - } else { - entryPrefix = key.getPrefix(); - } + String keySuffix = block.getPrefix(); + block = block.withPrefix(keySuffix.substring(commentAwareIndexOf(':', keySuffix) + 1)); + + // Begin moving whitespace from the key to the entry that contains the key + String originalKeyPrefix = key.getPrefix(); key = key.withPrefix(""); - Yaml.Mapping.Entry entry = new Yaml.Mapping.Entry(randomId(), entryPrefix, Markers.EMPTY, key, keyValueSeparatorPrefix, block); - entries.add(entry); + + // When a dash, indicating the beginning of a sequence, is present whitespace before it will be handled by the SequenceEntry + // Similarly if the prefix includes a ':', it will be owned by the mapping that contains this mapping + // So this entry's prefix begins after any such delimiter + int entryPrefixStartIndex = Math.max( + commentAwareIndexOf('-', originalKeyPrefix), + commentAwareIndexOf(':', originalKeyPrefix)) + 1; + String entryPrefix = originalKeyPrefix.substring(entryPrefixStartIndex); + String beforeMappingValueIndicator = keySuffix.substring(0, + Math.max(commentAwareIndexOf(':', keySuffix), 0)); + entries.add(new Yaml.Mapping.Entry(randomId(), entryPrefix, Markers.EMPTY, key, beforeMappingValueIndicator, block)); key = null; } } @Override - public Yaml.Mapping build() { - return new Yaml.Mapping(randomId(), Markers.EMPTY, startBracePrefix, entries, null, anchor); + public MappingWithPrefix build() { + return new MappingWithPrefix(prefix, startBracePrefix, entries, null, anchor); } } - @SuppressWarnings("InnerClassMayBeStatic") - private class SequenceBuilder implements BlockBuilder { - - private final boolean dash; - + private static class SequenceBuilder implements BlockBuilder { private final String prefix; @Nullable private final String startBracketPrefix; @@ -436,46 +461,106 @@ private class SequenceBuilder implements BlockBuilder { private final List<Yaml.Sequence.Entry> entries = new ArrayList<>(); - private SequenceBuilder(@Nullable boolean dash, String prefix, @Nullable String startBracketPrefix, @Nullable Yaml.Anchor anchor) { - this.dash = dash; + private SequenceBuilder(String prefix, @Nullable String startBracketPrefix, @Nullable Yaml.Anchor anchor) { this.prefix = prefix; this.startBracketPrefix = startBracketPrefix; this.anchor = anchor; } @Override - public void push(@Nullable String dashPrefix, Yaml.Block block, @Nullable String commaPrefix) { - String entryPrefix = ""; - boolean thisDash = dash; - if(dashPrefix == null) { - // If the dash has ended up inside the entryPrefix of the element, bring it back out - if(block instanceof Yaml.Mapping) { - Yaml.Mapping mapping = (Yaml.Mapping) block; - Yaml.Mapping.Entry entry = mapping.getEntries().get(0); - String mappingEntryPrefix = entry.getPrefix(); - int maybeDashIndex = StringUtils.indexOfNextNonWhitespaceHashComments(0, mappingEntryPrefix); - if(maybeDashIndex >= 0 && mappingEntryPrefix.charAt(maybeDashIndex) == '-') { - thisDash = true; - entryPrefix = mappingEntryPrefix.substring(0, maybeDashIndex); - String afterDash = mappingEntryPrefix.substring(maybeDashIndex + 1); - block = mapping.withEntries(ListUtils.concat(entry.withPrefix(afterDash), mapping.getEntries().subList(1, mapping.getEntries().size()))); - } - } + public void push(Yaml.Block block) { + push(block, null); + } + + public void push(Yaml.Block block, @Nullable String commaPrefix) { + String rawPrefix = block.getPrefix(); + int dashIndex = commentAwareIndexOf('-', rawPrefix); + String entryPrefix; + String blockPrefix; + boolean hasDash = dashIndex != -1; + if (hasDash) { + entryPrefix = rawPrefix.substring(0, dashIndex); + blockPrefix = rawPrefix.substring(dashIndex + 1); } else { - entryPrefix = dashPrefix; - } - if(entries.isEmpty()) { - entryPrefix = prefix + entryPrefix; + entryPrefix = ""; + blockPrefix = rawPrefix; } - entries.add(new Yaml.Sequence.Entry(randomId(), entryPrefix, Markers.EMPTY, block, thisDash, commaPrefix)); + entries.add(new Yaml.Sequence.Entry(randomId(), entryPrefix, Markers.EMPTY, block.withPrefix(blockPrefix), hasDash, commaPrefix)); } @Override - public Yaml.Sequence build() { - return new Yaml.Sequence(randomId(), Markers.EMPTY, startBracketPrefix, entries, null, anchor); + public SequenceWithPrefix build() { + return new SequenceWithPrefix(prefix, startBracketPrefix, entries, null, anchor); + } + } + + @Getter + private static class MappingWithPrefix extends Yaml.Mapping { + private String prefix; + + public MappingWithPrefix(String prefix, @Nullable String startBracePrefix, List<Yaml.Mapping.Entry> entries, @Nullable String endBracePrefix, @Nullable Anchor anchor) { + super(randomId(), Markers.EMPTY, startBracePrefix, entries, endBracePrefix, anchor); + this.prefix = prefix; + } + + @Override + public Mapping withPrefix(String prefix) { + this.prefix = prefix; + return this; } } + @Getter + private static class SequenceWithPrefix extends Yaml.Sequence { + private String prefix; + + public SequenceWithPrefix(String prefix, @Nullable String startBracketPrefix, List<Yaml.Sequence.Entry> entries, @Nullable String endBracketPrefix, @Nullable Anchor anchor) { + super(randomId(), Markers.EMPTY, startBracketPrefix, entries, endBracketPrefix, anchor); + this.prefix = prefix; + } + + @Override + public Sequence withPrefix(String prefix) { + this.prefix = prefix; + return this; + } + } + + private SourceFile unwrapPrefixedMappings(SourceFile y) { + if (!(y instanceof Yaml.Documents)) { + return y; + } + //noinspection ConstantConditions + return (Yaml.Documents) new YamlIsoVisitor<Integer>() { + @Override + public Yaml.Sequence visitSequence(Yaml.Sequence sequence, Integer p) { + if (sequence instanceof SequenceWithPrefix) { + SequenceWithPrefix sequenceWithPrefix = (SequenceWithPrefix) sequence; + return super.visitSequence( + new Yaml.Sequence( + sequenceWithPrefix.getId(), + sequenceWithPrefix.getMarkers(), + sequenceWithPrefix.getOpeningBracketPrefix(), + ListUtils.mapFirst(sequenceWithPrefix.getEntries(), e -> e.withPrefix(sequenceWithPrefix.getPrefix())), + sequenceWithPrefix.getClosingBracketPrefix(), + sequenceWithPrefix.getAnchor() + ), p); + } + return super.visitSequence(sequence, p); + } + + @Override + public Yaml.Mapping visitMapping(Yaml.Mapping mapping, Integer p) { + if (mapping instanceof MappingWithPrefix) { + MappingWithPrefix mappingWithPrefix = (MappingWithPrefix) mapping; + return super.visitMapping(new Yaml.Mapping(mappingWithPrefix.getId(), + mappingWithPrefix.getMarkers(), mappingWithPrefix.getOpeningBracePrefix(), mappingWithPrefix.getEntries(), null, mappingWithPrefix.getAnchor()), p); + } + return super.visitMapping(mapping, p); + } + }.visit(y, 0); + } + public static Builder builder() { return new Builder(); } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java index 6ee5318d36f..9a0426a9a2b 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java @@ -94,7 +94,7 @@ public Yaml visitMappingEntry(Yaml.Mapping.Entry entry, PrintOutputCapture<P> p) @Override public Yaml visitMapping(Yaml.Mapping mapping, PrintOutputCapture<P> p) { - beforeSyntax(mapping, p); + visitMarkers(mapping.getMarkers(), p); if (mapping.getAnchor() != null) { visit(mapping.getAnchor(), p); } @@ -161,7 +161,8 @@ public Yaml visitScalar(Yaml.Scalar scalar, PrintOutputCapture<P> p) { } public Yaml visitAnchor(Yaml.Anchor anchor, PrintOutputCapture<P> p) { - beforeSyntax(anchor, p); + visitMarkers(anchor.getMarkers(), p); + p.append(anchor.getPrefix()); p.append("&"); p.append(anchor.getKey()); p.append(anchor.getPostfix()); diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java index 231b8e38930..00f2fa0995d 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java @@ -16,16 +16,18 @@ package org.openrewrite.yaml; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.openrewrite.SourceFile; -import org.openrewrite.test.RewriteTest; +import org.openrewrite.tree.ParseError; import org.openrewrite.yaml.tree.Yaml; import java.util.List; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.openrewrite.yaml.Assertions.yaml; -class YamlParserTest implements RewriteTest { +class YamlParserTest { @Test void ascii() { @@ -45,13 +47,15 @@ void ascii() { assertThat(title.getValue()).isEqualTo("b"); } - @Test - void unicodeCharacterSpanningMultipleBytes() { - rewriteRun( - yaml(""" - # 🛠 - 🛠: "🛠" - """) - ); + @ParameterizedTest + @ValueSource(strings = { + "🛠", + "🛠🛠", + "🛠 🛠" + }) + void unicodeParseError(String input ) { + Stream<SourceFile> yamlSources = YamlParser.builder().build().parse("a: %s\n".formatted(input)); + assertThat(yamlSources).singleElement().isInstanceOf(ParseError.class); } + } diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java index 91c38b1d961..c6b78abe98c 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java @@ -42,7 +42,7 @@ void flowStyleMapping() { """ { "data": { - "prometheus.yml" : "global:\\n scrape_interval: 10s\\n scrape_timeout: 9s" + "prometheus.yml": "global:\\n scrape_interval: 10s\\n scrape_timeout: 9s" } } """ diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java index 1737ea4206a..5c7ed8316f9 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java @@ -28,9 +28,7 @@ void blockSequence() { rewriteRun( yaml( """ - - - apples # apple - # orange + - apples - oranges """, spec -> spec.afterRecipe(y -> { @@ -48,10 +46,10 @@ void blockSequenceOfMappings() { rewriteRun( yaml( """ - - name: Fred - age: 45 # Fred is 45 years old - - name: Barney - age: 25 + - name: Fred + age: 45 + - name: Barney + age: 25 """ ) ); @@ -63,21 +61,15 @@ void multiLineInlineSequenceWithFunnyIndentation() { yaml( """ [ - a , - b , - c , + a, + b, + c, ] """ ) ); } - @Test - void emptySequence() { - rewriteRun(yaml(" [ ] ")); - } - - @Test void sequenceOfEmptyInlineSequence() { rewriteRun(yaml("- []")); @@ -118,21 +110,13 @@ void sequenceOfMixedSequences() { """ - [] - [ 1 ] + - foo: [] + - bar: + - baz: [ + a] """ ) ); -// rewriteRun( -// yaml( -// """ -// - [] -// - [ 1 ] -// - foo: [] -// - bar: -// - baz: [ -// a] -// """ -// ) -// ); } @Test From 204817158041e617fef247831dfbe6055dfcd932 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 26 Jul 2023 18:13:32 -0700 Subject: [PATCH 101/447] Find and FindAndReplace accept multiple file path filters --- .../src/main/java/org/openrewrite/text/Find.java | 13 +++++++++++-- .../java/org/openrewrite/text/FindAndReplace.java | 13 +++++++++++-- .../test/java/org/openrewrite/text/FindTest.java | 12 +++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/Find.java b/rewrite-core/src/main/java/org/openrewrite/text/Find.java index 00117051266..9789ddec9ea 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/Find.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/Find.java @@ -26,6 +26,7 @@ import org.openrewrite.remote.Remote; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -80,7 +81,9 @@ public String getDescription() { @Option(displayName = "File pattern", description = "A glob expression that can be used to constrain which directories or source files should be searched. " + - "When not set, all source files are searched.", + "Multiple patterns may be specified, separated by a semicolon `;`. " + + "If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " + + "When not set, all source files are searched. ", example = "**/*.java") @Nullable String filePattern; @@ -129,8 +132,14 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { return plainText.withText("").withSnippets(snippets); } }; + //noinspection DuplicatedCode if(filePattern != null) { - visitor = Preconditions.check(new HasSourcePath<>(filePattern), visitor); + //noinspection unchecked + TreeVisitor<?, ExecutionContext> check = Preconditions.or(Arrays.stream(filePattern.split(";")) + .map(HasSourcePath<ExecutionContext>::new) + .toArray(TreeVisitor[]::new)); + + visitor = Preconditions.check(check, visitor); } return visitor; } diff --git a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java index 13588399866..6f4ecdd9cd2 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java @@ -25,6 +25,7 @@ import org.openrewrite.quark.Quark; import org.openrewrite.remote.Remote; +import java.util.Arrays; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -75,7 +76,9 @@ public class FindAndReplace extends Recipe { @Option(displayName = "File pattern", description = "A glob expression that can be used to constrain which directories or source files should be searched. " + - "When not set, all source files are searched.", + "Multiple patterns may be specified, separated by a semicolon `;`. " + + "If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " + + "When not set, all source files are searched. ", example = "**/*.java") @Nullable String filePattern; @@ -141,8 +144,14 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { .withMarkers(sourceFile.getMarkers().add(new AlreadyReplaced(randomId()))); } }; + //noinspection DuplicatedCode if(filePattern != null) { - visitor = Preconditions.check(new HasSourcePath<>(filePattern), visitor); + //noinspection unchecked + TreeVisitor<?, ExecutionContext> check = Preconditions.or(Arrays.stream(filePattern.split(";")) + .map(HasSourcePath<ExecutionContext>::new) + .toArray(TreeVisitor[]::new)); + + visitor = Preconditions.check(check, visitor); } return visitor; } diff --git a/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java b/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java index 5d85c017bff..8c8b6c8a112 100644 --- a/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/text/FindTest.java @@ -58,7 +58,7 @@ void plainText() { @Test void caseInsensitive() { rewriteRun( - spec -> spec.recipe(new Find("text", null, null, null, null, "**/foo/**")), + spec -> spec.recipe(new Find("text", null, null, null, null, "**/foo/**;**/baz/**")), dir("foo", text( """ @@ -73,6 +73,16 @@ void caseInsensitive() { text(""" TEXT """) + ), + dir("baz", + text( + """ + TEXT + """, + """ + ~~>TEXT + """ + ) ) ); } From cd66f6256222d458d330747324d4fce8df15aaaa Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 26 Jul 2023 21:08:00 -0700 Subject: [PATCH 102/447] Add a FindMultiselect recipe for UI testing --- .../org/openrewrite/text/FindMultiselect.java | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 rewrite-core/src/main/java/org/openrewrite/text/FindMultiselect.java diff --git a/rewrite-core/src/main/java/org/openrewrite/text/FindMultiselect.java b/rewrite-core/src/main/java/org/openrewrite/text/FindMultiselect.java new file mode 100644 index 00000000000..cf8d7463e4b --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/text/FindMultiselect.java @@ -0,0 +1,159 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.text; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.binary.Binary; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.Markers; +import org.openrewrite.marker.SearchResult; +import org.openrewrite.quark.Quark; +import org.openrewrite.remote.Remote; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toSet; + +@Incubating(since = "8.2.0") +@Value +@EqualsAndHashCode(callSuper = true) +public class FindMultiselect extends Recipe { + + @Override + public String getDisplayName() { + return "Experimental find text with multiselect"; + } + + @Override + public String getDescription() { + return "Search for text, treating all textual sources as plain text. " + + "This version of the recipe exists to experiment with multiselect recipe options."; + } + + @Option(displayName = "Find", + description = "The text to find.", + example = "blacklist") + String find; + + @Option(displayName = "Regex", + description = "If true, `find` will be interpreted as a Regular Expression. Default `false`.", + required = false) + @Nullable + Boolean regex; + + @Option(displayName = "Regex options", + description = "Regex processing options. Multiple options may be specified. These options do nothing if `regex` mode is not enabled.\n" + + "* Case-sensitive - The search will be sensitive to letter case. " + + "* Multiline - Allows `^` and `$` to match the beginning and end of lines, respectively." + + "* Dot all - Allows `.` to match line terminators.", + valid = {"Case-sensitive", "Multiline", "Dot all"}, + required = false) + @Nullable + Set<String> regexOptions; + + @Option(displayName = "File pattern", + description = "A glob expression that can be used to constrain which directories or source files should be searched. " + + "Multiple patterns may be specified, separated by a semicolon `;`. " + + "If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " + + "When not set, all source files are searched. ", + example = "**/*.java") + @Nullable + String filePattern; + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + Boolean caseSensitive; + Boolean multiline; + Boolean dotAll; + if(regexOptions != null) { + Set<String> lowerCaseOptions = regexOptions.stream() + .map(String::toLowerCase) + .collect(toSet()); + caseSensitive = lowerCaseOptions.contains("Case-sensitive"); + multiline = lowerCaseOptions.contains("Multiline"); + dotAll = lowerCaseOptions.contains("Dot all"); + } else { + caseSensitive = null; + multiline = null; + dotAll = null; + } + + TreeVisitor<?, ExecutionContext> visitor = new TreeVisitor<Tree, ExecutionContext>() { + @Override + public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + SourceFile sourceFile = (SourceFile) requireNonNull(tree); + if (sourceFile instanceof Quark || sourceFile instanceof Remote || sourceFile instanceof Binary) { + return sourceFile; + } + PlainText plainText = PlainTextParser.convert(sourceFile); + String searchStr = find; + if (!Boolean.TRUE.equals(regex)) { + searchStr = Pattern.quote(searchStr); + } + int patternOptions = 0; + if(!Boolean.TRUE.equals(caseSensitive)) { + patternOptions |= Pattern.CASE_INSENSITIVE; + } + if(Boolean.TRUE.equals(multiline)) { + patternOptions |= Pattern.MULTILINE; + } + if(Boolean.TRUE.equals(dotAll)) { + patternOptions |= Pattern.DOTALL; + } + Pattern pattern = Pattern.compile(searchStr, patternOptions); + Matcher matcher = pattern.matcher(plainText.getText()); + String rawText = plainText.getText(); + if (!matcher.find()) { + return sourceFile; + } + matcher.reset(); + List<PlainText.Snippet> snippets = new ArrayList<>(); + int previousEnd = 0; + while (matcher.find()) { + int matchStart = matcher.start(); + snippets.add(snippet(rawText.substring(previousEnd, matchStart))); + snippets.add(SearchResult.found(snippet(rawText.substring(matchStart, matcher.end())))); + previousEnd = matcher.end(); + } + snippets.add(snippet(rawText.substring(previousEnd))); + return plainText.withText("").withSnippets(snippets); + } + }; + //noinspection DuplicatedCode + if(filePattern != null) { + //noinspection unchecked + TreeVisitor<?, ExecutionContext> check = Preconditions.or(Arrays.stream(filePattern.split(";")) + .map(HasSourcePath<ExecutionContext>::new) + .toArray(TreeVisitor[]::new)); + + visitor = Preconditions.check(check, visitor); + } + return visitor; + } + + + private static PlainText.Snippet snippet(String text) { + return new PlainText.Snippet(Tree.randomId(), Markers.EMPTY, text); + } +} From bb9f47a7947a6ad6ebbe9dcece329856f3d1963f Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Thu, 27 Jul 2023 10:15:34 +0200 Subject: [PATCH 103/447] Short circuit to not append empty strings (#3441) StringBuilder runs through all the logic to ensure capacity, put the string given coder & bytes, none of which are needed for the fairly common case of printing empty prefixes. --- .../org/openrewrite/PrintOutputCapture.java | 2 +- .../org/openrewrite/java/JavaPrinter.java | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/PrintOutputCapture.java b/rewrite-core/src/main/java/org/openrewrite/PrintOutputCapture.java index 014f4953c8e..211fcf3fe19 100644 --- a/rewrite-core/src/main/java/org/openrewrite/PrintOutputCapture.java +++ b/rewrite-core/src/main/java/org/openrewrite/PrintOutputCapture.java @@ -48,7 +48,7 @@ public String getOut() { } public PrintOutputCapture<P> append(@Nullable String text) { - if (text == null) { + if (text == null || text.isEmpty()) { return this; } out.append(text); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java index 0bbca854c1f..9764f0856ff 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java @@ -147,7 +147,7 @@ protected void visitModifier(Modifier mod, PrintOutputCapture<P> p) { @Override public J visitAnnotation(Annotation annotation, PrintOutputCapture<P> p) { beforeSyntax(annotation, Space.Location.ANNOTATION_PREFIX, p); - p.append("@"); + p.append('@'); visit(annotation.getAnnotationType(), p); visitContainer("(", annotation.getPadding().getArguments(), JContainer.Location.ANNOTATION_ARGUMENTS, ",", ")", p); afterSyntax(annotation, p); @@ -166,7 +166,7 @@ public J visitAnnotatedType(AnnotatedType annotatedType, PrintOutputCapture<P> p @Override public J visitArrayDimension(ArrayDimension arrayDimension, PrintOutputCapture<P> p) { beforeSyntax(arrayDimension, Space.Location.DIMENSION_PREFIX, p); - p.append("["); + p.append('['); visitRightPadded(arrayDimension.getPadding().getIndex(), JRightPadded.Location.ARRAY_INDEX, "]", p); afterSyntax(arrayDimension, p); return arrayDimension; @@ -496,7 +496,7 @@ public J visitCompilationUnit(J.CompilationUnit cu, PrintOutputCapture<P> p) { visitRightPadded(cu.getPadding().getPackageDeclaration(), JRightPadded.Location.PACKAGE, ";", p); visitRightPadded(cu.getPadding().getImports(), JRightPadded.Location.IMPORT, ";", p); if (!cu.getImports().isEmpty()) { - p.append(";"); + p.append(';'); } visit(cu.getClasses(), p); afterSyntax(cu, p); @@ -746,9 +746,9 @@ public J visitMethodDeclaration(MethodDeclaration method, PrintOutputCapture<P> visit(typeParameters.getAnnotations(), p); visitSpace(typeParameters.getPrefix(), Space.Location.TYPE_PARAMETERS, p); visitMarkers(typeParameters.getMarkers(), p); - p.append("<"); + p.append('<'); visitRightPadded(typeParameters.getPadding().getTypeParameters(), JRightPadded.Location.TYPE_PARAMETER, ",", p); - p.append(">"); + p.append('>'); } visit(method.getReturnTypeExpression(), p); visit(method.getAnnotations().getName().getAnnotations(), p); @@ -905,7 +905,7 @@ public J visitPrimitive(Primitive primitive, PrintOutputCapture<P> p) { @Override public <T extends J> J visitParentheses(Parentheses<T> parens, PrintOutputCapture<P> p) { beforeSyntax(parens, Space.Location.PARENTHESES_PREFIX, p); - p.append("("); + p.append('('); visitRightPadded(parens.getPadding().getTree(), JRightPadded.Location.PARENTHESES, ")", p); afterSyntax(parens, p); return parens; @@ -1041,20 +1041,20 @@ public J visitUnary(Unary unary, PrintOutputCapture<P> p) { p.append("--"); break; case Positive: - p.append("+"); + p.append('+'); visit(unary.getExpression(), p); break; case Negative: - p.append("-"); + p.append('-'); visit(unary.getExpression(), p); break; case Complement: - p.append("~"); + p.append('~'); visit(unary.getExpression(), p); break; case Not: default: - p.append("!"); + p.append('!'); visit(unary.getExpression(), p); } afterSyntax(unary, p); From 83f44b28b31eea0f2414c2aec79b65f67a5d2419 Mon Sep 17 00:00:00 2001 From: Raquel Pau <1483433+rpau@users.noreply.github.com> Date: Fri, 28 Jul 2023 18:29:22 +0200 Subject: [PATCH 104/447] Adding the Moderne CLI as a valid build tool (#3445) --- .../src/main/java/org/openrewrite/marker/BuildTool.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/BuildTool.java b/rewrite-core/src/main/java/org/openrewrite/marker/BuildTool.java index 04a1f37b51c..076d73d91f7 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/BuildTool.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/BuildTool.java @@ -34,6 +34,7 @@ public class BuildTool implements Marker { public enum Type { Gradle, Maven, - Bazel + Bazel, + ModerneCli } } From 90fbb3f95203559ddb8de991489ac27e19e2d432 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sat, 29 Jul 2023 01:59:04 -0400 Subject: [PATCH 105/447] Add support for JAXB XJB xml files (#3446) Co-authored-by: Adriano Machado <admachad@redhat.com> --- rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java b/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java index e1f20bc158d..529c99256bb 100755 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java @@ -77,7 +77,8 @@ public boolean accept(Path path) { p.endsWith(".xsd") || p.endsWith(".xsl") || p.endsWith(".xslt") || - p.endsWith(".tld"); + p.endsWith(".tld") || + p.endsWith(".xjb"); } @Override From 5d1cb3cecd4e3abccdc77410d407192a732d0a7b Mon Sep 17 00:00:00 2001 From: Bernhard Haumacher <haui@haumacher.de> Date: Mon, 31 Jul 2023 13:13:17 +0200 Subject: [PATCH 106/447] Ensure that even system-installed artifacts are resolved. (#3447) When running Maven on Linux from a system maintainer-installed package and adding a dependency to a recipe that is also a dependency of Maven itself (e.g. slf4j-api), then the system-installed version is used (e.g. file:/usr/share/java/slf4j-api.jar). Unfortunately, this path is not matched be the regular expression, since no version is added to the file name. This results in an incomprehensible error "Unable to find runtime dependencies beginning with: slf4j-api" that gives no clue what's wrong. Therefore, its better to also output the current class path being scanned. --- .../src/main/java/org/openrewrite/java/JavaParser.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index 46a60c7a9b2..b5d40655c9f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -83,7 +83,7 @@ static List<Path> dependenciesFromClasspath(String... artifactNames) { List<Path> artifacts = new ArrayList<>(artifactNames.length); List<String> missingArtifactNames = new ArrayList<>(artifactNames.length); for (String artifactName : artifactNames) { - Pattern jarPattern = Pattern.compile(artifactName + "-.*?\\.jar$"); + Pattern jarPattern = Pattern.compile(artifactName + "(?:" + "-.*?" + ")?" + "\\.jar$"); // In a multi-project IDE classpath, some classpath entries aren't jars Pattern explodedPattern = Pattern.compile("/" + artifactName + "/"); boolean lacking = true; @@ -104,7 +104,8 @@ static List<Path> dependenciesFromClasspath(String... artifactNames) { if (!missingArtifactNames.isEmpty()) { throw new IllegalArgumentException("Unable to find runtime dependencies beginning with: " + - missingArtifactNames.stream().map(a -> "'" + a + "'").sorted().collect(joining(", "))); + missingArtifactNames.stream().map(a -> "'" + a + "'").sorted().collect(joining(", ")) + + ", classpath: " + runtimeClasspath); } return artifacts; From e39e75e79414a54a5c1202e87361c7dff65d7c7a Mon Sep 17 00:00:00 2001 From: traceyyoshima <tracey.yoshima@gmail.com> Date: Tue, 1 Aug 2023 14:21:02 -0600 Subject: [PATCH 107/447] Fixed java doc on MethodMatcher for `All method invocations`. --- .../src/main/java/org/openrewrite/java/MethodMatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java b/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java index 3e08465fe9d..67e469b6630 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java @@ -51,7 +51,7 @@ * <P><PRE> * EXAMPLES: * <p> - * * *(..) - All method invocations + * *..* *(..) - All method invocations * java.util.* *(..) - All method invocations to classes belonging to java.util (including sub-packages) * java.util.Collections *(..) - All method invocations on java.util.Collections class * java.util.Collections unmodifiable*(..) - All method invocations starting with "unmodifiable" on java.util.Collections From c54d9ca209ab78589f86e8685e3ffc50f7eb8348 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Wed, 2 Aug 2023 14:59:24 -0400 Subject: [PATCH 108/447] Add LST model type to FindSourceFiles data table --- .../src/main/java/org/openrewrite/FindSourceFiles.java | 3 ++- .../src/main/java/org/openrewrite/table/SourcesFiles.java | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/FindSourceFiles.java b/rewrite-core/src/main/java/org/openrewrite/FindSourceFiles.java index 86a9df52039..bf4e3687e6c 100644 --- a/rewrite-core/src/main/java/org/openrewrite/FindSourceFiles.java +++ b/rewrite-core/src/main/java/org/openrewrite/FindSourceFiles.java @@ -54,7 +54,8 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { SourceFile sourceFile = (SourceFile) tree; Path sourcePath = sourceFile.getSourcePath(); if (PathUtils.matchesGlob(sourcePath, normalize(filePattern))) { - results.insertRow(ctx, new SourcesFiles.Row(sourcePath.toString())); + results.insertRow(ctx, new SourcesFiles.Row(sourcePath.toString(), + tree.getClass().getSimpleName())); return SearchResult.found(sourceFile); } } diff --git a/rewrite-core/src/main/java/org/openrewrite/table/SourcesFiles.java b/rewrite-core/src/main/java/org/openrewrite/table/SourcesFiles.java index 7f5f42b39fa..12cb57a10ef 100644 --- a/rewrite-core/src/main/java/org/openrewrite/table/SourcesFiles.java +++ b/rewrite-core/src/main/java/org/openrewrite/table/SourcesFiles.java @@ -32,5 +32,9 @@ public static class Row { @Column(displayName = "Source path before the run", description = "The source path of the file before the run.") String sourcePath; + + @Column(displayName = "LST type", + description = "The LST model type that the file is parsed as.") + String type; } } From 93c6f858ebcb6b1e0b6713f4b14358e0fd7eb014 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Thu, 3 Aug 2023 09:12:08 -0400 Subject: [PATCH 109/447] Add ParseError#erroneous --- .../java/org/openrewrite/tree/ParseError.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java b/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java index b436bdd65d4..a5e76f99e95 100644 --- a/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java +++ b/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java @@ -29,22 +29,14 @@ import static java.util.Collections.singletonList; @Value +@With public class ParseError implements SourceFile { - @With @EqualsAndHashCode.Include - @Getter UUID id; - @With - @Getter Markers markers; - - @With - @Getter Path sourcePath; - @With - @Getter @Nullable FileAttributes fileAttributes; @@ -68,18 +60,21 @@ public SourceFile withCharset(Charset charset) { return withCharsetName(charset.name()); } - @With - @Getter boolean charsetBomMarked; - @With - @Getter @Nullable Checksum checksum; - @With String text; + /** + * A parsed LST that was determined at parsing time to be erroneous, for + * example if it doesn't faithfully produce the original source text at + * printing time. + */ + @Nullable + SourceFile erroneous; + @Override public <P> boolean isAcceptable(TreeVisitor<?, P> v, P p) { return v.isAdaptableTo(ParseErrorVisitor.class); @@ -105,7 +100,8 @@ public static ParseError build(Parser parser, parser.getCharset(ctx).name(), is.isCharsetBomMarked(), null, - is.readFully() + is.readFully(), + null ); } } From 25dd9db1696adb69efc4ad91ee1a4ade82758466 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Thu, 3 Aug 2023 15:21:38 +0200 Subject: [PATCH 110/447] Take license header into account when adding gradle plugin (#3451) * take license header into account --- .../gradle/plugins/AddPluginVisitor.java | 67 ++++++++++--- .../gradle/plugins/AddBuildPluginTest.java | 94 ++++++++++++++++++- .../AddGradleEnterpriseGradlePluginTest.java | 31 ++++++ 3 files changed, 177 insertions(+), 15 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java index 76ab0355250..35df134b4c0 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java @@ -29,10 +29,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.search.FindMethods; -import org.openrewrite.java.tree.Expression; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.Space; -import org.openrewrite.java.tree.Statement; +import org.openrewrite.java.tree.*; import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.tree.GroupArtifact; @@ -41,6 +38,7 @@ import org.openrewrite.semver.*; import java.nio.file.Paths; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -83,7 +81,7 @@ public static Optional<String> resolvePluginVersion(String pluginId, String curr } private static Optional<String> findNewerVersion(String groupId, String artifactId, String version, VersionComparator versionComparator, - List<MavenRepository> repositories, ExecutionContext ctx) throws MavenDownloadingException { + List<MavenRepository> repositories, ExecutionContext ctx) throws MavenDownloadingException { try { MavenMetadata mavenMetadata = downloadMetadata(groupId, artifactId, repositories, ctx); return versionComparator.upgrade(version, mavenMetadata.getVersioning().getVersions()); @@ -99,6 +97,41 @@ private static MavenMetadata downloadMetadata(String groupId, String artifactId, repositories); } + private static @Nullable Comment getLicenseHeader(G.CompilationUnit cu) { + if (!cu.getStatements().isEmpty()) { + Statement firstStatement = cu.getStatements().get(0); + if (!firstStatement.getComments().isEmpty()) { + Comment firstComment = firstStatement.getComments().get(0); + if (isLicenseHeader(firstComment)) { + return firstComment; + } + } + } else if (cu.getEof() != null && !cu.getEof().getComments().isEmpty()) { + Comment firstComment = cu.getEof().getComments().get(0); + if (isLicenseHeader(firstComment)) { + // Adding suffix so when we later use it, formats well. + return firstComment.withSuffix("\n\n"); + } + } + return null; + } + + private static boolean isLicenseHeader(Comment comment) { + return comment instanceof TextComment && comment.isMultiline() && + ((TextComment) comment).getText().contains("License"); + } + + private static G.CompilationUnit removeLicenseHeader(G.CompilationUnit cu) { + if (!cu.getStatements().isEmpty()) { + return cu.withStatements(ListUtils.mapFirst(cu.getStatements(), + s -> s.withComments(s.getComments().subList(1, s.getComments().size())) + )); + } else { + List<Comment> eofComments = cu.getEof().getComments(); + return cu.withEof(cu.getEof().withComments(eofComments.subList(1, eofComments.size()))); + } + } + @Override public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { if (FindPlugins.find(cu, pluginId).isEmpty()) { @@ -142,8 +175,8 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integ .parseInputs( singletonList( Parser.Input.fromString("plugins {\n" + - " id " + delimiter + pluginId + delimiter + (version.map(s -> " version " + delimiter + s + delimiter).orElse("")) + "\n" + - "}")), + " id " + delimiter + pluginId + delimiter + (version.map(s -> " version " + delimiter + s + delimiter).orElse("")) + "\n" + + "}")), null, ctx ) @@ -154,11 +187,10 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integ .get(0); if (FindMethods.find(cu, "RewriteGradleProject plugins(..)").isEmpty() && FindMethods.find(cu, "RewriteSettings plugins(..)").isEmpty()) { - Space leadingSpace = Space.firstPrefix(cu.getStatements()); if (cu.getSourcePath().endsWith(Paths.get("settings.gradle")) - && !cu.getStatements().isEmpty() - && cu.getStatements().get(0) instanceof J.MethodInvocation - && ((J.MethodInvocation) cu.getStatements().get(0)).getSimpleName().equals("pluginManagement")) { + && !cu.getStatements().isEmpty() + && cu.getStatements().get(0) instanceof J.MethodInvocation + && ((J.MethodInvocation) cu.getStatements().get(0)).getSimpleName().equals("pluginManagement")) { return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), 1)); } else { int insertAtIdx = 0; @@ -170,7 +202,16 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integ } } if (insertAtIdx == 0) { - return cu.withStatements(ListUtils.insert(Space.formatFirstPrefix(cu.getStatements(), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())), autoFormat(statement, ctx, getCursor()), insertAtIdx)); + Comment licenseHeader = getLicenseHeader(cu); + if (licenseHeader != null) { + cu = removeLicenseHeader(cu); + statement = statement.withComments(Collections.singletonList(licenseHeader)); + } + Space leadingSpace = Space.firstPrefix(cu.getStatements()); + return cu.withStatements(ListUtils.insert( + Space.formatFirstPrefix(cu.getStatements(), leadingSpace.withWhitespace("\n\n" + leadingSpace.getWhitespace())), + autoFormat(statement, ctx, getCursor()), + insertAtIdx)); } else { return cu.withStatements(ListUtils.insert(cu.getStatements(), autoFormat(statement.withPrefix(Space.format("\n\n")), ctx, getCursor()), insertAtIdx)); } @@ -178,11 +219,11 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integ } else { MethodMatcher buildPluginsMatcher = new MethodMatcher("RewriteGradleProject plugins(groovy.lang.Closure)"); MethodMatcher settingsPluginsMatcher = new MethodMatcher("RewriteSettings plugins(groovy.lang.Closure)"); + J.MethodInvocation pluginDef = (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) ((J.MethodInvocation) autoFormat(statement, ctx, getCursor())).getArguments().get(0)).getBody()).getStatements().get(0)).getExpression(); return cu.withStatements(ListUtils.map(cu.getStatements(), stat -> { if (stat instanceof J.MethodInvocation) { J.MethodInvocation m = (J.MethodInvocation) stat; if (buildPluginsMatcher.matches(m) || settingsPluginsMatcher.matches(m)) { - J.MethodInvocation pluginDef = (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) ((J.MethodInvocation) autoFormat(statement, ctx, getCursor())).getArguments().get(0)).getBody()).getStatements().get(0)).getExpression(); m = m.withArguments(ListUtils.map(m.getArguments(), a -> { if (a instanceof J.Lambda) { J.Lambda l = (J.Lambda) a; diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java index 5e581723a2d..4a44abb6c0c 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddBuildPluginTest.java @@ -17,11 +17,12 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.marker.BuildTool; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; -import static org.openrewrite.gradle.Assertions.buildGradle; -import static org.openrewrite.gradle.Assertions.withToolingApi; +import static org.openrewrite.Tree.randomId; +import static org.openrewrite.gradle.Assertions.*; class AddBuildPluginTest implements RewriteTest { @Override @@ -144,4 +145,93 @@ void addPluginAfterBuildscriptBlock() { ) ); } + + @Test + void addPluginToNewBlockWithLicenseHeader() { + rewriteRun( + spec -> { + spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null)); + spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))); + }, + buildGradle( + """ + /* + * License header + */ + + dependencies { + } + """, """ + /* + * License header + */ + + plugins { + id 'com.jfrog.bintray' version '1.0' + } + + dependencies { + } + """ + ) + ); + } + + @Test + void addPluginToNewBlockWithLicenseHeaderAndComment() { + rewriteRun( + spec -> { + spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null)); + spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))); + }, + buildGradle( + """ + /* + * License header + */ + + // Comment + dependencies { + } + """, """ + /* + * License header + */ + + plugins { + id 'com.jfrog.bintray' version '1.0' + } + + // Comment + dependencies { + } + """ + ) + ); + } + + @Test + void addPluginToNewBlockWithOnlyLicenseHeader() { + rewriteRun( + spec -> { + spec.recipe(new AddBuildPlugin("com.jfrog.bintray", "1.0", null)); + spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))); + }, + buildGradle( + """ + /* + * License header + */ + """, """ + /* + * License header + */ + + plugins { + id 'com.jfrog.bintray' version '1.0' + } + """ + ) + ); + } } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java index 940494d8af0..56f026baaad 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java @@ -255,4 +255,35 @@ void defaultsToLatestRelease() { ) ); } + + @Test + void addNewSettingsPluginsBlockWithLicenseHeader() { + rewriteRun( + spec -> spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))), + buildGradle( + "" + ), + settingsGradle( + """ + /* + * Licensed to... + */ + + rootProject.name = 'my-project' + """, + interpolateResolvedVersion(""" + /* + * Licensed to... + */ + + plugins { + id 'com.gradle.enterprise' version '%s' + } + + rootProject.name = 'my-project' + """ + ) + ) + ); + } } From 5437d9de1670ff2e721061a3c0a3c190912c0291 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Thu, 3 Aug 2023 15:38:14 +0200 Subject: [PATCH 111/447] fixed valid and docs (#3452) --- .../plugins/AddGradleEnterpriseGradlePlugin.java | 4 ++-- .../maven/AddGradleEnterpriseMavenExtension.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java index 47c97ec7ee9..f845dd00e66 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java @@ -97,8 +97,8 @@ public class AddGradleEnterpriseGradlePlugin extends Recipe { Boolean uploadInBackground; @Option(displayName = "Publish Criteria", - description = "When set to `always` the plugin will publish build scans of every single build. " + - "When set to `failure` the plugin will only publish build scans when the build fails. " + + description = "When set to `Always` the plugin will publish build scans of every single build. " + + "When set to `Failure` the plugin will only publish build scans when the build fails. " + "When omitted scans will be published only when the `--scan` option is passed to the build.", required = false, valid = {"Always", "Failure"}, diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java index 00832e2d6c2..7603973c244 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java @@ -103,13 +103,13 @@ public class AddGradleEnterpriseMavenExtension extends ScanningRecipe<AddGradleE Boolean uploadInBackground; @Option(displayName = "Publish Criteria", - description = "When set to `always` the extension will publish build scans of every single build. " + + description = "When set to `Always` the extension will publish build scans of every single build. " + "This is the default behavior when omitted." + - "When set to `failure` the extension will only publish build scans when the build fails. " + - "When set to `demand` the extension will only publish build scans when explicitly requested.", + "When set to `Failure` the extension will only publish build scans when the build fails. " + + "When set to `Demand` the extension will only publish build scans when explicitly requested.", required = false, - valid = {"always", "failure", "demand"}, - example = "true") + valid = {"Always", "Failure", "Demand"}, + example = "Always") @Nullable PublishCriteria publishCriteria; From 78fa25c3e17303a3746df34aafb5f9d306c97537 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Fri, 4 Aug 2023 09:10:59 -0400 Subject: [PATCH 112/447] File pattern not a required field for text search --- rewrite-core/src/main/java/org/openrewrite/text/Find.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/Find.java b/rewrite-core/src/main/java/org/openrewrite/text/Find.java index 9789ddec9ea..e683a43b4d5 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/Find.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/Find.java @@ -84,6 +84,7 @@ public String getDescription() { "Multiple patterns may be specified, separated by a semicolon `;`. " + "If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " + "When not set, all source files are searched. ", + required = false, example = "**/*.java") @Nullable String filePattern; From f50358bd729f1a0b7a18e7aec87c6b68e3ff186e Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Fri, 4 Aug 2023 17:07:15 +0200 Subject: [PATCH 113/447] Fix CRLF newlines in AddImport (#3455) * Checking new lines * added some more tests * Replace newlines with a negative look behind Co-authored-by: Shannon Pamperl <shanman190@gmail.com> --------- Co-authored-by: Tim te Beek <tim@moderne.io> Co-authored-by: Shannon Pamperl <shanman190@gmail.com> --- .../org/openrewrite/java/AddImportTest.java | 103 ++++++++++++++++++ .../java/org/openrewrite/java/AddImport.java | 43 +++++--- 2 files changed, 129 insertions(+), 17 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java index a6c6382db1a..80da85d2dd1 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java @@ -1142,4 +1142,107 @@ void noImportLayout() { ) ); } + + @Test + void crlfNewLinesWithoutPreviousImports() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new AddImport<>("java.util.List", null, false))), + java( + """ + package a; + class A {} + """.replace("\n", "\r\n"), + """ + package a; + + import java.util.List; + + class A {} + """.replace("\n", "\r\n") + ) + ); + } + + @Test + void crlfNewLinesWithPreviousImports() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new AddImport<>("java.util.List", null, false))), + java( + """ + package a; + + import java.util.Set; + + class A {} + """.replace("\n", "\r\n"), + """ + package a; + + import java.util.List; + import java.util.Set; + + class A {} + """.replace("\n", "\r\n") + ) + ); + } + + @Test + void crlfNewLinesWithPreviousImportsNoPackage() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new AddImport<>("java.util.List", null, false))), + java( + """ + import java.util.Set; + + class A {} + """.replace("\n", "\r\n"), + """ + import java.util.List; + import java.util.Set; + + class A {} + """.replace("\n", "\r\n") + ) + ); + } + + @Test + void crlfNewLinesWithPreviousImportsNoClass() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new AddImport<>("java.util.List", null, false))), + java( + """ + package a; + + import java.util.Arrays; + import java.util.Set; + """.replace("\n", "\r\n"), + """ + package a; + + import java.util.Arrays; + import java.util.List; + import java.util.Set; + """.replace("\n", "\r\n") + ) + ); + } + @Test + void crlfNewLinesWithPreviousImportsNoPackageNoClass() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new AddImport<>("java.util.List", null, false))), + java( + """ + import java.util.Arrays; + import java.util.Set; + """.replace("\n", "\r\n"), + """ + import java.util.Arrays; + import java.util.List; + import java.util.Set; + """.replace("\n", "\r\n") + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java b/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java index bad45c86527..e2121790120 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java @@ -28,6 +28,7 @@ import org.openrewrite.java.style.IntelliJ; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; +import org.openrewrite.style.GeneralFormatStyle; import java.util.ArrayList; import java.util.Collections; @@ -37,6 +38,7 @@ import static org.openrewrite.Tree.randomId; import static org.openrewrite.java.tree.TypeUtils.isOfClassType; +import static org.openrewrite.java.format.AutodetectGeneralFormatStyle.autodetectGeneralFormatStyle; /** * A Java refactoring visitor that can be used to add an import (or static import) to a given compilation unit. @@ -132,15 +134,9 @@ public AddImport(String type, @Nullable String statik, boolean onlyIfReferenced) .withComments(ListUtils.map(firstClassPrefix.getComments(), comment -> comment instanceof Javadoc ? null : comment)) .withWhitespace("")); - cu = cu.withClasses(ListUtils.map(cu.getClasses(), (i, clazz) -> { - Space prefix = clazz.getPrefix(); - return i == 0 ? - clazz.withPrefix(prefix.withComments(ListUtils.map(prefix.getComments(), - comment -> comment instanceof Javadoc ? comment : null))) : - clazz; - })); - } else { - importToAdd = importToAdd.withPrefix(Space.format("\n\n")); + cu = cu.withClasses(ListUtils.mapFirst(cu.getClasses(), clazz -> + clazz.withComments(ListUtils.map(clazz.getComments(), comment -> comment instanceof Javadoc ? comment : null)) + )); } } @@ -151,16 +147,17 @@ public AddImport(String type, @Nullable String statik, boolean onlyIfReferenced) .map(JavaSourceSet::getClasspath) .orElse(Collections.emptyList()); - cu = cu.getPadding().withImports(layoutStyle.addImport(cu.getPadding().getImports(), importToAdd, - cu.getPackageDeclaration(), classpath)); + List<JRightPadded<J.Import>> newImports = layoutStyle.addImport(cu.getPadding().getImports(), importToAdd, cu.getPackageDeclaration(), classpath); + + // ImportLayoutStile::addImport adds always `\n` as newlines. Checking if we need to fix them + newImports = checkCRLF(cu, newImports); + + cu = cu.getPadding().withImports(newImports); JavaSourceFile c = cu; - cu = cu.withClasses(ListUtils.map(cu.getClasses(), (i, clazz) -> { - if (i == 0) { - J.ClassDeclaration cl = autoFormat(clazz, clazz.getName(), p, new Cursor(null, c)); - clazz = clazz.withPrefix(clazz.getPrefix().withWhitespace(cl.getPrefix().getWhitespace())); - } - return clazz; + cu = cu.withClasses(ListUtils.mapFirst(cu.getClasses(), clazz -> { + J.ClassDeclaration cl = autoFormat(clazz, clazz.getName(), p, new Cursor(null, c)); + return clazz.withPrefix(clazz.getPrefix().withWhitespace(cl.getPrefix().getWhitespace())); })); j = cu; @@ -168,6 +165,18 @@ public AddImport(String type, @Nullable String statik, boolean onlyIfReferenced) return j; } + private List<JRightPadded<J.Import>> checkCRLF(JavaSourceFile cu, List<JRightPadded<J.Import>> newImports) { + GeneralFormatStyle generalFormatStyle = Optional.ofNullable(((SourceFile) cu).getStyle(GeneralFormatStyle.class)) + .orElse(autodetectGeneralFormatStyle(cu)); + if (generalFormatStyle.isUseCRLFNewLines()) { + return ListUtils.map(newImports, rp -> rp.map( + i -> i.withPrefix(i.getPrefix().withWhitespace(i.getPrefix().getWhitespace() + .replaceAll("(?<!\r)\n", "\r\n"))) + )); + } + return newImports; + } + private boolean isTypeReference(NameTree t) { boolean isTypRef = true; if (t instanceof J.FieldAccess) { From 4a5ce37ec69f7b8975c1ae75d60baf219ca57d1f Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Fri, 4 Aug 2023 19:43:40 +0200 Subject: [PATCH 114/447] Drop BuildToolFailure marker, recipe and table (#3331) * Drop BuildToolFailure marker, recipe and table For https://github.com/moderneinc/moderne-cli/issues/459 * Drop FailureLogAnalyzerTest too --- .../openrewrite/FindBuildToolFailures.java | 141 ------------------ .../openrewrite/marker/BuildToolFailure.java | 47 ------ .../openrewrite/table/BuildToolFailures.java | 52 ------- .../openrewrite/FailureLogAnalyzerTest.java | 53 ------- 4 files changed, 293 deletions(-) delete mode 100644 rewrite-core/src/main/java/org/openrewrite/FindBuildToolFailures.java delete mode 100644 rewrite-core/src/main/java/org/openrewrite/marker/BuildToolFailure.java delete mode 100644 rewrite-core/src/main/java/org/openrewrite/table/BuildToolFailures.java delete mode 100644 rewrite-core/src/test/java/org/openrewrite/FailureLogAnalyzerTest.java diff --git a/rewrite-core/src/main/java/org/openrewrite/FindBuildToolFailures.java b/rewrite-core/src/main/java/org/openrewrite/FindBuildToolFailures.java deleted file mode 100644 index 47f818f3560..00000000000 --- a/rewrite-core/src/main/java/org/openrewrite/FindBuildToolFailures.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite; - -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.marker.BuildToolFailure; -import org.openrewrite.marker.Markup; -import org.openrewrite.table.BuildToolFailures; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -import static java.util.Objects.requireNonNull; - -@Value -@EqualsAndHashCode(callSuper = true) -public class FindBuildToolFailures extends Recipe { - transient BuildToolFailures failures = new BuildToolFailures(this); - - @Option(displayName = "Suppress log output", - description = "Default false. If true, the `logOutput` column will be empty in the output table.", - required = false) - @Nullable - Boolean suppressLogOutput; - - @Override - public String getDisplayName() { - return "Find source files with `BuildToolFailure` markers"; - } - - @Override - public String getDescription() { - return "This recipe explores build tool failures after an LST is produced for classifying the types of " + - "failures that can occur and prioritizing fixes according to the most common problems."; - } - - @Override - public TreeVisitor<?, ExecutionContext> getVisitor() { - return new TreeVisitor<Tree, ExecutionContext>() { - @Override - public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { - SourceFile sourceFile = (SourceFile) requireNonNull(tree); - return sourceFile.getMarkers().findFirst(BuildToolFailure.class) - .<Tree>map(failure -> { - String logFileContents = sourceFile.printAll(); - String requiredJavaVersion = FailureLogAnalyzer.requiredJavaVersion(logFileContents); - if (suppressLogOutput != null && suppressLogOutput) { - logFileContents = ""; - } - failures.insertRow(ctx, new BuildToolFailures.Row( - failure.getType(), - failure.getVersion(), - failure.getCommand(), - failure.getExitCode(), - requiredJavaVersion, - logFileContents - )); - return Markup.info(sourceFile, String.format("Exit code %d", failure.getExitCode())); - }) - .orElse(sourceFile); - } - }; - } -} - -class FailureLogAnalyzer { - - // Downgrade Java when necessary - private static final Pattern UNSUPPORTED_CLASS_FILE_MAJOR_VERSION = Pattern.compile("Unsupported class file (?:major )?version (\\d+)", Pattern.CASE_INSENSITIVE); - private static final Pattern SOURCE_TARGET_OPTION = Pattern.compile("(?:Source|Target) option (\\d+) is no longer supported. Use \\d+ or later", Pattern.CASE_INSENSITIVE); - - // Upgrade Java when necessary - private static final Pattern CLASS_FILE_MAJOR_VERSION = Pattern.compile("class file (?:major )?version (\\d+)"); - private static final String INVALID_FLAG_RELEASE = "invalid flag: --release"; - private static final String ADD_EXPORTS = "Unrecognized option: --add-exports"; - private static final String MODULE_PATH = "javac: invalid flag: --module-path"; - - private static final Pattern BAD_OPTION_WAS_IGNORED = Pattern.compile("bad option '-target:(\\d+)' was ignored"); - private static final Pattern INCOMPATIBLE_COMPONENT = Pattern.compile("Incompatible because this component declares a component compatible with Java (\\d+)"); - private static final Pattern INVALID_SOURCE_TARGET_RELEASE = Pattern.compile("invalid (?:source|target) release: (?:1\\.)?(\\d+)"); - private static final Pattern RELEASE_VERSION_NOT_SUPPORTED = Pattern.compile("release version (\\d+) not supported"); - private static final Pattern SOURCE_TARGET_OBSOLETE = Pattern.compile("(?:source|target) value (?:1\\.)?(\\d+) is obsolete", Pattern.CASE_INSENSITIVE); - private static final Pattern TOOLCHAIN = Pattern.compile("\\[ERROR] jdk \\[ version='(?:1\\.)?(\\d+)' ]"); - private static final Pattern USE_SOURCE = Pattern.compile("use -source (\\d+) or higher to enable"); - - @Nullable - static String requiredJavaVersion(String logFileContents) { - // Possibly downgrade Java when necessary - Matcher matcher = UNSUPPORTED_CLASS_FILE_MAJOR_VERSION.matcher(logFileContents); - if (matcher.find()) { - // Oldest version we currently support, as message gives us no indication of what version is required - return "8"; - } - matcher = SOURCE_TARGET_OPTION.matcher(logFileContents); - if (matcher.find()) { - // Might return unsupported versions, which we will then have to skip - return matcher.group(1); - } - - // Possibly upgrade Java when necessary - matcher = CLASS_FILE_MAJOR_VERSION.matcher(logFileContents); - if (matcher.find()) { - // https://docs.oracle.com/javase/specs/jvms/se20/html/jvms-4.html#jvms-4.1-200-B.2 - return String.valueOf(Integer.parseInt(matcher.group(1)) - 44); - } - if (logFileContents.contains(INVALID_FLAG_RELEASE) || - logFileContents.contains(ADD_EXPORTS) || - logFileContents.contains(MODULE_PATH)) { - return "11"; // Technically 9+, but we'll go for 11 as it's an LTS release - } - return Stream.of( - BAD_OPTION_WAS_IGNORED, - INCOMPATIBLE_COMPONENT, - INVALID_SOURCE_TARGET_RELEASE, - RELEASE_VERSION_NOT_SUPPORTED, - SOURCE_TARGET_OBSOLETE, - TOOLCHAIN, - USE_SOURCE) - .map(pattern -> pattern.matcher(logFileContents)) - .filter(Matcher::find) - .map(m -> m.group(1)) - .findFirst() - .orElse(null); - } -} diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/BuildToolFailure.java b/rewrite-core/src/main/java/org/openrewrite/marker/BuildToolFailure.java deleted file mode 100644 index b4edfd010b7..00000000000 --- a/rewrite-core/src/main/java/org/openrewrite/marker/BuildToolFailure.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2021 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.marker; - -import lombok.EqualsAndHashCode; -import lombok.Value; -import lombok.With; -import org.openrewrite.internal.lang.Nullable; - -import java.util.UUID; - -@Value -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -@With -public class BuildToolFailure implements Marker { - @EqualsAndHashCode.Include - UUID id; - - /** - * The name of the build tool that failed, possibly a wrapper. - */ - String type; - @Nullable - String version; - - /** - * The command that was executed. - */ - @Nullable - String command; - - @Nullable - Integer exitCode; -} diff --git a/rewrite-core/src/main/java/org/openrewrite/table/BuildToolFailures.java b/rewrite-core/src/main/java/org/openrewrite/table/BuildToolFailures.java deleted file mode 100644 index 987fa8f16ac..00000000000 --- a/rewrite-core/src/main/java/org/openrewrite/table/BuildToolFailures.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2022 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.table; - -import com.fasterxml.jackson.annotation.JsonIgnoreType; -import lombok.Value; -import org.openrewrite.Column; -import org.openrewrite.DataTable; -import org.openrewrite.Recipe; - -@JsonIgnoreType -public class BuildToolFailures extends DataTable<BuildToolFailures.Row> { - public BuildToolFailures(Recipe recipe) { - super(recipe, - "Build tool failures", - "Log output of failed build tool runs along with exit code and further diagnostics."); - } - - @Value - public static class Row { - @Column(displayName = "Type", description = "The type of build tool that failed.") - String type; - - @Column(displayName = "Version", description = "The version of the build tool that failed, if available.") - String version; - - @Column(displayName = "Command", description = "The command that was executed.") - String command; - - @Column(displayName = "Exit code", description = "The exit code of the build tool run.") - Integer exitCode; - - @Column(displayName = "Required Java version", description = "The required Java version for the build, if detectable.") - String requiredJavaVersion; - - @Column(displayName = "Log output", description = "The log output of the build tool run.") - String logOutput; - } -} diff --git a/rewrite-core/src/test/java/org/openrewrite/FailureLogAnalyzerTest.java b/rewrite-core/src/test/java/org/openrewrite/FailureLogAnalyzerTest.java deleted file mode 100644 index 9c6138c64fd..00000000000 --- a/rewrite-core/src/test/java/org/openrewrite/FailureLogAnalyzerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import static org.assertj.core.api.Assertions.assertThat; - -class FailureLogAnalyzerTest { - - @ParameterizedTest - @CsvSource(delimiter = '|', textBlock = """ - BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 61 | 8 - error: Source option 6 is no longer supported. Use 7 or later. | 6 - error: source option 6 is no longer supported. Use 7 or later. | 6 - error: Target option 6 is no longer supported. Use 7 or later. | 6 - Error:PARSE ERROR: Error:unsupported class file version 53.0 | 8 - DefaultCodeFormatter has been compiled by a more recent version of the Java Runtime (class file version 55.0), | 11 - Fatal error compiling: invalid target release: 1.9 -> [Help 1] | 9 - Fatal error compiling: invalid target release: 17 -> [Help 1] | 17 - Fatal error compiling: invalid target release: 11 -> [Help 1] | 11 - Fatal error compiling: error: release version 17 not supported | 17 - javac: invalid target release: 11 | 11 - error: invalid source release: 17 | 17 - Fatal error compiling: invalid flag: --release | 11 - Unrecognized option: --add-exports | 11 - javac: invalid flag: --module-path | 11 - [ERROR] jdk [ version='1.8' ] | 8 - [WARNING] : bad option '-target:11' was ignored | 11 - [ERROR] warning: [options] source value 1.5 is obsolete and will be removed in a future release | 5 - [ERROR] warning: [options] target value 1.5 is obsolete and will be removed in a future release | 5 - (use -source 7 or higher to enable diamond operator) | 7 - (use -source 8 or higher to enable lambda expressions) | 8 - Incompatible because this component declares a component compatible with Java 11 and the consumer needed a component compatible with Java 8 | 11 - """) - void determineRequiredClassFileVersion(String logFileContents, String expected) { - assertThat(FailureLogAnalyzer.requiredJavaVersion(logFileContents)).isEqualTo(expected); - } -} From 76dab52bae9a2431f43af8460c0d68b2c5df4150 Mon Sep 17 00:00:00 2001 From: Nick McKinney <mckinneynicholas@gmail.com> Date: Fri, 4 Aug 2023 16:08:18 -0400 Subject: [PATCH 115/447] ExpectedToFail test case for #3458 --- rewrite-maven/build.gradle.kts | 1 + .../openrewrite/maven/AddDependencyTest.java | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/rewrite-maven/build.gradle.kts b/rewrite-maven/build.gradle.kts index 2a9c6f06417..6953d55d9ea 100755 --- a/rewrite-maven/build.gradle.kts +++ b/rewrite-maven/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation("org.apache.commons:commons-text:latest.release") testImplementation(project(":rewrite-test")) + testImplementation("org.junit-pioneer:junit-pioneer:2.0.0") testImplementation("com.squareup.okhttp3:mockwebserver:4.+") testImplementation("com.squareup.okio:okio-jvm:3.0.0") testImplementation("org.mapdb:mapdb:latest.release") diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java index b1a8664c2cb..516fd25ffaf 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.Issue; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaParser; @@ -522,6 +523,50 @@ void addDependencyDoesntAddWhenExistingDependency() { ); } + @Test + @ExpectedToFail + @Issue("https://github.com/openrewrite/rewrite/issues/3458") + void addDependencyOopsAllComments() { + rewriteRun( + spec -> spec.recipe(addDependency("com.google.guava:guava:29.0-jre", "com.google.common.math.IntMath")), + mavenProject( + "project", + srcMainJava( + java(usingGuavaIntMath) + ), + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <dependencies> + <!-- my cool dependencies section --> + <!-- etc --> + </dependencies> + </project> + """, + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <dependencies> + <!-- my cool dependencies section --> + <!-- etc --> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>29.0-jre</version> + </dependency> + </dependencies> + </project> + """ + ) + ) + ); + } + @Test void useManaged() { rewriteRun( From dc9f04ae6dd44ac6c981fa63c5ee165d85c8f3ed Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Sat, 5 Aug 2023 01:20:20 +0200 Subject: [PATCH 116/447] Add Maven dependency before only comments in <dependencies> (#3460) * Add Maven dependency before only comments in <dependencies> Fixes #3458 * Drop JUnit pioneer again * Restore original order; drop unused import --- rewrite-maven/build.gradle.kts | 1 - .../openrewrite/maven/internal/InsertDependencyComparator.java | 3 ++- .../src/test/java/org/openrewrite/maven/AddDependencyTest.java | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/rewrite-maven/build.gradle.kts b/rewrite-maven/build.gradle.kts index 6953d55d9ea..2a9c6f06417 100755 --- a/rewrite-maven/build.gradle.kts +++ b/rewrite-maven/build.gradle.kts @@ -38,7 +38,6 @@ dependencies { implementation("org.apache.commons:commons-text:latest.release") testImplementation(project(":rewrite-test")) - testImplementation("org.junit-pioneer:junit-pioneer:2.0.0") testImplementation("com.squareup.okhttp3:mockwebserver:4.+") testImplementation("com.squareup.okio:okio-jvm:3.0.0") testImplementation("org.mapdb:mapdb:latest.release") diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/InsertDependencyComparator.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/InsertDependencyComparator.java index fc8dbb6399a..6669c1321af 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/InsertDependencyComparator.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/InsertDependencyComparator.java @@ -64,7 +64,8 @@ public InsertDependencyComparator(List<? extends Content> existingDependencies, @Override public int compare(Content o1, Content o2) { - return positions.get(o1).compareTo(positions.get(o2)); + Float anotherFloat = positions.get(o2); + return anotherFloat == null ? 0 : positions.get(o1).compareTo(anotherFloat); } private static final Comparator<Xml.Tag> dependencyComparator = (d1, d2) -> { diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java index 516fd25ffaf..88ce237f8c5 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java @@ -19,7 +19,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.Issue; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaParser; @@ -524,7 +523,6 @@ void addDependencyDoesntAddWhenExistingDependency() { } @Test - @ExpectedToFail @Issue("https://github.com/openrewrite/rewrite/issues/3458") void addDependencyOopsAllComments() { rewriteRun( From bcec4d71f6b7a561d2867e8fd30e47e7f951e042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Schn=C3=A9ider?= <jkschneider@gmail.com> Date: Sat, 5 Aug 2023 08:40:41 -0400 Subject: [PATCH 117/447] Require parse-to-print idempotence (#3459) Co-authored-by: Tim te Beek <tim@moderne.io> --- .../org/openrewrite/ExecutionContext.java | 1 + .../src/main/java/org/openrewrite/Parser.java | 16 ++ .../main/java/org/openrewrite/SourceFile.java | 13 + .../gradle/search/DependencyInsight.java | 2 + .../org/openrewrite/groovy/GroovyParser.java | 2 +- .../java/org/openrewrite/hcl/HclParser.java | 18 +- .../java/isolated/ReloadableJava11Parser.java | 2 +- .../java/isolated/ReloadableJava17Parser.java | 2 +- .../java/ReloadableJava8Parser.java | 2 +- .../internal/template/JavaTemplateParser.java | 10 +- .../java/org/openrewrite/json/JsonParser.java | 18 +- .../maven/table/DependencyGraph.java | 49 ++++ .../properties/PropertiesParser.java | 14 +- .../org/openrewrite/protobuf/ProtoParser.java | 16 +- .../java/org/openrewrite/xml/XmlParser.java | 16 +- .../java/org/openrewrite/yaml/YamlParser.java | 20 +- .../yaml/AppendToSequenceTest.java | 248 +++++++++--------- 17 files changed, 266 insertions(+), 183 deletions(-) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/table/DependencyGraph.java diff --git a/rewrite-core/src/main/java/org/openrewrite/ExecutionContext.java b/rewrite-core/src/main/java/org/openrewrite/ExecutionContext.java index 5c37563b6d5..d7cb109d9b9 100644 --- a/rewrite-core/src/main/java/org/openrewrite/ExecutionContext.java +++ b/rewrite-core/src/main/java/org/openrewrite/ExecutionContext.java @@ -34,6 +34,7 @@ public interface ExecutionContext { String CURRENT_RECIPE = "org.openrewrite.currentRecipe"; String DATA_TABLES = "org.openrewrite.dataTables"; String RUN_TIMEOUT = "org.openrewrite.runTimeout"; + String REQUIRE_PRINT_EQUALS_INPUT = "org.openrewrite.requirePrintEqualsInput"; @Incubating(since = "7.20.0") default ExecutionContext addObserver(TreeObserver.Subscription observer) { diff --git a/rewrite-core/src/main/java/org/openrewrite/Parser.java b/rewrite-core/src/main/java/org/openrewrite/Parser.java index bb922ac454d..44e8bc2220b 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Parser.java +++ b/rewrite-core/src/main/java/org/openrewrite/Parser.java @@ -20,6 +20,7 @@ import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingExecutionContextView; import java.io.*; @@ -38,6 +39,21 @@ import static java.util.stream.Collectors.toList; public interface Parser { + @Incubating(since = "8.2.0") + default SourceFile requirePrintEqualsInput(SourceFile sourceFile, Parser.Input input, @Nullable Path relativeTo, ExecutionContext ctx) { + if (ctx.getMessage(ExecutionContext.REQUIRE_PRINT_EQUALS_INPUT, true) && + !sourceFile.printEqualsInput(input, ctx)) { + return ParseError.build( + this, + input, + relativeTo, + ctx, + new IllegalStateException(sourceFile.getSourcePath() + " is not print idempotent.") + ).withErroneous(sourceFile); + } + return sourceFile; + } + default Stream<SourceFile> parse(Iterable<Path> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { return parseInputs(StreamSupport .stream(sourceFiles.spliterator(), false) diff --git a/rewrite-core/src/main/java/org/openrewrite/SourceFile.java b/rewrite-core/src/main/java/org/openrewrite/SourceFile.java index 429ef739174..4b2b3c0661b 100644 --- a/rewrite-core/src/main/java/org/openrewrite/SourceFile.java +++ b/rewrite-core/src/main/java/org/openrewrite/SourceFile.java @@ -15,6 +15,7 @@ */ package org.openrewrite; +import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.style.NamedStyles; import org.openrewrite.style.Style; @@ -26,6 +27,18 @@ import java.util.function.Predicate; public interface SourceFile extends Tree { + + /** + * Does this source file represented as an LST, when printed, produce a byte-for-byte identical + * result to the original input source file? + * + * @param input The input source. + * @return <code>true</code> if the parse-to-print loop is idempotent, <code>false</code> otherwise. + */ + default boolean printEqualsInput(Parser.Input input, ExecutionContext ctx) { + return printAll().equals(StringUtils.readFully(input.getSource(ctx))); + } + /** * @return An absolute or relative file path. */ diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java index d9bd5c3f60e..df7dd7a02b3 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java @@ -30,6 +30,7 @@ import org.openrewrite.java.tree.J; import org.openrewrite.marker.SearchResult; import org.openrewrite.maven.table.DependenciesInUse; +import org.openrewrite.maven.table.DependencyGraph; import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.maven.tree.ResolvedDependency; @@ -44,6 +45,7 @@ @EqualsAndHashCode(callSuper = true) public class DependencyInsight extends Recipe { transient DependenciesInUse dependenciesInUse = new DependenciesInUse(this); + transient DependencyGraph dependencyGraph = new DependencyGraph(this); private static final MethodMatcher DEPENDENCY_CONFIGURATION_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java index 2cba1c33533..e1f6fce8734 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java @@ -159,7 +159,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re gcu = gcu.withMarkers(m); } pctx.getParsingListener().parsed(compiled.getInput(), gcu); - return gcu; + return requirePrintEqualsInput(gcu, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); return ParseError.build(this, input, relativeTo, ctx, t); diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclParser.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclParser.java index 6f74a00e122..c46f6fdf09e 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclParser.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclParser.java @@ -46,34 +46,34 @@ private HclParser(List<NamedStyles> styles) { @Override public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - return acceptedInputs(sourceFiles).map(sourceFile -> { + return acceptedInputs(sourceFiles).map(input -> { try { - EncodingDetectingInputStream is = sourceFile.getSource(ctx); + EncodingDetectingInputStream is = input.getSource(ctx); String sourceStr = is.readFully(); HCLLexer lexer = new HCLLexer(CharStreams.fromString(sourceStr)); lexer.removeErrorListeners(); - lexer.addErrorListener(new ForwardingErrorListener(sourceFile.getPath(), ctx)); + lexer.addErrorListener(new ForwardingErrorListener(input.getPath(), ctx)); HCLParser parser = new HCLParser(new CommonTokenStream(lexer)); parser.removeErrorListeners(); - parser.addErrorListener(new ForwardingErrorListener(sourceFile.getPath(), ctx)); + parser.addErrorListener(new ForwardingErrorListener(input.getPath(), ctx)); Hcl.ConfigFile configFile = (Hcl.ConfigFile) new HclParserVisitor( - sourceFile.getRelativePath(relativeTo), + input.getRelativePath(relativeTo), sourceStr, is.getCharset(), is.isCharsetBomMarked(), - sourceFile.getFileAttributes() + input.getFileAttributes() ).visitConfigFile(parser.configFile()); configFile = configFile.withMarkers(Markers.build(styles)); - parsingListener.parsed(sourceFile, configFile); - return (SourceFile) configFile; + parsingListener.parsed(input, configFile); + return requirePrintEqualsInput(configFile, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); - return ParseError.build(this, sourceFile, relativeTo, ctx, t); + return ParseError.build(this, input, relativeTo, ctx, t); } }); } diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java index 578c08bcdf8..ecd9e5cca57 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java @@ -169,7 +169,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat J.CompilationUnit cu = (J.CompilationUnit) parser.scan(cuByPath.getValue(), Space.EMPTY); cuByPath.setValue(null); // allow memory used by this JCCompilationUnit to be released parsingListener.parsed(input, cu); - return cu; + return requirePrintEqualsInput(cu, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); return ParseError.build(this, input, relativeTo, ctx, t); diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java index 1a6801cf809..9c0b885b1b0 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java @@ -165,7 +165,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat J.CompilationUnit cu = (J.CompilationUnit) parser.scan(cuByPath.getValue(), Space.EMPTY); cuByPath.setValue(null); // allow memory used by this JCCompilationUnit to be released parsingListener.parsed(input, cu); - return cu; + return requirePrintEqualsInput(cu, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); return ParseError.build(this, input, relativeTo, ctx, t); 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 7a2dd6bc681..47cb26056a2 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 @@ -192,7 +192,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat J.CompilationUnit cu = (J.CompilationUnit) parser.scan(cuByPath.getValue(), Space.EMPTY); cuByPath.setValue(null); // allow memory used by this JCCompilationUnit to be released parsingListener.parsed(input, cu); - return (SourceFile) cu; + return requirePrintEqualsInput(cu, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); return ParseError.build(this, input, relativeTo, ctx, t); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateParser.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateParser.java index 72321855a7a..6d6fc121e21 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateParser.java @@ -234,11 +234,15 @@ private String addImports(String stub) { private JavaSourceFile compileTemplate(@Language("java") String stub) { ExecutionContext ctx = new InMemoryExecutionContext(); ctx.putMessage(JavaParser.SKIP_SOURCE_SET_TYPE_GENERATION, true); + ctx.putMessage(ExecutionContext.REQUIRE_PRINT_EQUALS_INPUT, false); JavaParser jp = parser.clone().build(); - return (JavaSourceFile) (stub.contains("@SubAnnotation") ? + return (stub.contains("@SubAnnotation") ? jp.reset().parse(ctx, stub, SUBSTITUTED_ANNOTATION) : - jp.reset().parse(ctx, stub) - ).findFirst().orElseThrow(() -> new IllegalArgumentException("Could not parse as Java")); + jp.reset().parse(ctx, stub)) + .findFirst() + .filter(JavaSourceFile.class::isInstance) // Filters out ParseErrors + .map(JavaSourceFile.class::cast) + .orElseThrow(() -> new IllegalArgumentException("Could not parse as Java")); } /** diff --git a/rewrite-json/src/main/java/org/openrewrite/json/JsonParser.java b/rewrite-json/src/main/java/org/openrewrite/json/JsonParser.java index 4cce201b98c..c0573bafac3 100755 --- a/rewrite-json/src/main/java/org/openrewrite/json/JsonParser.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/JsonParser.java @@ -36,24 +36,24 @@ public class JsonParser implements Parser { @Override public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - return acceptedInputs(sourceFiles).map(sourceFile -> { - try (InputStream sourceStream = sourceFile.getSource(ctx)) { + return acceptedInputs(sourceFiles).map(input -> { + try (InputStream sourceStream = input.getSource(ctx)) { JSON5Parser parser = new JSON5Parser(new CommonTokenStream(new JSON5Lexer( CharStreams.fromStream(sourceStream)))); parser.removeErrorListeners(); - parser.addErrorListener(new ForwardingErrorListener(sourceFile.getPath(), ctx)); + parser.addErrorListener(new ForwardingErrorListener(input.getPath(), ctx)); Json.Document document = new JsonParserVisitor( - sourceFile.getRelativePath(relativeTo), - sourceFile.getFileAttributes(), - sourceFile.getSource(ctx) + input.getRelativePath(relativeTo), + input.getFileAttributes(), + input.getSource(ctx) ).visitJson5(parser.json5()); - parsingListener.parsed(sourceFile, document); - return document; + parsingListener.parsed(input, document); + return requirePrintEqualsInput(document, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); - return ParseError.build(this, sourceFile, relativeTo, ctx, t); + return ParseError.build(this, input, relativeTo, ctx, t); } }); } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/table/DependencyGraph.java b/rewrite-maven/src/main/java/org/openrewrite/maven/table/DependencyGraph.java new file mode 100644 index 00000000000..4e2d3cf8fdf --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/table/DependencyGraph.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.table; + +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +public class DependencyGraph extends DataTable<DependencyGraph.Row> { + + public DependencyGraph(Recipe recipe) { + super(recipe, DependencyGraph.Row.class, + DependenciesInUse.class.getName(), + "Dependency graph", "Relationships between dependencies."); + } + + @Value + public static class Row { + @Column(displayName = "Project name", + description = "The name of the project that contains the dependency.") + String projectName; + + @Column(displayName = "Source set", + description = "The source set that contains the dependency.") + String sourceSet; + + @Column(displayName = "From dependency", + description = "A dependency that depends on the 'to' dependency.") + String from; + + @Column(displayName = "From dependency", + description = "A dependency that depends on the 'to' dependency.") + String to; + } +} diff --git a/rewrite-properties/src/main/java/org/openrewrite/properties/PropertiesParser.java b/rewrite-properties/src/main/java/org/openrewrite/properties/PropertiesParser.java index 233c0c3034d..c8c7d5fae7e 100755 --- a/rewrite-properties/src/main/java/org/openrewrite/properties/PropertiesParser.java +++ b/rewrite-properties/src/main/java/org/openrewrite/properties/PropertiesParser.java @@ -41,16 +41,16 @@ public Stream<SourceFile> parse(@Language("properties") String... sources) { @Override public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - return acceptedInputs(sourceFiles).map(sourceFile -> { - Path path = sourceFile.getRelativePath(relativeTo); - try (EncodingDetectingInputStream is = sourceFile.getSource(ctx)) { + return acceptedInputs(sourceFiles).map(input -> { + Path path = input.getRelativePath(relativeTo); + try (EncodingDetectingInputStream is = input.getSource(ctx)) { Properties.File file = parseFromInput(path, is) - .withFileAttributes(sourceFile.getFileAttributes()); - parsingListener.parsed(sourceFile, file); - return file; + .withFileAttributes(input.getFileAttributes()); + parsingListener.parsed(input, file); + return requirePrintEqualsInput(file, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); - return ParseError.build(this, sourceFile, relativeTo, ctx, t); + return ParseError.build(this, input, relativeTo, ctx, t); } }); } diff --git a/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java b/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java index 6ac46e23697..3fce1fd3fb2 100644 --- a/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java +++ b/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java @@ -38,16 +38,16 @@ public class ProtoParser implements Parser { @Override public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - return acceptedInputs(sourceFiles).map(sourceFile -> { - Path path = sourceFile.getRelativePath(relativeTo); + return acceptedInputs(sourceFiles).map(input -> { + Path path = input.getRelativePath(relativeTo); try { - EncodingDetectingInputStream is = sourceFile.getSource(ctx); + EncodingDetectingInputStream is = input.getSource(ctx); String sourceStr = is.readFully(); Protobuf2Parser parser = new Protobuf2Parser(new CommonTokenStream(new Protobuf2Lexer( CharStreams.fromString(sourceStr)))); parser.removeErrorListeners(); - parser.addErrorListener(new ForwardingErrorListener(sourceFile.getPath(), ctx)); + parser.addErrorListener(new ForwardingErrorListener(input.getPath(), ctx)); if (sourceStr.contains("proto3")) { return null; @@ -55,16 +55,16 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat Proto.Document document = new ProtoParserVisitor( path, - sourceFile.getFileAttributes(), + input.getFileAttributes(), sourceStr, is.getCharset(), is.isCharsetBomMarked() ).visitProto(parser.proto()); - parsingListener.parsed(sourceFile, document); - return document; + parsingListener.parsed(input, document); + return requirePrintEqualsInput(document, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); - return ParseError.build(this, sourceFile, relativeTo, ctx, t); + return ParseError.build(this, input, relativeTo, ctx, t); } }) // filter out the nulls produced for `proto3` sources diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java b/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java index 529c99256bb..0cffadbe958 100755 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java @@ -36,29 +36,29 @@ public class XmlParser implements Parser { @Override public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - return acceptedInputs(sourceFiles).map(sourceFile -> { - Path path = sourceFile.getRelativePath(relativeTo); - try (EncodingDetectingInputStream is = sourceFile.getSource(ctx)) { + return acceptedInputs(sourceFiles).map(input -> { + Path path = input.getRelativePath(relativeTo); + try (EncodingDetectingInputStream is = input.getSource(ctx)) { String sourceStr = is.readFully(); XMLParser parser = new XMLParser(new CommonTokenStream(new XMLLexer( CharStreams.fromString(sourceStr)))); parser.removeErrorListeners(); - parser.addErrorListener(new ForwardingErrorListener(sourceFile.getPath(), ctx)); + parser.addErrorListener(new ForwardingErrorListener(input.getPath(), ctx)); Xml.Document document = new XmlParserVisitor( path, - sourceFile.getFileAttributes(), + input.getFileAttributes(), sourceStr, is.getCharset(), is.isCharsetBomMarked() ).visitDocument(parser.document()); - parsingListener.parsed(sourceFile, document); - return document; + parsingListener.parsed(input, document); + return requirePrintEqualsInput(document, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); - return ParseError.build(this, sourceFile, relativeTo, ctx, t); + return ParseError.build(this, input, relativeTo, ctx, t); } }); } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index 5de0129d96f..1339bd8f308 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -60,18 +60,19 @@ public Stream<SourceFile> parse(@Language("yml") String... sources) { public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); return acceptedInputs(sourceFiles) - .map(sourceFile -> { - Path path = sourceFile.getRelativePath(relativeTo); - try (EncodingDetectingInputStream is = sourceFile.getSource(ctx)) { + .map(input -> { + Path path = input.getRelativePath(relativeTo); + try (EncodingDetectingInputStream is = input.getSource(ctx)) { Yaml.Documents yaml = parseFromInput(path, is); - parsingListener.parsed(sourceFile, yaml); - return yaml.withFileAttributes(sourceFile.getFileAttributes()); + parsingListener.parsed(input, yaml); + yaml = yaml.withFileAttributes(input.getFileAttributes()); + yaml = unwrapPrefixedMappings(yaml); + return requirePrintEqualsInput(yaml, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); - return ParseError.build(this, sourceFile, relativeTo, ctx, t); + return ParseError.build(this, input, relativeTo, ctx, t); } }) - .map(this::unwrapPrefixedMappings) .map(sourceFile -> { if (sourceFile instanceof Yaml.Documents) { Yaml.Documents docs = (Yaml.Documents) sourceFile; @@ -526,10 +527,7 @@ public Sequence withPrefix(String prefix) { } } - private SourceFile unwrapPrefixedMappings(SourceFile y) { - if (!(y instanceof Yaml.Documents)) { - return y; - } + private Yaml.Documents unwrapPrefixedMappings(Yaml.Documents y) { //noinspection ConstantConditions return (Yaml.Documents) new YamlIsoVisitor<Integer>() { @Override diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/AppendToSequenceTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/AppendToSequenceTest.java index f9546a3c09c..492d4720073 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/AppendToSequenceTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/AppendToSequenceTest.java @@ -40,17 +40,17 @@ void appendToSequenceHasDashTrue() { )), yaml( """ - things: - fruit: - - apple - - blueberry + things: + fruit: + - apple + - blueberry """, """ - things: - fruit: - - apple - - blueberry - - strawberry + things: + fruit: + - apple + - blueberry + - strawberry """ ) ); @@ -68,17 +68,17 @@ void appendToSequenceOfSingleQuotedValuesHasDashTrue() { )), yaml( """ - things: - fruit: - - 'apple' - - 'blueberry' + things: + fruit: + - 'apple' + - 'blueberry' """, """ - things: - fruit: - - 'apple' - - 'blueberry' - - 'strawberry' + things: + fruit: + - 'apple' + - 'blueberry' + - 'strawberry' """ ) ); @@ -96,17 +96,17 @@ void appendToSequenceWhenExistingSequenceValuesMatchInExactOrder() { )), yaml( """ - things: - fruit: - - apple - - blueberry + things: + fruit: + - apple + - blueberry """, """ - things: - fruit: - - apple - - blueberry - - strawberry + things: + fruit: + - apple + - blueberry + - strawberry """ ) ); @@ -124,17 +124,17 @@ void appendToSequenceWhenExistingSequenceValuesMatchInAnyOrder() { )), yaml( """ - things: - fruit: - - apple - - blueberry + things: + fruit: + - apple + - blueberry """, """ - things: - fruit: - - apple - - blueberry - - strawberry + things: + fruit: + - apple + - blueberry + - strawberry """ ) ); @@ -152,10 +152,10 @@ void doNotAppendToSequenceWhenExistingSequenceValuesDoNotMatch() { )), yaml( """ - things: - fruit: - - apple - - blueberry + things: + fruit: + - apple + - blueberry """ ) ); @@ -173,23 +173,23 @@ void appendToSequenceOfNameValuePair() { )), yaml( """ - things: - fruit: - - name: apple - - name: blueberry - animals: - - cat - - dog + things: + fruit: + - name: apple + - name: blueberry + animals: + - cat + - dog """, """ - things: - fruit: - - name: apple - - name: blueberry - - name: strawberry - animals: - - cat - - dog + things: + fruit: + - name: apple + - name: blueberry + - name: strawberry + animals: + - cat + - dog """ ) ); @@ -207,23 +207,23 @@ void appendToSequenceOfNameValuePairMatchExistingValuesInAnyOrder() { )), yaml( """ - things: - fruit: - - name: apple - - name: blueberry - animals: - - cat - - dog + things: + fruit: + - name: apple + - name: blueberry + animals: + - cat + - dog """, """ - things: - fruit: - - name: apple - - name: blueberry - - name: strawberry - animals: - - cat - - dog + things: + fruit: + - name: apple + - name: blueberry + - name: strawberry + animals: + - cat + - dog """ ) ); @@ -241,18 +241,18 @@ void appendToSequenceOfLiteralsHasDashFalse() { )), yaml( """ - things: - fruit: [apple, blueberry] - animals: - - cat - - dog + things: + fruit: [apple, blueberry] + animals: + - cat + - dog """, """ - things: - fruit: [apple, blueberry, strawberry] - animals: - - cat - - dog + things: + fruit: [apple, blueberry, strawberry] + animals: + - cat + - dog """ ) ); @@ -270,18 +270,18 @@ void appendToSequenceOfSingleQuotedValuesHasDashFalse() { )), yaml( """ - things: - fruit: ['apple', 'blueberry'] - animals: - - cat - - dog + things: + fruit: ['apple', 'blueberry'] + animals: + - cat + - dog """, """ - things: - fruit: ['apple', 'blueberry', 'strawberry'] - animals: - - cat - - dog + things: + fruit: ['apple', 'blueberry', 'strawberry'] + animals: + - cat + - dog """ ) ); @@ -299,18 +299,18 @@ void appendToSequenceOfDoubleQuotedValuesHasDashFalse() { )), yaml( """ - things: - fruit: ["apple", "blueberry"] - animals: - - cat - - dog + things: + fruit: ["apple", "blueberry"] + animals: + - cat + - dog """, """ - things: - fruit: ["apple", "blueberry", "strawberry"] - animals: - - cat - - dog + things: + fruit: ["apple", "blueberry", "strawberry"] + animals: + - cat + - dog """ ) ); @@ -328,12 +328,12 @@ void appendToEmptySequence() { )), yaml( """ - things: - fruit: [] + things: + fruit: [] """, """ - things: - fruit: [strawberry] + things: + fruit: [strawberry] """ ) ); @@ -351,21 +351,21 @@ void modifyRegionList() { )), yaml( """ - prod: - regions: - - name: us-bar-1 - - name: us-foo-1 - other: - - name: outerspace-1 + prod: + regions: + - name: us-bar-1 + - name: us-foo-1 + other: + - name: outerspace-1 """, """ - prod: - regions: - - name: us-bar-1 - - name: us-foo-1 - - name: us-foo-2 - other: - - name: outerspace-1 + prod: + regions: + - name: us-bar-1 + - name: us-foo-1 + - name: us-foo-2 + other: + - name: outerspace-1 """ ) ); @@ -383,13 +383,13 @@ void doesNotModifyRegionListBecauseValueIsAlreadyPresent() { )), yaml( """ - prod: - regions: - - name: us-bar-1 - - name: us-foo-1 - - name: us-foo-2 - other: - - name: outerspace-1 + prod: + regions: + - name: us-bar-1 + - name: us-foo-1 + - name: us-foo-2 + other: + - name: outerspace-1 """ ) ); From 361b7ba095abaa0381da124049de20355a3a398a Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sat, 5 Aug 2023 08:59:59 -0400 Subject: [PATCH 118/447] Add FindParseToPrintInequality --- .../src/main/java/org/openrewrite/Result.java | 115 +------------ .../internal/InMemoryDiffEntry.java | 151 ++++++++++++++++++ .../search/FindParseToPrintInequality.java | 80 ++++++++++ .../table/ParseToPrintInequalities.java | 44 +++++ .../InMemoryDiffEntryTest.java} | 19 +-- 5 files changed, 286 insertions(+), 123 deletions(-) create mode 100644 rewrite-core/src/main/java/org/openrewrite/internal/InMemoryDiffEntry.java create mode 100644 rewrite-core/src/main/java/org/openrewrite/search/FindParseToPrintInequality.java create mode 100644 rewrite-core/src/main/java/org/openrewrite/table/ParseToPrintInequalities.java rename rewrite-core/src/test/java/org/openrewrite/{ResultTest.java => internal/InMemoryDiffEntryTest.java} (94%) diff --git a/rewrite-core/src/main/java/org/openrewrite/Result.java b/rewrite-core/src/main/java/org/openrewrite/Result.java index d62db9cff25..e2fc144eec2 100755 --- a/rewrite-core/src/main/java/org/openrewrite/Result.java +++ b/rewrite-core/src/main/java/org/openrewrite/Result.java @@ -23,6 +23,7 @@ import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; import org.eclipse.jgit.lib.*; import org.openrewrite.config.RecipeDescriptor; +import org.openrewrite.internal.InMemoryDiffEntry; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.RecipesThatMadeChanges; @@ -185,118 +186,4 @@ public String diff(@Nullable Path relativeTo, @Nullable PrintOutputCapture.Marke public String toString() { return diff(); } - - static class InMemoryDiffEntry extends DiffEntry implements AutoCloseable { - - static final AbbreviatedObjectId A_ZERO = AbbreviatedObjectId - .fromObjectId(ObjectId.zeroId()); - - private final InMemoryRepository repo; - private final Set<Recipe> recipesThatMadeChanges; - - InMemoryDiffEntry(@Nullable Path originalFilePath, @Nullable Path filePath, @Nullable Path relativeTo, String oldSource, - String newSource, Set<Recipe> recipesThatMadeChanges) { - this(originalFilePath, filePath, relativeTo, oldSource, newSource, recipesThatMadeChanges, FileMode.REGULAR_FILE, FileMode.REGULAR_FILE); - } - - InMemoryDiffEntry(@Nullable Path originalFilePath, @Nullable Path filePath, @Nullable Path relativeTo, String oldSource, - String newSource, Set<Recipe> recipesThatMadeChanges, FileMode oldMode, FileMode newMode) { - - this.recipesThatMadeChanges = recipesThatMadeChanges; - - try { - this.repo = new InMemoryRepository.Builder() - .setRepositoryDescription(new DfsRepositoryDescription()) - .build(); - - try (ObjectInserter inserter = repo.getObjectDatabase().newInserter()) { - - if (originalFilePath != null) { - this.oldId = inserter.insert(Constants.OBJ_BLOB, oldSource.getBytes(StandardCharsets.UTF_8)).abbreviate(40); - this.oldMode = oldMode; - this.oldPath = (relativeTo == null ? originalFilePath : relativeTo.relativize(originalFilePath)).toString().replace("\\", "/"); - } else { - this.oldId = A_ZERO; - this.oldMode = FileMode.MISSING; - this.oldPath = DEV_NULL; - } - - if (filePath != null) { - this.newId = inserter.insert(Constants.OBJ_BLOB, newSource.getBytes(StandardCharsets.UTF_8)).abbreviate(40); - this.newMode = newMode; - this.newPath = (relativeTo == null ? filePath : relativeTo.relativize(filePath)).toString().replace("\\", "/"); - } else { - this.newId = A_ZERO; - this.newMode = FileMode.MISSING; - this.newPath = DEV_NULL; - } - inserter.flush(); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - if (this.oldMode == FileMode.MISSING && this.newMode != FileMode.MISSING) { - this.changeType = ChangeType.ADD; - } else if (this.oldMode != FileMode.MISSING && this.newMode == FileMode.MISSING) { - this.changeType = ChangeType.DELETE; - } else if (!oldPath.equals(newPath)) { - this.changeType = ChangeType.RENAME; - } else { - this.changeType = ChangeType.MODIFY; - } - } - - String getDiff() { - return getDiff(false); - } - - String getDiff(@Nullable Boolean ignoreAllWhitespace) { - if (ignoreAllWhitespace == null) { - ignoreAllWhitespace = false; - } - - if (oldId.equals(newId) && oldPath.equals(newPath)) { - return ""; - } - - ByteArrayOutputStream patch = new ByteArrayOutputStream(); - try (DiffFormatter formatter = new DiffFormatter(patch)) { - formatter.setDiffComparator(ignoreAllWhitespace ? RawTextComparator.WS_IGNORE_ALL : RawTextComparator.DEFAULT); - formatter.setRepository(repo); - formatter.format(this); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - String diff = patch.toString(); - - AtomicBoolean addedComment = new AtomicBoolean(false); - // NOTE: String.lines() would remove empty lines which we don't want - return Arrays.stream(diff.split("\n")) - .map(l -> { - if (!addedComment.get() && l.startsWith("@@") && l.endsWith("@@")) { - addedComment.set(true); - - Set<String> sortedRecipeNames = new LinkedHashSet<>(); - for (Recipe recipesThatMadeChange : recipesThatMadeChanges) { - sortedRecipeNames.add(recipesThatMadeChange.getName()); - } - StringJoiner joinedRecipeNames = new StringJoiner(", ", " ", ""); - for (String name : sortedRecipeNames) { - joinedRecipeNames.add(name); - } - - return l + joinedRecipeNames; - } - return l; - }) - .collect(Collectors.joining("\n")) + "\n"; - } - - @Override - public void close() { - this.repo.close(); - } - } } diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryDiffEntry.java b/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryDiffEntry.java new file mode 100644 index 00000000000..42fa06de793 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryDiffEntry.java @@ -0,0 +1,151 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.internal; + +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.diff.RawTextComparator; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.lib.*; +import org.openrewrite.Recipe; +import org.openrewrite.internal.lang.Nullable; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +public class InMemoryDiffEntry extends DiffEntry implements AutoCloseable { + + static final AbbreviatedObjectId A_ZERO = AbbreviatedObjectId + .fromObjectId(ObjectId.zeroId()); + + private final InMemoryRepository repo; + private final Set<Recipe> recipesThatMadeChanges; + + public InMemoryDiffEntry(@Nullable Path originalFilePath, @Nullable Path filePath, @Nullable Path relativeTo, String oldSource, + String newSource, Set<Recipe> recipesThatMadeChanges) { + this(originalFilePath, filePath, relativeTo, oldSource, newSource, recipesThatMadeChanges, FileMode.REGULAR_FILE, FileMode.REGULAR_FILE); + } + + public InMemoryDiffEntry(@Nullable Path originalFilePath, @Nullable Path filePath, @Nullable Path relativeTo, String oldSource, + String newSource, Set<Recipe> recipesThatMadeChanges, FileMode oldMode, FileMode newMode) { + + this.recipesThatMadeChanges = recipesThatMadeChanges; + + try { + this.repo = new InMemoryRepository.Builder() + .setRepositoryDescription(new DfsRepositoryDescription()) + .build(); + + try (ObjectInserter inserter = repo.getObjectDatabase().newInserter()) { + + if (originalFilePath != null) { + this.oldId = inserter.insert(Constants.OBJ_BLOB, oldSource.getBytes(StandardCharsets.UTF_8)).abbreviate(40); + this.oldMode = oldMode; + this.oldPath = (relativeTo == null ? originalFilePath : relativeTo.relativize(originalFilePath)).toString().replace("\\", "/"); + } else { + this.oldId = A_ZERO; + this.oldMode = FileMode.MISSING; + this.oldPath = DEV_NULL; + } + + if (filePath != null) { + this.newId = inserter.insert(Constants.OBJ_BLOB, newSource.getBytes(StandardCharsets.UTF_8)).abbreviate(40); + this.newMode = newMode; + this.newPath = (relativeTo == null ? filePath : relativeTo.relativize(filePath)).toString().replace("\\", "/"); + } else { + this.newId = A_ZERO; + this.newMode = FileMode.MISSING; + this.newPath = DEV_NULL; + } + inserter.flush(); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + if (this.oldMode == FileMode.MISSING && this.newMode != FileMode.MISSING) { + this.changeType = ChangeType.ADD; + } else if (this.oldMode != FileMode.MISSING && this.newMode == FileMode.MISSING) { + this.changeType = ChangeType.DELETE; + } else if (!oldPath.equals(newPath)) { + this.changeType = ChangeType.RENAME; + } else { + this.changeType = ChangeType.MODIFY; + } + } + + public String getDiff() { + return getDiff(false); + } + + public String getDiff(@Nullable Boolean ignoreAllWhitespace) { + if (ignoreAllWhitespace == null) { + ignoreAllWhitespace = false; + } + + if (oldId.equals(newId) && oldPath.equals(newPath)) { + return ""; + } + + ByteArrayOutputStream patch = new ByteArrayOutputStream(); + try (DiffFormatter formatter = new DiffFormatter(patch)) { + formatter.setDiffComparator(ignoreAllWhitespace ? RawTextComparator.WS_IGNORE_ALL : RawTextComparator.DEFAULT); + formatter.setRepository(repo); + formatter.format(this); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + String diff = patch.toString(); + + AtomicBoolean addedComment = new AtomicBoolean(false); + // NOTE: String.lines() would remove empty lines which we don't want + return Arrays.stream(diff.split("\n")) + .map(l -> { + if (!addedComment.get() && l.startsWith("@@") && l.endsWith("@@")) { + addedComment.set(true); + + Set<String> sortedRecipeNames = new LinkedHashSet<>(); + for (Recipe recipesThatMadeChange : recipesThatMadeChanges) { + sortedRecipeNames.add(recipesThatMadeChange.getName()); + } + StringJoiner joinedRecipeNames = new StringJoiner(", ", " ", ""); + for (String name : sortedRecipeNames) { + joinedRecipeNames.add(name); + } + + return l + joinedRecipeNames; + } + return l; + }) + .collect(Collectors.joining("\n")) + "\n"; + } + + @Override + public void close() { + this.repo.close(); + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/search/FindParseToPrintInequality.java b/rewrite-core/src/main/java/org/openrewrite/search/FindParseToPrintInequality.java new file mode 100644 index 00000000000..2f7012092cd --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/search/FindParseToPrintInequality.java @@ -0,0 +1,80 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.search; + +import org.eclipse.jgit.lib.FileMode; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.InMemoryDiffEntry; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.SearchResult; +import org.openrewrite.table.ParseToPrintInequalities; +import org.openrewrite.tree.ParseError; + +import java.util.Collections; + +public class FindParseToPrintInequality extends Recipe { + transient ParseToPrintInequalities inequalities = new ParseToPrintInequalities(this); + + @Override + public String getDisplayName() { + return "Find parse to print inequality"; + } + + @Override + public String getDescription() { + return "OpenRewrite `Parser` implementations should produce `SourceFile` objects whose `printAll()` " + + "method should be byte-for-byte equivalent with the original source file. When this isn't true, " + + "recipes can still run on the `SourceFile` and even produce diffs, but the diffs would fail to " + + "apply as a patch to the original source file. Most `Parser` use `Parser#requirePrintEqualsInput` " + + "to produce a `ParseError` when they fail to produce a `SourceFile` that is print idempotent."; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new TreeVisitor<Tree, ExecutionContext>() { + @Override + public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof ParseError) { + ParseError parseError = (ParseError) tree; + if (parseError.getErroneous() != null) { + FileMode mode = parseError.getFileAttributes() != null && parseError.getFileAttributes() + .isExecutable() ? FileMode.EXECUTABLE_FILE : FileMode.REGULAR_FILE; + try (InMemoryDiffEntry diffEntry = new InMemoryDiffEntry( + parseError.getSourcePath(), + parseError.getSourcePath(), + null, + parseError.getText(), + parseError.getErroneous().printAll(), + Collections.emptySet(), + mode, + mode + )) { + inequalities.insertRow(ctx, new ParseToPrintInequalities.Row( + parseError.getSourcePath().toString(), + diffEntry.getDiff(false) + )); + } + return SearchResult.found(parseError); + } + } + return super.visit(tree, ctx); + } + }; + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/table/ParseToPrintInequalities.java b/rewrite-core/src/main/java/org/openrewrite/table/ParseToPrintInequalities.java new file mode 100644 index 00000000000..08ef688aa3c --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/table/ParseToPrintInequalities.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.table; + +import com.fasterxml.jackson.annotation.JsonIgnoreType; +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; +import org.openrewrite.internal.lang.Nullable; + +@JsonIgnoreType +public class ParseToPrintInequalities extends DataTable<ParseToPrintInequalities.Row> { + public ParseToPrintInequalities(Recipe recipe) { + super(recipe, + "Parser to print inequalities", + "A list of files that parsers produced `SourceFile` which, when printed, " + + "didn't match the original source code."); + } + + @Value + public static class Row { + @Column(displayName = "Source path", description = "The file that failed to parse.") + String sourcePath; + + @Column(displayName = "Diff", + description = "The diff between the original source code and the printed `SourceFile`.") + @Nullable + String diff; + } +} diff --git a/rewrite-core/src/test/java/org/openrewrite/ResultTest.java b/rewrite-core/src/test/java/org/openrewrite/internal/InMemoryDiffEntryTest.java similarity index 94% rename from rewrite-core/src/test/java/org/openrewrite/ResultTest.java rename to rewrite-core/src/test/java/org/openrewrite/internal/InMemoryDiffEntryTest.java index f8387fcef13..e8b7a4fc9bd 100644 --- a/rewrite-core/src/test/java/org/openrewrite/ResultTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/internal/InMemoryDiffEntryTest.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite; +package org.openrewrite.internal; import org.eclipse.jgit.lib.FileMode; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.openrewrite.internal.InMemoryDiffEntry; import org.openrewrite.internal.StringUtils; import java.nio.file.Path; @@ -29,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.test.RewriteTest.toRecipe; -class ResultTest { +class InMemoryDiffEntryTest { private final Path filePath = Paths.get("com/netflix/MyJavaClass.java"); private String ab(String which) { @@ -38,7 +39,7 @@ private String ab(String which) { @Test void idempotent() { - try (var diff = new Result.InMemoryDiffEntry( + try (var diff = new InMemoryDiffEntry( Paths.get("com/netflix/MyJavaClass.java"), Paths.get("com/netflix/MyJavaClass.java"), null, @@ -52,7 +53,7 @@ void idempotent() { @Test void ignoreWhitespace() { - try (var diff = new Result.InMemoryDiffEntry( + try (var diff = new InMemoryDiffEntry( Paths.get("com/netflix/MyJavaClass.java"), Paths.get("com/netflix/MyJavaClass.java"), null, @@ -74,7 +75,7 @@ void ignoreWhitespace() { @Test void singleLineChange() { - try (var result = new Result.InMemoryDiffEntry( + try (var result = new InMemoryDiffEntry( filePath, filePath, null, """ public void test() { @@ -105,7 +106,7 @@ public void test() { @Test void multipleChangesMoreThanThreeLinesApart() { - try (var result = new Result.InMemoryDiffEntry( + try (var result = new InMemoryDiffEntry( filePath, filePath, null, """ public void test() { @@ -167,7 +168,7 @@ public void test() { @Test void addFile() { - try (var result = new Result.InMemoryDiffEntry( + try (var result = new InMemoryDiffEntry( null, filePath, null, "", """ @@ -194,7 +195,7 @@ public void test() { @Test void deleteFile() { - try (var result = new Result.InMemoryDiffEntry( + try (var result = new InMemoryDiffEntry( filePath, null, null, """ public void test() { @@ -221,7 +222,7 @@ public void test() { @Disabled("Does not work with CI due to jgit shadowJar") @Test void executableFile() { - try (var result = new Result.InMemoryDiffEntry( + try (var result = new InMemoryDiffEntry( filePath, null, null, """ public void test() { From 49c1f6866e147fde45b3d8bf99f452715ea79f8d Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 7 Aug 2023 17:01:21 -0400 Subject: [PATCH 119/447] Polish --- .../src/main/java/org/openrewrite/table/LstProvenanceTable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java index a1991b7c954..365cbc87c5f 100644 --- a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java +++ b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java @@ -27,7 +27,7 @@ public class LstProvenanceTable extends DataTable<LstProvenanceTable.Row> { public LstProvenanceTable(Recipe recipe) { super(recipe, - "LST Provenance", + "LST provenance", "Table showing which tools were used to produce LSTs."); } From 413baf2f951c7faa692fe95a6bd8e4c9ed53faaa Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 7 Aug 2023 16:16:41 -0700 Subject: [PATCH 120/447] Add timestamp fields to LstProvenance data table --- .../src/main/java/org/openrewrite/FindLstProvenance.java | 5 ++++- .../main/java/org/openrewrite/marker/LstProvenance.java | 2 ++ .../java/org/openrewrite/table/LstProvenanceTable.java | 8 ++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java b/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java index 25868b6ee1d..3b3577976ab 100644 --- a/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java +++ b/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java @@ -23,6 +23,8 @@ import java.util.HashSet; import java.util.Set; +import static java.time.ZoneOffset.UTC; + @Value @EqualsAndHashCode(callSuper = true) public class FindLstProvenance extends ScanningRecipe<FindLstProvenance.Accumulator> { @@ -60,7 +62,8 @@ public Tree preVisit(Tree tree, ExecutionContext ctx) { } if(acc.seenProvenance.add(lstProvenance)) { provenanceTable.insertRow(ctx, new LstProvenanceTable.Row(lstProvenance.getBuildToolType(), - lstProvenance.getBuildToolVersion(), lstProvenance.getLstSerializerVersion())); + lstProvenance.getBuildToolVersion(), lstProvenance.getLstSerializerVersion(), + lstProvenance.getTimestampUtc().toEpochMilli(), lstProvenance.getTimestampUtc().atZone(UTC).toString())); } return tree; } diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java b/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java index 8108559761b..b6d3b2cb9d7 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java @@ -19,6 +19,7 @@ import lombok.Value; import lombok.With; +import java.time.Instant; import java.util.UUID; @Value @@ -30,6 +31,7 @@ public class LstProvenance implements Marker { Type buildToolType; String buildToolVersion; String lstSerializerVersion; + Instant timestampUtc; public enum Type { Gradle, diff --git a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java index 365cbc87c5f..aa11a5d261a 100644 --- a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java +++ b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java @@ -44,5 +44,13 @@ public static class Row { @Column(displayName = "LST serializer version", description = "The version of LST serializer which produced the LST.") String lstSerializerVersion; + + @Column(displayName = "Timestamp (epoch millis)", + description = "UTC timestamp describing when the LST was produced, in milliseconds since the unix epoch.") + long timestampEpochMillis; + + @Column(displayName = "Timestamp", + description = "UTC timestamp describing when the LST was produced, in ISO-8601 format. e.g.: \"2023‐08‐07T22:24:06+00:00 UTC+00:00\"") + String timestampUtc; } } From 22bf86f4709751db3542e9f3da4fec61d08355ef Mon Sep 17 00:00:00 2001 From: cjobinabo <48495887+cjobinabo@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:52:41 -0500 Subject: [PATCH 121/447] Added ChangeNamespaceValue recipe and tests (#3465) * Added ChangeNamespaceValue recipe and tests * Add license header * Apply formatter * Merge with Adriano's change and pr suggestions * updated redundant code * license update * set required to false for nullable fields * refactor * make searchAllNamespaces optional and extract XPathMatcher * Update rewrite-xml/src/main/java/org/openrewrite/xml/ChangeNamespaceValue.java Co-authored-by: Tim te Beek <timtebeek@gmail.com> --------- Co-authored-by: Tim te Beek <tim@moderne.io> Co-authored-by: joanvr <joan@moderne.io> Co-authored-by: Tim te Beek <timtebeek@gmail.com> --- .../openrewrite/xml/ChangeNamespaceValue.java | 160 +++++++++++++ .../xml/ChangeNamespaceValueTest.java | 212 ++++++++++++++++++ 2 files changed, 372 insertions(+) create mode 100644 rewrite-xml/src/main/java/org/openrewrite/xml/ChangeNamespaceValue.java create mode 100644 rewrite-xml/src/test/java/org/openrewrite/xml/ChangeNamespaceValueTest.java diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeNamespaceValue.java b/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeNamespaceValue.java new file mode 100644 index 00000000000..f19cd57629d --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeNamespaceValue.java @@ -0,0 +1,160 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.xml; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.NonNull; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.semver.Semver; +import org.openrewrite.xml.tree.Xml; + +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = true) +public class ChangeNamespaceValue extends Recipe { + + private static final String XMLNS_PREFIX = "xmlns"; + private static final String VERSION_PREFIX = "version"; + + @Override + public String getDisplayName() { + return "Change XML Attribute of a specific resource version"; + } + + @Override + public String getDescription() { + return "Alters XML Attribute value within specified element of a specific resource versions."; + } + + @Nullable + @Option(displayName = "Element name", + description = "The name of the element whose attribute's value is to be changed. Interpreted as an XPath Expression.", + example = "property", + required = false) + String elementName; + + @Nullable + @Option(displayName = "Old value", + description = "Only change the property value if it matches the configured `oldValue`.", + example = "newfoo.bar.attribute.value.string", + required = false) + String oldValue; + + @Option(displayName = "New value", + description = "The new value to be used for the namespace.", + example = "newfoo.bar.attribute.value.string") + String newValue; + + @Nullable + @Option(displayName = "Resource version", + description = "The version of resource to change", + example = "1.1", + required = false) + String versionMatcher; + + @Nullable + @Option(displayName = "Search All Namespaces", + description = "Specify whether evaluate all namespaces. Defaults to true", + example = "true", + required = false) + Boolean searchAllNamespaces; + + + @JsonCreator + public ChangeNamespaceValue(@Nullable @JsonProperty("elementName") String elementName, @Nullable @JsonProperty("oldValue") String oldValue, + @NonNull @JsonProperty("newValue") String newValue, @Nullable @JsonProperty("versionMatcher") String versionMatcher, + @Nullable @JsonProperty("searchAllNamespaces") Boolean searchAllNamespaces) { + this.elementName = elementName; + this.oldValue = oldValue; + this.newValue = newValue; + this.versionMatcher = versionMatcher; + this.searchAllNamespaces = searchAllNamespaces; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + + return new XmlIsoVisitor<ExecutionContext>() { + + @Nullable + private final XPathMatcher elementNameMatcher = elementName != null ? new XPathMatcher(elementName) : null; + + @Override + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + + if (matchesElementName(getCursor()) && matchesVersion(t)) { + t = t.withAttributes(ListUtils.map(t.getAttributes(), this::maybeReplaceNamespaceAttribute)); + } + + return t; + } + + private boolean matchesElementName(Cursor cursor) { + return elementNameMatcher == null || elementNameMatcher.matches(cursor); + } + + private boolean matchesVersion(Xml.Tag tag) { + if (versionMatcher == null) { + return true; + } + for (Xml.Attribute attribute : tag.getAttributes()) { + if (isVersionAttribute(attribute) && isVersionMatch(attribute)) { + return true; + } + } + return false; + } + + private Xml.Attribute maybeReplaceNamespaceAttribute(Xml.Attribute attribute) { + if (isXmlnsAttribute(attribute) && isOldValue(attribute)) { + return attribute.withValue( + new Xml.Attribute.Value(attribute.getId(), + "", + attribute.getMarkers(), + attribute.getValue().getQuote(), + newValue)); + } + return attribute; + } + + private boolean isXmlnsAttribute(Xml.Attribute attribute) { + boolean searchAll = searchAllNamespaces == null || Boolean.TRUE.equals(searchAllNamespaces); + return searchAll && attribute.getKeyAsString().startsWith(XMLNS_PREFIX) || + !searchAll && attribute.getKeyAsString().equals(XMLNS_PREFIX); + } + + private boolean isVersionAttribute(Xml.Attribute attribute) { + return attribute.getKeyAsString().startsWith(VERSION_PREFIX); + } + + private boolean isOldValue(Xml.Attribute attribute) { + return oldValue == null || attribute.getValueAsString().equals(oldValue); + } + + private boolean isVersionMatch(Xml.Attribute attribute) { + return versionMatcher == null || Semver.validate(attribute.getValueAsString(), versionMatcher).isValid(); + } + }; + } + +} diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeNamespaceValueTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeNamespaceValueTest.java new file mode 100644 index 00000000000..a4c545acbfa --- /dev/null +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeNamespaceValueTest.java @@ -0,0 +1,212 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.xml; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.xml.Assertions.xml; + +public class ChangeNamespaceValueTest implements RewriteTest { + + @Test + void replaceVersion24Test() { + rewriteRun( + spec -> spec.recipe(new ChangeNamespaceValue("web-app", null, "http://java.sun.com/xml/ns/j2ee", "2.4", false)), + xml( + """ + <web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.4" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """, + """ + <web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """ + ) + ); + } + + @Test + void replaceVersion25Test() { + rewriteRun( + spec -> spec.recipe(new ChangeNamespaceValue("web-app", null, "http://java.sun.com/xml/ns/java", "2.5,3.0", false)), + xml( + """ + <web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.5" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """, + """ + <web-app xmlns="http://java.sun.com/xml/ns/java" version="2.5" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """ + ) + ); + } + + @Test + void replaceVersion30Test() { + rewriteRun( + spec -> spec.recipe(new ChangeNamespaceValue("web-app", null, "http://java.sun.com/xml/ns/java", "2.5,3.0", false)), + xml( + """ + <web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="3.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """, + """ + <web-app xmlns="http://java.sun.com/xml/ns/java" version="3.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """ + ) + ); + } + + @Test + void replaceVersion31Test() { + rewriteRun( + spec -> spec.recipe(new ChangeNamespaceValue("web-app", null, "http://xmlns.jcp.org/xml/ns/javaee", "3.1+", false)), + xml( + """ + <web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="3.1" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_1.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """, + """ + <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="3.1" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_1.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """ + ) + ); + } + + @Test + void replaceVersion32Test() { + rewriteRun( + spec -> spec.recipe(new ChangeNamespaceValue("web-app", null, "http://xmlns.jcp.org/xml/ns/javaee", "3.1+", false)), + xml( + """ + <web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="3.2" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_2.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """, + """ + <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="3.2" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_2.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """ + ) + ); + } + + @Test + void namespaceWithPrefixMatched() { + rewriteRun( + spec -> spec.recipe(new ChangeNamespaceValue(null, "http://old.namespace", "https://new.namespace", null, true)), + xml( + """ + <ns0:parent + xmlns:ns0="http://old.namespace" + xmlns:xs="http://www.w3.org/2000/10/XMLSchema-instance"> + <ns0:child>value</ns0:child> + </ns0:parent> + """, + """ + <ns0:parent + xmlns:ns0="https://new.namespace" + xmlns:xs="http://www.w3.org/2000/10/XMLSchema-instance"> + <ns0:child>value</ns0:child> + </ns0:parent> + """ + ) + ); + } + + @Test + void namespaceWithoutPrefixMatched() { + rewriteRun( + spec -> spec.recipe(new ChangeNamespaceValue(null, "http://old.namespace", "https://new.namespace", null, true)), + xml( + """ + <parent + xmlns="http://old.namespace" + xmlns:xs="http://www.w3.org/2000/10/XMLSchema-instance"> + <child>value</child> + </parent> + """, + """ + <parent + xmlns="https://new.namespace" + xmlns:xs="http://www.w3.org/2000/10/XMLSchema-instance"> + <child>value</child> + </parent> + """ + ) + ); + } + + @Test + void namespaceNotMatched() { + rewriteRun( + spec -> spec.recipe(new ChangeNamespaceValue(null, "http://non.existant.namespace", "https://new.namespace", null, true)), + xml( + """ + <ns0:parent + xmlns:ns0="http://old.namespace" + xmlns:xs="http://www.w3.org/2000/10/XMLSchema-instance"> + <ns0:child>value</ns0:child> + </ns0:parent> + """ + ) + ); + } +} + From 5834be934a476be8622afcdec8c45a1f0255ba2c Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 9 Aug 2023 08:52:24 +0200 Subject: [PATCH 122/447] Use `maybeAddImport()` in `UseStaticImport` The class had an inlined copy of this method. --- .../src/main/java/org/openrewrite/java/UseStaticImport.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java b/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java index 38c9c28eac3..70136b9c483 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java @@ -63,14 +63,10 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu JavaType.FullyQualified receiverType = m.getMethodType().getDeclaringType(); maybeRemoveImport(receiverType); - AddImport<ExecutionContext> addStatic = new AddImport<>( + maybeAddImport( receiverType.getFullyQualifiedName(), m.getSimpleName(), false); - - if (!getAfterVisit().contains(addStatic)) { - doAfterVisit(addStatic); - } } if (m.getSelect() != null) { From 528b04fb1360db373d6fe3c1609424b30d17423c Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 9 Aug 2023 09:57:08 +0200 Subject: [PATCH 123/447] `AutodetectGeneralFormatStyle` now also processes comments Both multi-line plain comments and Javadoc comments can also contain line breaks. --- .../org/openrewrite/java/AddImportTest.java | 105 ++++++++++++++++++ .../java/org/openrewrite/java/AddImport.java | 2 +- .../format/AutodetectGeneralFormatStyle.java | 31 +++++- 3 files changed, 133 insertions(+), 5 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java index 80da85d2dd1..6fadf67bc2d 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java @@ -1228,6 +1228,7 @@ void crlfNewLinesWithPreviousImportsNoClass() { ) ); } + @Test void crlfNewLinesWithPreviousImportsNoPackageNoClass() { rewriteRun( @@ -1245,4 +1246,108 @@ void crlfNewLinesWithPreviousImportsNoPackageNoClass() { ) ); } + + @Test + void crlfNewLinesInComments() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new AddImport<>("java.util.List", null, false))), + java( + """ + /* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + */ + """.replace("\n", "\r\n") + + """ + import java.util.Arrays; + import java.util.Set; + """, + """ + /* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + */ + import java.util.Arrays; + import java.util.List; + import java.util.Set; + """.replace("\n", "\r\n") + ) + ); + } + + @Test + void crlfNewLinesInJavadoc() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new AddImport<>("java.util.List", null, false))), + java( + """ + import java.util.Arrays; + import java.util.Set; + + """ + + """ + /** + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + */ + """.replace("\n", "\r\n") + + "class Foo {}", + """ + import java.util.Arrays; + import java.util.List; + import java.util.Set; + + /** + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + */ + class Foo {} + """.replace("\n", "\r\n") + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java b/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java index e2121790120..32aac49b34c 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java @@ -149,7 +149,7 @@ public AddImport(String type, @Nullable String statik, boolean onlyIfReferenced) List<JRightPadded<J.Import>> newImports = layoutStyle.addImport(cu.getPadding().getImports(), importToAdd, cu.getPackageDeclaration(), classpath); - // ImportLayoutStile::addImport adds always `\n` as newlines. Checking if we need to fix them + // ImportLayoutStyle::addImport adds always `\n` as newlines. Checking if we need to fix them newImports = checkCRLF(cu, newImports); cu = cu.getPadding().withImports(newImports); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/AutodetectGeneralFormatStyle.java b/rewrite-java/src/main/java/org/openrewrite/java/format/AutodetectGeneralFormatStyle.java index 76e429a27f1..f4d4b053e30 100755 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/AutodetectGeneralFormatStyle.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/AutodetectGeneralFormatStyle.java @@ -16,12 +16,13 @@ package org.openrewrite.java.format; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.tree.JavaSourceFile; -import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.*; import org.openrewrite.style.GeneralFormatStyle; public class AutodetectGeneralFormatStyle extends JavaIsoVisitor<LineEndingsCount> { + AutodetectJavadocVisitor javadocVisitor = new AutodetectJavadocVisitor(); + /** * Makes a best-effort attempt to determine whether windows-style (CRLF) line endings or unix-style (LF) are * more common in the supplied AST. @@ -38,7 +39,18 @@ public static GeneralFormatStyle autodetectGeneralFormatStyle(JavaSourceFile j) @Override public Space visitSpace(Space space, Space.Location loc, LineEndingsCount count) { - String s = space.getWhitespace(); + processString(space.getWhitespace(), count); + for (Comment comment : space.getComments()) { + if (comment instanceof TextComment) { + processString(((TextComment) comment).getText(), count); + } else if (comment instanceof Javadoc) { + javadocVisitor.visit((Javadoc) comment, count); + } + } + return space; + } + + private static void processString(String s, LineEndingsCount count) { for (int i = 0; i < s.length(); i++) { char current = s.charAt(i); char next = '\0'; @@ -52,7 +64,18 @@ public Space visitSpace(Space space, Space.Location loc, LineEndingsCount count) count.lf++; } } - return super.visitSpace(space, loc, count); + } + + private class AutodetectJavadocVisitor extends org.openrewrite.java.JavadocVisitor<LineEndingsCount> { + public AutodetectJavadocVisitor() { + super(AutodetectGeneralFormatStyle.this); + } + + @Override + public Javadoc visitLineBreak(Javadoc.LineBreak lineBreak, LineEndingsCount lineEndingsCount) { + processString(lineBreak.getMargin(), lineEndingsCount); + return lineBreak; + } } } From a8299641bf41b85ae65bd6aa98ad662e4025fa35 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Wed, 9 Aug 2023 10:08:20 -0400 Subject: [PATCH 124/447] Backwards compatibility on LstProvenance --- .../src/main/java/org/openrewrite/FindLstProvenance.java | 5 +++-- .../src/main/java/org/openrewrite/marker/LstProvenance.java | 6 ++++++ .../main/java/org/openrewrite/table/LstProvenanceTable.java | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java b/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java index 3b3577976ab..6176a053f7f 100644 --- a/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java +++ b/rewrite-core/src/main/java/org/openrewrite/FindLstProvenance.java @@ -60,10 +60,11 @@ public Tree preVisit(Tree tree, ExecutionContext ctx) { if (lstProvenance == null) { return tree; } - if(acc.seenProvenance.add(lstProvenance)) { + if (acc.seenProvenance.add(lstProvenance)) { provenanceTable.insertRow(ctx, new LstProvenanceTable.Row(lstProvenance.getBuildToolType(), lstProvenance.getBuildToolVersion(), lstProvenance.getLstSerializerVersion(), - lstProvenance.getTimestampUtc().toEpochMilli(), lstProvenance.getTimestampUtc().atZone(UTC).toString())); + lstProvenance.getTimestampUtc() == null ? null : lstProvenance.getTimestampUtc().toEpochMilli(), + lstProvenance.getTimestampUtc() == null ? null : lstProvenance.getTimestampUtc().atZone(UTC).toString())); } return tree; } diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java b/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java index b6d3b2cb9d7..05da361ec33 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java @@ -18,6 +18,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; import lombok.With; +import org.openrewrite.internal.lang.Nullable; import java.time.Instant; import java.util.UUID; @@ -31,6 +32,11 @@ public class LstProvenance implements Marker { Type buildToolType; String buildToolVersion; String lstSerializerVersion; + + /** + * Nullable for backwards compatibility with older LSTs. + */ + @Nullable Instant timestampUtc; public enum Type { diff --git a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java index aa11a5d261a..02b4652592e 100644 --- a/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java +++ b/rewrite-core/src/main/java/org/openrewrite/table/LstProvenanceTable.java @@ -20,6 +20,7 @@ import org.openrewrite.Column; import org.openrewrite.DataTable; import org.openrewrite.Recipe; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.LstProvenance; @JsonIgnoreType @@ -47,10 +48,12 @@ public static class Row { @Column(displayName = "Timestamp (epoch millis)", description = "UTC timestamp describing when the LST was produced, in milliseconds since the unix epoch.") - long timestampEpochMillis; + @Nullable + Long timestampEpochMillis; @Column(displayName = "Timestamp", description = "UTC timestamp describing when the LST was produced, in ISO-8601 format. e.g.: \"2023‐08‐07T22:24:06+00:00 UTC+00:00\"") + @Nullable String timestampUtc; } } From fce53c1996365069abfac01e9e49cf36cea772ec Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 9 Aug 2023 10:45:01 -0700 Subject: [PATCH 125/447] Polish AddRepository --- .../org/openrewrite/maven/AddRepository.java | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddRepository.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddRepository.java index 6a33b907c32..25819344362 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddRepository.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddRepository.java @@ -17,6 +17,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; +import org.intellij.lang.annotations.Language; import org.openrewrite.ExecutionContext; import org.openrewrite.Option; import org.openrewrite.Recipe; @@ -38,42 +39,42 @@ public class AddRepository extends Recipe { private static final XPathMatcher REPOS_MATCHER = new XPathMatcher("/project/repositories"); @Option(description = "Repository id") - private String id; + String id; @Option(description = "Repository URL") - private String url; + String url; @Option(required = false, description = "Repository name") @Nullable - private String repoName; + String repoName; @Option(required = false, description = "Repository layout") @Nullable - private String layout; + String layout; @Option(required = false, description = "Snapshots from the repository are available") @Nullable - private Boolean snapshotsEnabled; + Boolean snapshotsEnabled; @Option(required = false, description = "Snapshots checksum policy") @Nullable - private String snapshotsChecksumPolicy; + String snapshotsChecksumPolicy; @Option(required = false, description = "Snapshots update policy policy") @Nullable - private String snapshotsUpdatePolicy; + String snapshotsUpdatePolicy; @Option(required = false, description = "Releases from the repository are available") @Nullable - private Boolean releasesEnabled; + Boolean releasesEnabled; @Option(required = false, description = "Releases checksum policy") @Nullable - private String releasesChecksumPolicy; + String releasesChecksumPolicy; @Option(required = false, description = "Releases update policy") @Nullable - private String releasesUpdatePolicy; + String releasesUpdatePolicy; @Override public String getDisplayName() { @@ -114,28 +115,27 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { if (maybeRepo.isPresent()) { Xml.Tag repo = maybeRepo.get(); if (repoName != null && !Objects.equals(repoName, repo.getChildValue("name").orElse(null))) { - if (repoName == null) { - repositories = (Xml.Tag) new RemoveContentVisitor<>(repo.getChild("name").get(), true).visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); - } else { - repositories = (Xml.Tag) new ChangeTagValueVisitor<>(repo.getChild("name").get(), repoName).visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); - } + //noinspection OptionalGetWithoutIsPresent + repositories = (Xml.Tag) new ChangeTagValueVisitor<>(repo.getChild("name").get(), repoName) + .visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); } - if (layout != null && !Objects.equals(layout, repo.getChildValue("layout").orElse(null))) { - if (layout == null) { - repositories = (Xml.Tag) new RemoveContentVisitor<>(repo.getChild("layout").get(), true).visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); - } else { - repositories = (Xml.Tag) new ChangeTagValueVisitor<>(repo.getChild("layout").get(), repoName).visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); - } + if (repoName != null && layout != null && !Objects.equals(layout, repo.getChildValue("layout").orElse(null))) { + //noinspection OptionalGetWithoutIsPresent + repositories = (Xml.Tag) new ChangeTagValueVisitor<>(repo.getChild("layout").get(), repoName) + .visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); maybeUpdateModel(); } if (!isReleasesEqual(repo)) { Xml.Tag releases = repo.getChild("releases").orElse(null); if (releases == null) { - repositories = (Xml.Tag) new AddToTagVisitor<>(repo, Xml.Tag.build(assembleReleases())).visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); + repositories = (Xml.Tag) new AddToTagVisitor<>(repo, Xml.Tag.build(assembleReleases())) + .visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); } else { - repositories = (Xml.Tag) new RemoveContentVisitor<>(releases, true).visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); + repositories = (Xml.Tag) new RemoveContentVisitor<>(releases, true) + .visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); if (!isNoSnapshots()) { - repositories = (Xml.Tag) new AddToTagVisitor<>(repo, Xml.Tag.build(assembleReleases())).visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); + repositories = (Xml.Tag) new AddToTagVisitor<>(repo, Xml.Tag.build(assembleReleases())) + .visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); } } maybeUpdateModel(); @@ -153,17 +153,17 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { maybeUpdateModel(); } } else { - StringBuilder sb = new StringBuilder(); - sb.append("<repository>\n"); - sb.append(assembleTagWithValue("id", id)); - sb.append(assembleTagWithValue("url", url)); - sb.append(assembleTagWithValue("name", repoName)); - sb.append(assembleTagWithValue("layout", layout)); - sb.append(assembleReleases()); - sb.append(assembleSnapshots()); - sb.append("</repository>\n"); + @Language("xml") + String sb = "<repository>\n" + + assembleTagWithValue("id", id) + + assembleTagWithValue("url", url) + + assembleTagWithValue("name", repoName) + + assembleTagWithValue("layout", layout) + + assembleReleases() + + assembleSnapshots() + + "</repository>\n"; - Xml.Tag repoTag = Xml.Tag.build(sb.toString()); + Xml.Tag repoTag = Xml.Tag.build(sb); repositories = (Xml.Tag) new AddToTagVisitor<>(repositories, repoTag).visitNonNull(repositories, ctx, getCursor().getParentOrThrow()); maybeUpdateModel(); } @@ -173,7 +173,7 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { }; } - private String assembleTagWithValue(String tag, String value) { + private String assembleTagWithValue(String tag, @Nullable String value) { StringBuilder sb = new StringBuilder(); if (value != null) { sb.append("<"); From 087a788eb9a2af2297b83ad32b28d836ab3dc268 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 10 Aug 2023 09:37:31 +0200 Subject: [PATCH 126/447] Optimize `Space#format(String)` A common case is that the `String` to be formatted is empty or just a single space. --- .../java/org/openrewrite/java/tree/Space.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index 8c39aab2d09..b5fcc783d8a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -47,6 +47,7 @@ public class Space { * So use flyweights to avoid storing many instances of functionally identical spaces */ private static final Map<String, Space> flyweights = new WeakHashMap<>(); + static { flyweights.put(" ", SINGLE_SPACE); } @@ -133,6 +134,12 @@ public static Space firstPrefix(@Nullable List<? extends J> trees) { } public static Space format(String formatting) { + if (formatting.isEmpty()) { + return Space.EMPTY; + } else if (" ".equals(formatting)) { + return Space.SINGLE_SPACE; + } + StringBuilder prefix = new StringBuilder(); StringBuilder comment = new StringBuilder(); List<Comment> comments = new ArrayList<>(); @@ -142,8 +149,8 @@ public static Space format(String formatting) { char last = 0; - char[] charArray = formatting.toCharArray(); - for (char c : charArray) { + for (int i = 0; i < formatting.length(); i++) { + char c = formatting.charAt(i); switch (c) { case '/': if (inSingleLineComment) { @@ -196,7 +203,7 @@ public static Space format(String formatting) { last = c; } // If a file ends with a single-line comment there may be no terminating newline - if(!comment.toString().isEmpty()) { + if (!comment.toString().isEmpty()) { comments.add(new TextComment(false, comment.toString(), prefix.toString(), Markers.EMPTY)); } @@ -258,9 +265,8 @@ public String toString() { StringBuilder printedWs = new StringBuilder(); int lastNewline = 0; if (whitespace != null) { - char[] charArray = whitespace.toCharArray(); - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; + for (int i = 0; i < whitespace.length(); i++) { + char c = whitespace.charAt(i); if (c == '\n') { printedWs.append("\\n"); lastNewline = i + 1; @@ -276,8 +282,8 @@ public String toString() { } return "Space(" + - "comments=<" + (comments.size() == 1 ? "1 comment" : comments.size() + " comments") + - ">, whitespace='" + printedWs + "')"; + "comments=<" + (comments.size() == 1 ? "1 comment" : comments.size() + " comments") + + ">, whitespace='" + printedWs + "')"; } public enum Location { From 30704de3214ff8cc6fb3719e8fad30f1abc477db Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 10 Aug 2023 09:51:57 +0200 Subject: [PATCH 127/447] Optimize `TypeTree#build(String)` Slightly optimize to reduce number of object allocations. --- .../org/openrewrite/java/tree/TypeTree.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeTree.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeTree.java index 79ad8b9076e..70cb88fb827 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeTree.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeTree.java @@ -17,7 +17,6 @@ import org.openrewrite.marker.Markers; -import java.util.Collections; import java.util.Scanner; import static java.util.Collections.emptyList; @@ -28,11 +27,12 @@ * array, or parameterized type). */ public interface TypeTree extends NameTree { + static <T extends TypeTree & Expression> T build(String fullyQualifiedName) { - Scanner scanner = new Scanner(fullyQualifiedName.replace('$', '.')); - scanner.useDelimiter("\\."); + Scanner scanner = new Scanner(fullyQualifiedName); + scanner.useDelimiter("[.$]"); - String fullName = ""; + StringBuilder fullName = new StringBuilder(); Expression expr = null; String nextLeftPad = ""; for (int i = 0; scanner.hasNext(); i++) { @@ -40,7 +40,9 @@ static <T extends TypeTree & Expression> T build(String fullyQualifiedName) { StringBuilder partBuilder = null; StringBuilder whitespaceBeforeNext = new StringBuilder(); - for (char c : scanner.next().toCharArray()) { + String segment = scanner.next(); + for (int j = 0; j < segment.length(); j++) { + char c = segment.charAt(j); if (!Character.isWhitespace(c)) { if (partBuilder == null) { partBuilder = new StringBuilder(); @@ -59,10 +61,10 @@ static <T extends TypeTree & Expression> T build(String fullyQualifiedName) { String part = partBuilder.toString(); if (i == 0) { - fullName = part; + fullName.append(part); expr = new Identifier(randomId(), Space.format(whitespaceBefore.toString()), Markers.EMPTY, emptyList(), part, null, null); } else { - fullName += "." + part; + fullName.append('.').append(part); expr = new J.FieldAccess( randomId(), Space.EMPTY, @@ -75,14 +77,14 @@ static <T extends TypeTree & Expression> T build(String fullyQualifiedName) { Space.format(whitespaceBefore.toString()), Markers.EMPTY, emptyList(), - part.trim(), + part, null, null ), Markers.EMPTY ), - (Character.isUpperCase(part.charAt(0))) ? - JavaType.ShallowClass.build(fullName) : + Character.isUpperCase(part.charAt(0)) ? + JavaType.ShallowClass.build(fullName.toString()) : null ); } From b065a6e960bfbc9a081061e48bb6921dcdbe06f3 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 10 Aug 2023 14:21:27 +0200 Subject: [PATCH 128/447] Add `JavaSourceFile#service(Class)` and `ImportService` (#3466) Let `JavaSourceFile` act as a service registry, so that other JVM languages (like Kotlin or Groovy) can override some central "services" which require a different implementation than the standard Java implementation. An example is imports: Kotlin doesn't really have static imports, it has more implicitly imported types, and also the concept of aliases. With the indirection via an `ImportService` (which Kotlin can extend), the existing Java recipes can continue using `JavaVisitor#maybeAddImport()` and will end up using the Kotlin-specific implementation when visiting Kotlin sources. --- ...ortenFullyQualifiedTypeReferencesTest.java | 2 +- .../openrewrite/java/ExtractInterface.java | 5 +++- .../org/openrewrite/java/JavaVisitor.java | 8 +++-- .../java/recipes/MigrateRecipeToRewrite8.java | 2 +- .../java/service/ImportService.java | 29 +++++++++++++++++++ .../java/service/package-info.java | 21 ++++++++++++++ .../openrewrite/java/tree/JavaSourceFile.java | 11 +++++++ 7 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/service/package-info.java diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java index d55638b5189..cf8dd9684a7 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java @@ -228,7 +228,7 @@ void visitSubtreeOnly() { @SuppressWarnings("DataFlowIssue") public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { if (method.getSimpleName().equals("m1")) { - return (J.MethodDeclaration) new ShortenFullyQualifiedTypeReferences().getVisitor().visit(method, ctx); + return (J.MethodDeclaration) new ShortenFullyQualifiedTypeReferences().getVisitor().visit(method, ctx, getCursor().getParent()); } return super.visitMethodDeclaration(method, ctx); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ExtractInterface.java b/rewrite-java/src/main/java/org/openrewrite/java/ExtractInterface.java index 5f9f2bc54f8..c6b7c78a2ba 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ExtractInterface.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ExtractInterface.java @@ -17,6 +17,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; +import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.search.FindAnnotations; @@ -116,7 +117,9 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex JavaType.ShallowClass type = JavaType.ShallowClass.build(fullyQualifiedInterfaceName); J.Block body = classDecl.getBody(); - J.ClassDeclaration implementing = (J.ClassDeclaration) new ImplementInterface<>(classDecl, type).visitNonNull(classDecl, ctx); + Cursor parent = getCursor().getParent(); + assert parent != null; + J.ClassDeclaration implementing = (J.ClassDeclaration) new ImplementInterface<>(classDecl, type).visitNonNull(classDecl, ctx, parent); return (J.ClassDeclaration) new JavaIsoVisitor<ExecutionContext>() { @Override diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index c65a51e9ace..5c0e03fd12a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -22,6 +22,7 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.format.AutoFormatVisitor; +import org.openrewrite.java.service.ImportService; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; @@ -122,9 +123,10 @@ public void maybeAddImport(String fullyQualifiedName, boolean onlyIfReferenced) } public void maybeAddImport(String fullyQualifiedName, @Nullable String statik, boolean onlyIfReferenced) { - AddImport<P> op = new AddImport<>(fullyQualifiedName, statik, onlyIfReferenced); - if (!getAfterVisit().contains(op)) { - doAfterVisit(op); + ImportService service = getCursor().firstEnclosingOrThrow(JavaSourceFile.class).service(ImportService.class); + JavaVisitor<P> visitor = service.addImportVisitor(fullyQualifiedName, statik, onlyIfReferenced); + if (!getAfterVisit().contains(visitor)) { + doAfterVisit(visitor); } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/recipes/MigrateRecipeToRewrite8.java b/rewrite-java/src/main/java/org/openrewrite/java/recipes/MigrateRecipeToRewrite8.java index f640ff4afa2..d9f58602577 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/recipes/MigrateRecipeToRewrite8.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/recipes/MigrateRecipeToRewrite8.java @@ -345,7 +345,7 @@ public J.MemberReference visitMemberReference(J.MemberReference memberRef, } return memberRef; } - }.visitNonNull(visitMethod, ctx); + }.visitNonNull(visitMethod, ctx, getCursor().getParent()); maybeAddImport("org.openrewrite.internal.lang.Nullable"); maybeAddImport("org.openrewrite.Tree"); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java b/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java new file mode 100644 index 00000000000..d03b8ed23f8 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.service; + +import org.openrewrite.Incubating; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.AddImport; +import org.openrewrite.java.JavaVisitor; + +@Incubating(since = "8.1.16") +public class ImportService { + + public <P> JavaVisitor<P> addImportVisitor(String packageName, @Nullable String typeName, boolean onlyIfReferenced) { + return new AddImport<>(packageName, typeName, onlyIfReferenced); + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/service/package-info.java b/rewrite-java/src/main/java/org/openrewrite/java/service/package-info.java new file mode 100644 index 00000000000..8a95f702bc2 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/service/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + */ +@NonNullApi +@NonNullFields +package org.openrewrite.java.service; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java index e98c189414a..95bd23e2184 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java @@ -15,9 +15,11 @@ */ package org.openrewrite.java.tree; +import org.openrewrite.Incubating; import org.openrewrite.SourceFile; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.TypesInUse; +import org.openrewrite.java.service.ImportService; import java.nio.file.Path; import java.util.List; @@ -51,6 +53,15 @@ public interface JavaSourceFile extends J { SourceFile withSourcePath(Path path); + @Incubating(since = "8.2.0") + @SuppressWarnings("unchecked") + default <S> S service(Class<S> service) { + if (service == ImportService.class) { + return (S) new ImportService(); + } + throw new UnsupportedOperationException("Service " + service + " not supported"); + } + interface Padding { List<JRightPadded<Import>> getImports(); JavaSourceFile withImports(List<JRightPadded<Import>> imports); From 81582279dc825aa07a13c5609126dfbd2fcf6bd9 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Thu, 10 Aug 2023 10:53:02 -0400 Subject: [PATCH 129/447] Add more events to ParsingEventListener --- .../org/openrewrite/text/PlainTextParser.java | 13 +++++++------ .../openrewrite/tree/ParsingEventListener.java | 17 +++++++++++++++-- .../org/openrewrite/groovy/GroovyParser.java | 1 + .../java/org/openrewrite/hcl/HclParser.java | 1 + .../java/isolated/ReloadableJava11Parser.java | 3 ++- .../java/isolated/ReloadableJava17Parser.java | 2 ++ .../openrewrite/java/ReloadableJava8Parser.java | 3 ++- .../java/org/openrewrite/json/JsonParser.java | 1 + .../java/org/openrewrite/maven/MavenParser.java | 2 ++ .../properties/PropertiesParser.java | 1 + .../org/openrewrite/protobuf/ProtoParser.java | 1 + .../java/org/openrewrite/xml/XmlParser.java | 1 + .../java/org/openrewrite/yaml/YamlParser.java | 1 + 13 files changed, 37 insertions(+), 10 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java b/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java index cd02513b14a..f032278fbcd 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java @@ -62,10 +62,11 @@ public static PlainText convert(SourceFile sourceFile) { public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - return StreamSupport.stream(sources.spliterator(), false).map(source -> { - Path path = source.getRelativePath(relativeTo); + return StreamSupport.stream(sources.spliterator(), false).map(input -> { + Path path = input.getRelativePath(relativeTo); + parsingListener.startedParsing(input); try { - EncodingDetectingInputStream is = source.getSource(ctx); + EncodingDetectingInputStream is = input.getSource(ctx); String sourceStr = is.readFully(); PlainText plainText = new PlainText( randomId(), @@ -73,16 +74,16 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re Markers.EMPTY, is.getCharset().name(), is.isCharsetBomMarked(), - source.getFileAttributes(), + input.getFileAttributes(), null, sourceStr, null ); - parsingListener.parsed(source, plainText); + parsingListener.parsed(input, plainText); return plainText; } catch (Throwable t) { ctx.getOnError().accept(t); - return ParseError.build(this, source, relativeTo, ctx, t); + return ParseError.build(this, input, relativeTo, ctx, t); } }); } diff --git a/rewrite-core/src/main/java/org/openrewrite/tree/ParsingEventListener.java b/rewrite-core/src/main/java/org/openrewrite/tree/ParsingEventListener.java index 47c007e8fa7..f3c5bd9aaf2 100644 --- a/rewrite-core/src/main/java/org/openrewrite/tree/ParsingEventListener.java +++ b/rewrite-core/src/main/java/org/openrewrite/tree/ParsingEventListener.java @@ -19,7 +19,20 @@ import org.openrewrite.SourceFile; public interface ParsingEventListener { - ParsingEventListener NOOP = (input, sourceFile) -> {}; + ParsingEventListener NOOP = new ParsingEventListener() { + }; - void parsed(Parser.Input input, SourceFile sourceFile); + /** + * Parsers may call this one or more times before parsing begins on any one source file + * to indicate to listeners preparatory steps that are about to be taken. + * @param stateMessage The message to the listener. + */ + default void intermediateMessage(String stateMessage) { + } + + default void startedParsing(Parser.Input input) { + } + + default void parsed(Parser.Input input, SourceFile sourceFile) { + } } diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java index e1f6fce8734..67240b74719 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java @@ -127,6 +127,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re errorCollector ); + pctx.getParsingListener().startedParsing(input); CompilationUnit compUnit = new CompilationUnit(configuration, null, classLoader, classLoader); compUnit.addSource(unit); compUnit.compile(Phases.CANONICALIZATION); diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclParser.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclParser.java index c46f6fdf09e..35d3db65a20 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclParser.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/HclParser.java @@ -48,6 +48,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); return acceptedInputs(sourceFiles).map(input -> { try { + parsingListener.startedParsing(input); EncodingDetectingInputStream is = input.getSource(ctx); String sourceStr = is.readFully(); diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java index ecd9e5cca57..895c4737043 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java @@ -152,9 +152,10 @@ public static Builder builder() { public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); LinkedHashMap<Input, JCTree.JCCompilationUnit> cus = parseInputsToCompilerAst(sourceFiles, ctx); - + parsingListener.intermediateMessage("Compiling Java source files"); return cus.entrySet().stream().map(cuByPath -> { Input input = cuByPath.getKey(); + parsingListener.startedParsing(input); try { ReloadableJava11ParserVisitor parser = new ReloadableJava11ParserVisitor( input.getRelativePath(relativeTo), diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java index 9c0b885b1b0..33f8166c6bd 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java @@ -148,9 +148,11 @@ public static Builder builder() { @Override public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); + parsingListener.intermediateMessage("Compiling Java source files"); LinkedHashMap<Input, JCTree.JCCompilationUnit> cus = parseInputsToCompilerAst(sourceFiles, ctx); return cus.entrySet().stream().map(cuByPath -> { Input input = cuByPath.getKey(); + parsingListener.startedParsing(input); try { ReloadableJava17ParserVisitor parser = new ReloadableJava17ParserVisitor( input.getRelativePath(relativeTo), 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 47cb26056a2..a69d53547a8 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 @@ -138,7 +138,7 @@ public void close() { @Override public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - + parsingListener.intermediateMessage("Compiling Java source files"); if (classpath != null) { // override classpath if (context.get(JavaFileManager.class) != pfm) { throw new IllegalStateException("JavaFileManager has been forked unexpectedly"); @@ -180,6 +180,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat return cus.entrySet().stream().map(cuByPath -> { Input input = cuByPath.getKey(); + parsingListener.startedParsing(input); try { ReloadableJava8ParserVisitor parser = new ReloadableJava8ParserVisitor( input.getRelativePath(relativeTo), diff --git a/rewrite-json/src/main/java/org/openrewrite/json/JsonParser.java b/rewrite-json/src/main/java/org/openrewrite/json/JsonParser.java index c0573bafac3..5a420b4d31c 100755 --- a/rewrite-json/src/main/java/org/openrewrite/json/JsonParser.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/JsonParser.java @@ -37,6 +37,7 @@ public class JsonParser implements Parser { public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); return acceptedInputs(sourceFiles).map(input -> { + parsingListener.startedParsing(input); try (InputStream sourceStream = input.getSource(ctx)) { JSON5Parser parser = new JSON5Parser(new CommonTokenStream(new JSON5Lexer( CharStreams.fromStream(sourceStream)))); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java index 910c980721d..b96a7b8f123 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java @@ -26,6 +26,7 @@ import org.openrewrite.maven.tree.Pom; import org.openrewrite.maven.tree.ResolvedPom; import org.openrewrite.tree.ParseError; +import org.openrewrite.tree.ParsingExecutionContextView; import org.openrewrite.xml.XmlParser; import org.openrewrite.xml.tree.Xml; @@ -94,6 +95,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re } } + ParsingExecutionContextView.view(ctx).getParsingListener().intermediateMessage("Resolving Maven dependencies"); MavenPomDownloader downloader = new MavenPomDownloader(projectPomsByPath, ctx); MavenExecutionContextView mavenCtx = MavenExecutionContextView.view(ctx); diff --git a/rewrite-properties/src/main/java/org/openrewrite/properties/PropertiesParser.java b/rewrite-properties/src/main/java/org/openrewrite/properties/PropertiesParser.java index c8c7d5fae7e..d912dd72915 100755 --- a/rewrite-properties/src/main/java/org/openrewrite/properties/PropertiesParser.java +++ b/rewrite-properties/src/main/java/org/openrewrite/properties/PropertiesParser.java @@ -42,6 +42,7 @@ public Stream<SourceFile> parse(@Language("properties") String... sources) { public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); return acceptedInputs(sourceFiles).map(input -> { + parsingListener.startedParsing(input); Path path = input.getRelativePath(relativeTo); try (EncodingDetectingInputStream is = input.getSource(ctx)) { Properties.File file = parseFromInput(path, is) diff --git a/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java b/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java index 3fce1fd3fb2..f2a7aae4bb4 100644 --- a/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java +++ b/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java @@ -39,6 +39,7 @@ public class ProtoParser implements Parser { public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); return acceptedInputs(sourceFiles).map(input -> { + parsingListener.startedParsing(input); Path path = input.getRelativePath(relativeTo); try { EncodingDetectingInputStream is = input.getSource(ctx); diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java b/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java index 0cffadbe958..00c74420226 100755 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/XmlParser.java @@ -37,6 +37,7 @@ public class XmlParser implements Parser { public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); return acceptedInputs(sourceFiles).map(input -> { + parsingListener.startedParsing(input); Path path = input.getRelativePath(relativeTo); try (EncodingDetectingInputStream is = input.getSource(ctx)) { String sourceStr = is.readFully(); diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index 1339bd8f308..8051fbac92c 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -61,6 +61,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); return acceptedInputs(sourceFiles) .map(input -> { + parsingListener.startedParsing(input); Path path = input.getRelativePath(relativeTo); try (EncodingDetectingInputStream is = input.getSource(ctx)) { Yaml.Documents yaml = parseFromInput(path, is); From 483f4357b44a55c31dece0ee1216a777799ef8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Carpenter=E2=84=A2=EF=B8=8F?= <kevin@moderne.io> Date: Thu, 10 Aug 2023 12:45:05 -0300 Subject: [PATCH 130/447] Add data table to EffectiveMavenRepositories and its test (#3469) --- .../search/EffectiveMavenRepositories.java | 29 ++++++++++++- .../EffectiveMavenRepositoriesTable.java | 43 +++++++++++++++++++ .../EffectiveMavenRepositoriesTest.java | 43 ++++++++++++++++++- 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositoriesTable.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositories.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositories.java index f301390ff36..1ec47738844 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositories.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositories.java @@ -15,9 +15,13 @@ */ package org.openrewrite.maven.search; +import lombok.EqualsAndHashCode; +import lombok.Value; import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.SearchResult; import org.openrewrite.maven.MavenExecutionContextView; import org.openrewrite.maven.MavenIsoVisitor; @@ -27,6 +31,8 @@ import java.util.StringJoiner; +@Value +@EqualsAndHashCode(callSuper = false) public class EffectiveMavenRepositories extends Recipe { @Override @@ -41,6 +47,14 @@ public String getDescription() { "determined when the LST was produced."; } + @Option(displayName = "Use markers", + description = "Whether to add markers for each effective Maven repository to the POM. Default `false`.", + required = false) + @Nullable + Boolean useMarkers; + + transient EffectiveMavenRepositoriesTable table = new EffectiveMavenRepositoriesTable(this); + @Override public TreeVisitor<?, ExecutionContext> getVisitor() { return new MavenIsoVisitor<ExecutionContext>() { @@ -50,13 +64,26 @@ public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { StringJoiner repositories = new StringJoiner("\n"); for (MavenRepository repository : mrr.getPom().getRepositories()) { repositories.add(repository.getUri()); + table.insertRow(ctx, new EffectiveMavenRepositoriesTable.Row( + document.getSourcePath().toString(), + repository.getUri())); } for (MavenRepository repository : MavenExecutionContextView.view(ctx) .getRepositories(mrr.getMavenSettings(), mrr.getActiveProfiles())) { repositories.add(repository.getUri()); + table.insertRow(ctx, new EffectiveMavenRepositoriesTable.Row( + document.getSourcePath().toString(), + repository.getUri())); } repositories.add(MavenRepository.MAVEN_CENTRAL.getUri()); - return SearchResult.found(document, repositories.toString()); + table.insertRow(ctx, new EffectiveMavenRepositoriesTable.Row( + document.getSourcePath().toString(), + MavenRepository.MAVEN_CENTRAL.getUri())); + if (Boolean.TRUE.equals(useMarkers)) { + return SearchResult.found(document, repositories.toString()); + } + + return document; } }; } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositoriesTable.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositoriesTable.java new file mode 100644 index 00000000000..a54e23e1132 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositoriesTable.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.search; + +import com.fasterxml.jackson.annotation.JsonIgnoreType; +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +@JsonIgnoreType +public class EffectiveMavenRepositoriesTable extends DataTable<EffectiveMavenRepositoriesTable.Row> { + + public EffectiveMavenRepositoriesTable(Recipe recipe) { + super(recipe, + "Effective Maven repositories", + "Table showing which Maven repositories were used in dependency resolution for this POM."); + } + + @Value + public static class Row { + @Column(displayName = "POM path", + description = "The path to the POM file.") + String pomPath; + + @Column(displayName = "Repository URI", + description = "The URI of the Maven repository.") + String repositoryUri; + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/search/EffectiveMavenRepositoriesTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/search/EffectiveMavenRepositoriesTest.java index c4b07e6c4c1..76b6af08f6a 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/search/EffectiveMavenRepositoriesTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/search/EffectiveMavenRepositoriesTest.java @@ -56,7 +56,7 @@ public class EffectiveMavenRepositoriesTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new EffectiveMavenRepositories()); + spec.recipe(new EffectiveMavenRepositories(true)); } @DocumentExample @@ -168,4 +168,45 @@ void fromMavenSettingsOnAst() { ) ); } + + @Test + void producesDataTable() { + rewriteRun( + spec -> spec + .recipe(new EffectiveMavenRepositories(false)) + .executionContext(MavenExecutionContextView.view(new InMemoryExecutionContext()) + .setMavenSettings(SPRING_MILESTONES_SETTINGS, "repo")) + .dataTableAsCsv(EffectiveMavenRepositoriesTable.class.getName(), """ + pomPath,repositoryUri + pom.xml,"https://repo.spring.io/milestone" + pom.xml,"https://repo.maven.apache.org/maven2" + module/pom.xml,"https://repo.spring.io/milestone" + module/pom.xml,"https://repo.maven.apache.org/maven2" + """) + .recipeExecutionContext(new InMemoryExecutionContext()), + pomXml( + """ + <project> + <groupId>org.openrewrite.example</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <modules> + <module>module</module> + </modules> + </project> + """, + spec -> spec.path("pom.xml") + ), + pomXml( + """ + <project> + <groupId>org.openrewrite.example</groupId> + <artifactId>module</artifactId> + <version>1</version> + </project> + """, + spec -> spec.path("module/pom.xml") + ) + ); + } } From ff8f57ad83cd137129ff4ecc09e2745ccb68400a Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 10 Aug 2023 12:23:53 -0700 Subject: [PATCH 131/447] Fix title casing --- rewrite-core/src/main/java/org/openrewrite/text/Find.java | 4 ++-- .../src/main/java/org/openrewrite/text/FindAndReplace.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/Find.java b/rewrite-core/src/main/java/org/openrewrite/text/Find.java index e683a43b4d5..c2ff43118f2 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/Find.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/Find.java @@ -64,7 +64,7 @@ public String getDescription() { @Nullable Boolean caseSensitive; - @Option(displayName = "Regex Multiline Mode", + @Option(displayName = "Regex multiline mode", description = "When performing a regex search setting this to `true` allows \"^\" and \"$\" to match the beginning and end of lines, respectively. " + "When performing a regex search when this is `false` \"^\" and \"$\" will match only the beginning and ending of the entire source file, respectively." + "Has no effect when not performing a regex search. Default `false`.", @@ -72,7 +72,7 @@ public String getDescription() { @Nullable Boolean multiline; - @Option(displayName = "Regex Dot All", + @Option(displayName = "Regex dot all", description = "When performing a regex search setting this to `true` allows \".\" to match line terminators." + "Has no effect when not performing a regex search. Default `false`.", required = false) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java index 6f4ecdd9cd2..4e128afe330 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java @@ -59,7 +59,7 @@ public class FindAndReplace extends Recipe { @Nullable Boolean caseSensitive; - @Option(displayName = "Regex Multiline Mode", + @Option(displayName = "Regex multiline mode", description = "When performing a regex search setting this to `true` allows \"^\" and \"$\" to match the beginning and end of lines, respectively. " + "When performing a regex search when this is `false` \"^\" and \"$\" will match only the beginning and ending of the entire source file, respectively." + "Has no effect when not performing a regex search. Default `false`.", @@ -67,7 +67,7 @@ public class FindAndReplace extends Recipe { @Nullable Boolean multiline; - @Option(displayName = "Regex Dot All", + @Option(displayName = "Regex dot all", description = "When performing a regex search setting this to `true` allows \".\" to match line terminators." + "Has no effect when not performing a regex search. Default `false`.", required = false) From 4e323ed95326195047fb4082271434a91c4c65a5 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 10 Aug 2023 21:36:02 +0200 Subject: [PATCH 132/447] Add `maybeAddImport(String, String, String, boolean)` (#3472) This canonical form accepts separate strings for the package name, type name (qualified in case of nested classes), and member. This way there is no confusion about what constitutes the package name or an owning class. --- .../org/openrewrite/java/AddImportTest.java | 2 +- .../java/org/openrewrite/java/AddImport.java | 97 ++++++++++--------- .../java/org/openrewrite/java/ChangeType.java | 52 +++++----- .../org/openrewrite/java/JavaVisitor.java | 17 +++- .../java/service/ImportService.java | 6 +- .../org/openrewrite/java/tree/TypeUtils.java | 52 +++++----- 6 files changed, 124 insertions(+), 102 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java index 6fadf67bc2d..27dd0f13052 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java @@ -536,7 +536,7 @@ class A {} ); rewriteRun( - spec -> spec.recipe(toRecipe(() -> new AddImport<>(pkg + ".B", null, false))), + spec -> spec.recipe(toRecipe(() -> new AddImport<>(pkg, "B", null, false))), sources.toArray(new SourceSpecs[0]) ); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java b/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java index 32aac49b34c..e27df64a85a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java @@ -37,40 +37,55 @@ import java.util.concurrent.atomic.AtomicReference; import static org.openrewrite.Tree.randomId; -import static org.openrewrite.java.tree.TypeUtils.isOfClassType; import static org.openrewrite.java.format.AutodetectGeneralFormatStyle.autodetectGeneralFormatStyle; +import static org.openrewrite.java.tree.TypeUtils.isOfClassType; /** * A Java refactoring visitor that can be used to add an import (or static import) to a given compilation unit. * This visitor can also be configured to only add the import if the imported class/method are referenced within the * compilation unit. - * <P><P> - * The {@link AddImport#type} must be supplied and represents a fully qualified class name. - * <P><P> - * The {@link AddImport#statik} is an optional method within the imported type. The staticMethod can be set to "*" + * <p> + * The {@link AddImport#typeName} must be supplied and together with the optional {@link AddImport#packageName} + * represents the fully qualified class name. + * <p> + * The {@link AddImport#member} is an optional method within the imported type. The staticMethod can be set to "*" * to represent a static wildcard import. - * <P><P> + * <p> * The {@link AddImport#onlyIfReferenced} is a flag (defaulted to true) to indicate if the import should only be added * if there is a reference to the imported class/method. */ @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) public class AddImport<P> extends JavaIsoVisitor<P> { + + @Nullable + private final String packageName; + + private final String typeName; + @EqualsAndHashCode.Include - private final String type; + private final String fullyQualifiedName; @EqualsAndHashCode.Include @Nullable - private final String statik; + private final String member; @EqualsAndHashCode.Include private final boolean onlyIfReferenced; - private final JavaType.Class classType; + public AddImport(String type, @Nullable String member, boolean onlyIfReferenced) { + int lastDotIdx = type.lastIndexOf('.'); + this.packageName = lastDotIdx != -1 ? type.substring(0, lastDotIdx) : null; + this.typeName = lastDotIdx != -1 ? type.substring(lastDotIdx + 1) : type; + this.fullyQualifiedName = type; + this.member = member; + this.onlyIfReferenced = onlyIfReferenced; + } - public AddImport(String type, @Nullable String statik, boolean onlyIfReferenced) { - this.type = type; - this.classType = JavaType.ShallowClass.build(type); - this.statik = statik; + public AddImport(@Nullable String packageName, String typeName, @Nullable String member, boolean onlyIfReferenced) { + this.packageName = packageName; + this.typeName = typeName.replace('.', '$'); + this.fullyQualifiedName = packageName == null ? typeName : packageName + "." + typeName; + this.member = member; this.onlyIfReferenced = onlyIfReferenced; } @@ -80,36 +95,28 @@ public AddImport(String type, @Nullable String statik, boolean onlyIfReferenced) J j = tree; if (tree instanceof JavaSourceFile) { JavaSourceFile cu = (JavaSourceFile) tree; - if (JavaType.Primitive.fromKeyword(classType.getFullyQualifiedName()) != null) { + if (packageName == null || JavaType.Primitive.fromKeyword(fullyQualifiedName) != null) { return cu; } - int dotIndex = classType.getFullyQualifiedName().lastIndexOf('.'); - if (dotIndex >= 0) { - String packageName = classType.getFullyQualifiedName().substring(0, dotIndex); - // No need to add imports if the class to import is in java.lang, or if the classes are within the same package - if (("java.lang".equals(packageName) && StringUtils.isBlank(statik)) || (cu.getPackageDeclaration() != null && - packageName.equals(cu.getPackageDeclaration().getExpression().printTrimmed(getCursor())))) { - return cu; - } - } - - if (onlyIfReferenced && !hasReference(cu)) { + // No need to add imports if the class to import is in java.lang, or if the classes are within the same package + if (("java.lang".equals(packageName) && StringUtils.isBlank(member)) || (cu.getPackageDeclaration() != null && + packageName.equals(cu.getPackageDeclaration().getExpression().printTrimmed(getCursor())))) { return cu; } - if (classType.getPackageName().isEmpty()) { + if (onlyIfReferenced && !hasReference(cu)) { return cu; } if (cu.getImports().stream().anyMatch(i -> { String ending = i.getQualid().getSimpleName(); - if (statik == null) { - return !i.isStatic() && i.getPackageName().equals(classType.getPackageName()) && - (ending.equals(classType.getClassName()) || "*".equals(ending)); + if (member == null) { + return !i.isStatic() && i.getPackageName().equals(packageName) && + (ending.equals(typeName) || "*".equals(ending)); } - return i.isStatic() && i.getTypeName().equals(classType.getFullyQualifiedName()) && - (ending.equals(statik) || "*".equals(ending)); + return i.isStatic() && i.getTypeName().equals(fullyQualifiedName) && + (ending.equals(member) || "*".equals(ending)); })) { return cu; } @@ -117,10 +124,10 @@ public AddImport(String type, @Nullable String statik, boolean onlyIfReferenced) J.Import importToAdd = new J.Import(randomId(), Space.EMPTY, Markers.EMPTY, - new JLeftPadded<>(statik == null ? Space.EMPTY : Space.format(" "), - statik != null, Markers.EMPTY), - TypeTree.build(classType.getFullyQualifiedName() + - (statik == null ? "" : "." + statik)).withPrefix(Space.format(" ")), + new JLeftPadded<>(member == null ? Space.EMPTY : Space.SINGLE_SPACE, + member != null, Markers.EMPTY), + TypeTree.build(fullyQualifiedName + + (member == null ? "" : "." + member)).withPrefix(Space.SINGLE_SPACE), null); List<JRightPadded<J.Import>> imports = new ArrayList<>(cu.getPadding().getImports()); @@ -180,7 +187,7 @@ private List<JRightPadded<J.Import>> checkCRLF(JavaSourceFile cu, List<JRightPad private boolean isTypeReference(NameTree t) { boolean isTypRef = true; if (t instanceof J.FieldAccess) { - isTypRef = isOfClassType(((J.FieldAccess) t).getTarget().getType(), type); + isTypRef = isOfClassType(((J.FieldAccess) t).getTarget().getType(), fullyQualifiedName); } return isTypRef; } @@ -197,11 +204,11 @@ private boolean isTypeReference(NameTree t) { */ //Note that using anyMatch when a stream is empty ends up returning true, which is not the behavior needed here! private boolean hasReference(JavaSourceFile compilationUnit) { - if (statik == null) { + if (member == null) { //Non-static imports, we just look for field accesses. - for (NameTree t : FindTypes.find(compilationUnit, type)) { - if ((!(t instanceof J.FieldAccess) || !((J.FieldAccess) t).isFullyQualifiedClassReference(type)) && - isTypeReference(t)) { + for (NameTree t : FindTypes.find(compilationUnit, fullyQualifiedName)) { + if ((!(t instanceof J.FieldAccess) || !((J.FieldAccess) t).isFullyQualifiedClassReference(fullyQualifiedName)) && + isTypeReference(t)) { return true; } } @@ -209,11 +216,11 @@ private boolean hasReference(JavaSourceFile compilationUnit) { } // For static method imports, we are either looking for a specific method or a wildcard. - for (J invocation : FindMethods.find(compilationUnit, type + " *(..)")) { + for (J invocation : FindMethods.find(compilationUnit, fullyQualifiedName + " *(..)")) { if (invocation instanceof J.MethodInvocation) { J.MethodInvocation mi = (J.MethodInvocation) invocation; if (mi.getSelect() == null && - ("*".equals(statik) || mi.getName().getSimpleName().equals(statik))) { + ("*".equals(member) || mi.getName().getSimpleName().equals(member))) { return true; } } @@ -230,7 +237,7 @@ private class FindStaticFieldAccess extends JavaIsoVisitor<AtomicReference<Boole public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, AtomicReference<Boolean> found) { // If the type isn't used there's no need to proceed further for (JavaType.Variable varType : cu.getTypesInUse().getVariables()) { - if (varType.getName().equals(statik) && isOfClassType(varType.getType(), type)) { + if (varType.getName().equals(member) && isOfClassType(varType.getType(), fullyQualifiedName)) { return super.visitCompilationUnit(cu, found); } } @@ -240,8 +247,8 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, AtomicRefere @Override public J.Identifier visitIdentifier(J.Identifier identifier, AtomicReference<Boolean> found) { assert getCursor().getParent() != null; - if (identifier.getSimpleName().equals(statik) && isOfClassType(identifier.getType(), type) && - !(getCursor().getParent().firstEnclosingOrThrow(J.class) instanceof J.FieldAccess)) { + if (identifier.getSimpleName().equals(member) && isOfClassType(identifier.getType(), fullyQualifiedName) && + !(getCursor().getParent().firstEnclosingOrThrow(J.class) instanceof J.FieldAccess)) { found.set(true); } return identifier; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java index 06f9f8ffbf1..0dac8e3035b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java @@ -21,6 +21,7 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.service.ImportService; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.marker.SearchResult; @@ -115,8 +116,8 @@ public J visit(@Nullable Tree tree, ExecutionContext ctx) { } @Override - public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { - J.ClassDeclaration cd = (J.ClassDeclaration) super.visitClassDeclaration(classDecl, executionContext); + public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration cd = (J.ClassDeclaration) super.visitClassDeclaration(classDecl, ctx); if (cd.getType() != null) { topLevelClassnames.add(getTopLevelClassName(cd.getType()).getFullyQualifiedName()); } @@ -124,7 +125,7 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ex } @Override - public J visitImport(J.Import import_, ExecutionContext executionContext) { + public J visitImport(J.Import import_, ExecutionContext ctx) { // visitCompilationUnit() handles changing the imports. // If we call super.visitImport() then visitFieldAccess() will change the imports before AddImport/RemoveImport see them. // visitFieldAccess() doesn't have the import-specific formatting logic that AddImport/RemoveImport do. @@ -132,13 +133,13 @@ public J visitImport(J.Import import_, ExecutionContext executionContext) { } @Override - public @Nullable JavaType visitType(@Nullable JavaType javaType, ExecutionContext executionContext) { + public @Nullable JavaType visitType(@Nullable JavaType javaType, ExecutionContext ctx) { return updateType(javaType); } @Override - public @Nullable J postVisit(J tree, ExecutionContext executionContext) { - J j = super.postVisit(tree, executionContext); + public @Nullable J postVisit(J tree, ExecutionContext ctx) { + J j = super.postVisit(tree, ctx); if (j instanceof J.MethodDeclaration) { J.MethodDeclaration method = (J.MethodDeclaration) j; j = method.withMethodType(updateType(method.getMethodType())); @@ -162,26 +163,33 @@ public J visitImport(J.Import import_, ExecutionContext executionContext) { if (maybeType instanceof JavaType.FullyQualified) { JavaType.FullyQualified type = (JavaType.FullyQualified) maybeType; if (originalType.getFullyQualifiedName().equals(type.getFullyQualifiedName())) { - sf = (JavaSourceFile) new RemoveImport<ExecutionContext>(originalType.getFullyQualifiedName()).visit(sf, executionContext, getCursor()); + sf = (JavaSourceFile) new RemoveImport<ExecutionContext>(originalType.getFullyQualifiedName()).visit(sf, ctx, getCursor()); } else if (originalType.getOwningClass() != null && originalType.getOwningClass().getFullyQualifiedName().equals(type.getFullyQualifiedName())) { - sf = (JavaSourceFile) new RemoveImport<ExecutionContext>(originalType.getOwningClass().getFullyQualifiedName()).visit(sf, executionContext, getCursor()); + sf = (JavaSourceFile) new RemoveImport<ExecutionContext>(originalType.getOwningClass().getFullyQualifiedName()).visit(sf, ctx, getCursor()); } } } } JavaType.FullyQualified fullyQualifiedTarget = TypeUtils.asFullyQualified(targetType); - if (fullyQualifiedTarget != null && !(fullyQualifiedTarget.getOwningClass() != null && topLevelClassnames.contains(getTopLevelClassName(fullyQualifiedTarget).getFullyQualifiedName()))) { - if (fullyQualifiedTarget.getOwningClass() != null && !"java.lang".equals(fullyQualifiedTarget.getPackageName())) { - sf = (JavaSourceFile) new AddImport<>(fullyQualifiedTarget.getOwningClass().getFullyQualifiedName(), null, true).visit(sf, executionContext, getCursor()); - } - if (!"java.lang".equals(fullyQualifiedTarget.getPackageName())) { - sf = (JavaSourceFile) new AddImport<>(fullyQualifiedTarget.getFullyQualifiedName(), null, true).visit(sf, executionContext, getCursor()); + if (fullyQualifiedTarget != null) { + JavaType.FullyQualified owningClass = fullyQualifiedTarget.getOwningClass(); + if (!(owningClass != null && topLevelClassnames.contains(getTopLevelClassName(fullyQualifiedTarget).getFullyQualifiedName()))) { + if (owningClass != null && !"java.lang".equals(fullyQualifiedTarget.getPackageName())) { + assert sf != null; + sf = (JavaSourceFile) sf.service(ImportService.class).addImportVisitor(owningClass.getPackageName(), owningClass.getClassName(), null, true) + .visit(sf, ctx, getCursor()); + } + if (!"java.lang".equals(fullyQualifiedTarget.getPackageName())) { + assert sf != null; + sf = (JavaSourceFile) sf.service(ImportService.class).addImportVisitor(fullyQualifiedTarget.getPackageName(), fullyQualifiedTarget.getClassName(), null, true) + .visit(sf, ctx, getCursor()); + } } } if (sf != null) { - sf = sf.withImports(ListUtils.map(sf.getImports(), i -> visitAndCast(i, executionContext, super::visitImport))); + sf = sf.withImports(ListUtils.map(sf.getImports(), i -> visitAndCast(i, ctx, super::visitImport))); } j = sf; @@ -477,7 +485,7 @@ private boolean updatePath(JavaSourceFile sf, String oldPath, String newPath) { } @Override - public J.Package visitPackage(J.Package pkg, ExecutionContext executionContext) { + public J.Package visitPackage(J.Package pkg, ExecutionContext ctxext) { String original = pkg.getExpression().printTrimmed(getCursor()).replaceAll("\\s", ""); if (original.equals(originalType.getPackageName())) { JavaType.FullyQualified fq = TypeUtils.asFullyQualified(targetType); @@ -497,17 +505,17 @@ public J.Package visitPackage(J.Package pkg, ExecutionContext executionContext) } @Override - public J.Import visitImport(J.Import _import, ExecutionContext executionContext) { + public J.Import visitImport(J.Import _import, ExecutionContext ctx) { Boolean updatePrefix = getCursor().pollNearestMessage("UPDATE_PREFIX"); if (updatePrefix != null && updatePrefix) { _import = _import.withPrefix(Space.EMPTY); } - return super.visitImport(_import, executionContext); + return super.visitImport(_import, ctx); } @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) { - J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, executionContext); + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); Boolean updatePrefix = getCursor().pollNearestMessage("UPDATE_PREFIX"); if (updatePrefix != null && updatePrefix) { cd = cd.withPrefix(Space.EMPTY); @@ -522,12 +530,12 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex } @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) { + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { if (method.isConstructor() && originalConstructor.matches(method.getMethodType())) { method = method.withName(method.getName().withSimpleName(targetType.getClassName())); method = method.withMethodType(updateType(method.getMethodType())); } - return super.visitMethodDeclaration(method, executionContext); + return super.visitMethodDeclaration(method, ctx); } private String getNewClassName(JavaType.FullyQualified fq) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index 5c0e03fd12a..1e660b2e816 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -112,19 +112,26 @@ public void maybeAddImport(String fullyQualifiedName) { * is idempotent and calling this method multiple times with the same arguments will only add an import once. * * @param fullyQualifiedName Fully-qualified name of the class. - * @param statik The static method or field to be imported. A wildcard "*" may also be used to statically import all methods/fields. + * @param member The static method or field to be imported. A wildcard "*" may also be used to statically import all methods/fields. */ - public void maybeAddImport(String fullyQualifiedName, String statik) { - maybeAddImport(fullyQualifiedName, statik, true); + public void maybeAddImport(String fullyQualifiedName, String member) { + maybeAddImport(fullyQualifiedName, member, true); } public void maybeAddImport(String fullyQualifiedName, boolean onlyIfReferenced) { maybeAddImport(fullyQualifiedName, null, onlyIfReferenced); } - public void maybeAddImport(String fullyQualifiedName, @Nullable String statik, boolean onlyIfReferenced) { + public void maybeAddImport(String fullyQualifiedName, @Nullable String member, boolean onlyIfReferenced) { + int lastDotIdx = fullyQualifiedName.lastIndexOf('.'); + String packageName = lastDotIdx != -1 ? fullyQualifiedName.substring(0, lastDotIdx) : null; + String typeName = lastDotIdx != -1 ? fullyQualifiedName.substring(lastDotIdx + 1) : fullyQualifiedName; + maybeAddImport(packageName, typeName, member, onlyIfReferenced); + } + + public void maybeAddImport(@Nullable String packageName, String typeName, @Nullable String member, boolean onlyIfReferenced) { ImportService service = getCursor().firstEnclosingOrThrow(JavaSourceFile.class).service(ImportService.class); - JavaVisitor<P> visitor = service.addImportVisitor(fullyQualifiedName, statik, onlyIfReferenced); + JavaVisitor<P> visitor = service.addImportVisitor(packageName, typeName, member, onlyIfReferenced); if (!getAfterVisit().contains(visitor)) { doAfterVisit(visitor); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java b/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java index d03b8ed23f8..6b854638a1f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java @@ -20,10 +20,10 @@ import org.openrewrite.java.AddImport; import org.openrewrite.java.JavaVisitor; -@Incubating(since = "8.1.16") +@Incubating(since = "8.2.0") public class ImportService { - public <P> JavaVisitor<P> addImportVisitor(String packageName, @Nullable String typeName, boolean onlyIfReferenced) { - return new AddImport<>(packageName, typeName, onlyIfReferenced); + public <P> JavaVisitor<P> addImportVisitor(@Nullable String packageName, String typeName, @Nullable String member, boolean onlyIfReferenced) { + return new AddImport<>(packageName, typeName, member, onlyIfReferenced); } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 19eb6601cc4..51e733fc122 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -46,7 +46,7 @@ public static boolean fullyQualifiedNamesAreEqual(@Nullable String fqn1, @Nullab return fqn1 == null && fqn2 == null; } - public static Predicate<String> fullyQualifiedNamesAreEqualAsPredicate (@Nullable String fqn1) { + public static Predicate<String> fullyQualifiedNamesAreEqualAsPredicate(@Nullable String fqn1) { return (fqn2) -> fullyQualifiedNamesAreEqual(fqn1, fqn2); } @@ -130,7 +130,7 @@ public static boolean isOfType(@Nullable JavaType type1, @Nullable JavaType type */ public static boolean isOfClassType(@Nullable JavaType type, String fqn) { if (type instanceof JavaType.FullyQualified) { - return TypeUtils.fullyQualifiedNamesAreEqual(((JavaType.FullyQualified) type).getFullyQualifiedName(), (fqn)); + return TypeUtils.fullyQualifiedNamesAreEqual(((JavaType.FullyQualified) type).getFullyQualifiedName(), fqn); } else if (type instanceof JavaType.Variable) { return isOfClassType(((JavaType.Variable) type).getType(), fqn); } else if (type instanceof JavaType.Method) { @@ -144,7 +144,7 @@ public static boolean isOfClassType(@Nullable JavaType type, String fqn) { } /** - * @param type The declaring type of the method invocation or constructor. + * @param type The declaring type of the method invocation or constructor. * @param matchOverride Whether to match the {@code Object} type. * @return True if the declaring type matches the criteria of this matcher. */ @@ -154,29 +154,29 @@ public static boolean isOfTypeWithName( boolean matchOverride, Predicate<String> matcher ) { - if (type == null || type instanceof JavaType.Unknown) { - return false; - } - if (matcher.test(type.getFullyQualifiedName())) { - return true; - } - if (matchOverride) { - if (!"java.lang.Object".equals(type.getFullyQualifiedName()) && - isOfTypeWithName(TYPE_OBJECT, true, matcher)) { - return true; - } - - if (isOfTypeWithName(type.getSupertype(), true, matcher)) { - return true; - } - - for (JavaType.FullyQualified anInterface : type.getInterfaces()) { - if (isOfTypeWithName(anInterface, true, matcher)) { - return true; - } - } - } - return false; + if (type == null || type instanceof JavaType.Unknown) { + return false; + } + if (matcher.test(type.getFullyQualifiedName())) { + return true; + } + if (matchOverride) { + if (!"java.lang.Object".equals(type.getFullyQualifiedName()) && + isOfTypeWithName(TYPE_OBJECT, true, matcher)) { + return true; + } + + if (isOfTypeWithName(type.getSupertype(), true, matcher)) { + return true; + } + + for (JavaType.FullyQualified anInterface : type.getInterfaces()) { + if (isOfTypeWithName(anInterface, true, matcher)) { + return true; + } + } + } + return false; } public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType from) { From 98405ee81e5f15e412fafa5ee938d0f0d975336d Mon Sep 17 00:00:00 2001 From: cjobinabo <48495887+cjobinabo@users.noreply.github.com> Date: Thu, 10 Aug 2023 14:48:17 -0500 Subject: [PATCH 133/447] Fix version matching issue in ChangeNamespaceValue and added test (#3471) * Fix version matching issue in ChangeNamespaceValue and added test * Apply formatter --------- Co-authored-by: Tim te Beek <tim@moderne.io> --- .../openrewrite/xml/ChangeNamespaceValue.java | 25 +++++++++++++++---- .../xml/ChangeNamespaceValueTest.java | 19 +++++++++++++- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeNamespaceValue.java b/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeNamespaceValue.java index f19cd57629d..830c9f01489 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeNamespaceValue.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/ChangeNamespaceValue.java @@ -23,11 +23,8 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.NonNull; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.semver.Semver; import org.openrewrite.xml.tree.Xml; -import java.util.List; - @Value @EqualsAndHashCode(callSuper = true) public class ChangeNamespaceValue extends Recipe { @@ -140,7 +137,7 @@ private Xml.Attribute maybeReplaceNamespaceAttribute(Xml.Attribute attribute) { private boolean isXmlnsAttribute(Xml.Attribute attribute) { boolean searchAll = searchAllNamespaces == null || Boolean.TRUE.equals(searchAllNamespaces); return searchAll && attribute.getKeyAsString().startsWith(XMLNS_PREFIX) || - !searchAll && attribute.getKeyAsString().equals(XMLNS_PREFIX); + !searchAll && attribute.getKeyAsString().equals(XMLNS_PREFIX); } private boolean isVersionAttribute(Xml.Attribute attribute) { @@ -152,7 +149,25 @@ private boolean isOldValue(Xml.Attribute attribute) { } private boolean isVersionMatch(Xml.Attribute attribute) { - return versionMatcher == null || Semver.validate(attribute.getValueAsString(), versionMatcher).isValid(); + String[] versions = versionMatcher.split(","); + double dversion = Double.parseDouble(attribute.getValueAsString()); + for (String splitVersion : versions) { + boolean checkGreaterThan = false; + double dversionExpected; + if (splitVersion.endsWith("+")) { + splitVersion = splitVersion.substring(0, splitVersion.length() - 1); + checkGreaterThan = true; + } + try { + dversionExpected = Double.parseDouble(splitVersion); + } catch (NumberFormatException e) { + return false; + } + if (!checkGreaterThan && dversionExpected == dversion || checkGreaterThan && dversionExpected <= dversion) { + return true; + } + } + return false; } }; } diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeNamespaceValueTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeNamespaceValueTest.java index a4c545acbfa..803b379b701 100644 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeNamespaceValueTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/ChangeNamespaceValueTest.java @@ -20,7 +20,7 @@ import static org.openrewrite.xml.Assertions.xml; -public class ChangeNamespaceValueTest implements RewriteTest { +class ChangeNamespaceValueTest implements RewriteTest { @Test void replaceVersion24Test() { @@ -147,6 +147,23 @@ void replaceVersion32Test() { ); } + @Test + void invalidVersionTest() { + rewriteRun( + spec -> spec.recipe(new ChangeNamespaceValue("web-app", null, "http://java.sun.com/xml/ns/j2ee", "2.5", false)), + xml( + """ + <web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.4" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" + id="WebApp_ID"> + <display-name>testWebDDNamespace</display-name> + </web-app> + """ + ) + ); + } + @Test void namespaceWithPrefixMatched() { rewriteRun( From eed5cb3c48fe48649f230f453bbee1ce9d053c72 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 11 Aug 2023 11:38:42 +0200 Subject: [PATCH 134/447] Fix problem with `ImportService` in `ChangeType` --- .../org/openrewrite/java/ChangeTypeTest.java | 252 +++++++++--------- .../java/org/openrewrite/java/ChangeType.java | 9 +- 2 files changed, 128 insertions(+), 133 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java index 18817d5510f..f10b2320093 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java @@ -75,7 +75,7 @@ void allowJavaLangSubpackages() { java( """ import java.util.logging.LoggingMXBean; - + class Test { static void method() { LoggingMXBean loggingBean = null; @@ -84,7 +84,7 @@ static void method() { """, """ import java.lang.management.PlatformLoggingMXBean; - + class Test { static void method() { PlatformLoggingMXBean loggingBean = null; @@ -104,7 +104,7 @@ void unnecessaryImport() { java( """ import test.Outer; - + class Test { private Outer p = Outer.of(); private Outer p2 = test.Outer.of(); @@ -114,12 +114,12 @@ class Test { java( """ package test; - + public class Outer { public static Outer of() { return new Outer(); } - + public static class Inner { } } @@ -138,7 +138,7 @@ void changeInnerClassToOuterClass() { """ import java.util.Map; import java.util.Map.Entry; - + class Test { Entry p; Map.Entry p2; @@ -146,7 +146,7 @@ class Test { """, """ import java.util.List; - + class Test { List p; List p2; @@ -164,14 +164,14 @@ void changeStaticFieldAccess() { java( """ import java.io.File; - + class Test { String p = File.separator; } """, """ import my.pkg.List; - + class Test { String p = List.separator; } @@ -196,14 +196,14 @@ void replaceWithNestedType() { java( """ import java.io.File; - + class Test { File p; } """, """ import java.util.Map; - + class Test { Map.Entry p; } @@ -220,14 +220,14 @@ void replacePrivateNestedType() { java( """ package a; - + class A { private static class B1 {} } """, """ package a; - + class A { private static class B2 {} } @@ -244,7 +244,7 @@ void deeplyNestedInnerClass() { java( """ package a; - + class A { public static class B { public static class C { @@ -254,7 +254,7 @@ public static class C { """, """ package a; - + class A { public static class B { public static class C2 { @@ -272,12 +272,12 @@ void simpleName() { java( """ import a.A1; - + public class B extends A1 {} """, """ import a.A2; - + public class B extends A2 {} """ ), @@ -318,7 +318,7 @@ void array2() { java( """ package com.acme.product; - + public class Pojo { } """ @@ -326,12 +326,12 @@ public class Pojo { java( """ package com.acme.project.impl; - + import com.acme.product.Pojo; - + public class UsePojo2 { Pojo[] p; - + void run() { p[0] = null; } @@ -339,12 +339,12 @@ void run() { """, """ package com.acme.project.impl; - + import com.acme.product.v2.Pojo; - + public class UsePojo2 { Pojo[] p; - + void run() { p[0] = null; } @@ -363,14 +363,14 @@ void array() { java( """ import a.A1; - + public class B { A1[] a = new A1[0]; } """, """ import a.A2; - + public class B { A2[] a = new A2[0]; } @@ -387,14 +387,14 @@ void multiDimensionalArray() { java( """ import a.A1; - + public class A { A1[][] multiDimensionalArray; } """, """ import a.A2; - + public class A { A2[][] multiDimensionalArray; } @@ -421,12 +421,12 @@ void classDecl() { java( """ import a.A1; - + public class B extends A1 implements I1 {} """, """ import a.A2; - + public class B extends A2 implements I2 {} """ ) @@ -442,14 +442,14 @@ void method() { java( """ import a.A1; - + public class B { public A1 foo() throws A1 { return null; } } """, """ import a.A2; - + public class B { public A2 foo() throws A2 { return null; } } @@ -466,10 +466,10 @@ void methodInvocationTypeParametersAndWildcard() { java( """ import a.A1; - + public class B { public <T extends A1> T generic(T n, java.util.List<? super A1> in) { - + } public void test() { A1.stat(); @@ -479,10 +479,10 @@ public void test() { """, """ import a.A2; - + public class B { public <T extends A2> T generic(T n, java.util.List<? super A2> in) { - + } public void test() { A2.stat(); @@ -503,7 +503,7 @@ void multiCatch() { java( """ import a.A1; - + public class B { public void test() { try {} @@ -513,7 +513,7 @@ public void test() { """, """ import a.A2; - + public class B { public void test() { try {} @@ -533,14 +533,14 @@ void multiVariable() { java( """ import a.A1; - + public class B { A1 f1, f2; } """, """ import a.A2; - + public class B { A2 f1, f2; } @@ -557,14 +557,14 @@ void newClass() { java( """ import a.A1; - + public class B { A1 a = new A1(); } """, """ import a.A2; - + public class B { A2 a = new A2(); } @@ -584,7 +584,7 @@ void updateAssignments() { java( """ import a.A1; - + class B { void method(A1 param) { A1 a = param; @@ -593,7 +593,7 @@ void method(A1 param) { """, """ import a.A2; - + class B { void method(A2 param) { A2 a = param; @@ -612,14 +612,14 @@ void parameterizedType() { java( """ import a.A1; - + public class B { Map<A1, A1> m; } """, """ import a.A2; - + public class B { Map<A2, A2> m; } @@ -637,14 +637,14 @@ void typeCast() { java( """ import a.A1; - + public class B { A1 a = (A1) null; } """, """ import a.A2; - + public class B { A2 a = (A2) null; } @@ -661,14 +661,14 @@ void classReference() { java( """ import a.A1; - + public class A { Class<?> clazz = A1.class; } """, """ import a.A2; - + public class A { Class<?> clazz = A2.class; } @@ -685,7 +685,7 @@ void methodSelect() { java( """ import a.A1; - + public class B { A1 a = null; public void test() { a.foo(); } @@ -693,7 +693,7 @@ public class B { """, """ import a.A2; - + public class B { A2 a = null; public void test() { a.foo(); } @@ -712,7 +712,7 @@ void staticImport() { java( """ import static a.A1.stat; - + public class B { public void test() { stat(); @@ -721,7 +721,7 @@ public void test() { """, """ import static a.A2.stat; - + public class B { public void test() { stat(); @@ -738,7 +738,7 @@ void staticImports2() { java( """ package com.acme.product; - + public class RunnableFactory { public static String getString() { return "hello"; @@ -749,9 +749,9 @@ public static String getString() { java( """ package com.acme.project.impl; - + import static com.acme.product.RunnableFactory.getString; - + public class StaticImportWorker { public void work() { getString().toLowerCase(); @@ -760,9 +760,9 @@ public void work() { """, """ package com.acme.project.impl; - + import static com.acme.product.v2.RunnableFactory.getString; - + public class StaticImportWorker { public void work() { getString().toLowerCase(); @@ -780,7 +780,7 @@ void staticConstant() { java( """ package com.acme.product; - + public class RunnableFactory { public static final String CONSTANT = "hello"; } @@ -789,9 +789,9 @@ public class RunnableFactory { java( """ package com.acme.project.impl; - + import static com.acme.product.RunnableFactory.CONSTANT; - + public class StaticImportWorker { public void work() { System.out.println(CONSTANT + " fred."); @@ -800,9 +800,9 @@ public void work() { """, """ package com.acme.project.impl; - + import static com.acme.product.v2.RunnableFactory.CONSTANT; - + public class StaticImportWorker { public void work() { System.out.println(CONSTANT + " fred."); @@ -884,23 +884,23 @@ public class B {} java( """ package com.myorg; - + import java.util.ArrayList; import com.yourorg.a.A; import java.util.List; - + public class Foo { List<A> a = new ArrayList<>(); } """, """ package com.myorg; - + import com.myorg.b.B; - + import java.util.ArrayList; import java.util.List; - + public class Foo { List<B> a = new ArrayList<>(); } @@ -916,7 +916,7 @@ void changeTypeWithInnerClass() { java( """ package com.acme.product; - + public class OuterClass { public static class InnerClass { @@ -927,15 +927,15 @@ public static class InnerClass { java( """ package de; - - import com.acme.product.OuterClass.InnerClass; + import com.acme.product.OuterClass; - + import com.acme.product.OuterClass.InnerClass; + public class UseInnerClass { public String work() { return new InnerClass().toString(); } - + public String work2() { return new OuterClass().toString(); } @@ -943,15 +943,15 @@ public String work2() { """, """ package de; - - import com.acme.product.v2.OuterClass.InnerClass; + import com.acme.product.v2.OuterClass; - + import com.acme.product.v2.OuterClass.InnerClass; + public class UseInnerClass { public String work() { return new InnerClass().toString(); } - + public String work2() { return new OuterClass().toString(); } @@ -969,7 +969,7 @@ void uppercaseInPackage() { java( """ package com.acme.product.util.accessDecision; - + public enum AccessVote { ABSTAIN } @@ -978,9 +978,9 @@ public enum AccessVote { java( """ package de; - + import com.acme.product.util.accessDecision.AccessVote; - + public class ProjectVoter { public AccessVote vote() { return AccessVote.ABSTAIN; @@ -989,9 +989,9 @@ public AccessVote vote() { """, """ package de; - + import com.acme.product.v2.util.accessDecision.AccessVote; - + public class ProjectVoter { public AccessVote vote() { return AccessVote.ABSTAIN; @@ -1018,7 +1018,7 @@ public interface Procedure { java( """ import com.acme.product.Procedure; - + public abstract class Worker { void callWorker() { worker(() -> { @@ -1029,7 +1029,7 @@ void callWorker() { """, """ import com.acme.product.Procedure2; - + public abstract class Worker { void callWorker() { worker(() -> { @@ -1050,7 +1050,7 @@ void assignment() { java( """ package com.acme.product.util.accessDecision; - + public enum AccessVote { ABSTAIN, GRANT @@ -1060,9 +1060,9 @@ public enum AccessVote { java( """ package de; - + import com.acme.product.util.accessDecision.AccessVote; - + public class ProjectVoter { public AccessVote vote(Object input) { AccessVote fred; @@ -1073,9 +1073,9 @@ public AccessVote vote(Object input) { """, """ package de; - + import com.acme.product.v2.util.accessDecision.AccessVote; - + public class ProjectVoter { public AccessVote vote(Object input) { AccessVote fred; @@ -1096,7 +1096,7 @@ void ternary() { java( """ package com.acme.product.util.accessDecision; - + public enum AccessVote { ABSTAIN, GRANT @@ -1106,9 +1106,9 @@ public enum AccessVote { java( """ package de; - + import com.acme.product.util.accessDecision.AccessVote; - + public class ProjectVoter { public AccessVote vote(Object input) { return input == null ? AccessVote.GRANT : AccessVote.ABSTAIN; @@ -1117,9 +1117,9 @@ public AccessVote vote(Object input) { """, """ package de; - + import com.acme.product.v2.util.accessDecision.AccessVote; - + public class ProjectVoter { public AccessVote vote(Object input) { return input == null ? AccessVote.GRANT : AccessVote.ABSTAIN; @@ -1169,7 +1169,7 @@ void javadocs() { java( """ import java.util.List; - + /** * {@link List} here */ @@ -1179,7 +1179,7 @@ class Test { """, """ import java.util.Collection; - + /** * {@link Collection} here */ @@ -1199,7 +1199,7 @@ void onlyUpdateApplicableImport() { java( """ package com.acme.product.factory; - + public class V1Factory { public static String getItem() { return "V1Factory"; @@ -1210,7 +1210,7 @@ public static String getItem() { java( """ package com.acme.product.factory; - + public class V2Factory { public static String getItem() { return "V2Factory"; @@ -1221,16 +1221,16 @@ public static String getItem() { java( """ import com.acme.product.factory.V1Factory; - + import static com.acme.product.factory.V2Factory.getItem; - + public class UseFactories { static class MyV1Factory extends V1Factory { static String getMyItemInherited() { return getItem(); } } - + static String getMyItemStaticImport() { return getItem(); } @@ -1238,16 +1238,16 @@ static String getMyItemStaticImport() { """, """ import com.acme.product.factory.V1FactoryA; - + import static com.acme.product.factory.V2Factory.getItem; - + public class UseFactories { static class MyV1Factory extends V1FactoryA { static String getMyItemInherited() { return getItem(); } } - + static String getMyItemStaticImport() { return getItem(); } @@ -1330,15 +1330,15 @@ void updateImportPrefixWithEmptyPackage() { java( """ package a.b; - + import java.util.List; - + class Original { } """, """ import java.util.List; - + class Target { } """ @@ -1474,9 +1474,9 @@ public class A2 { java( """ package org.foo; - + import a.A1; - + public class Example { public A1 method(A1 a1) { return a1; @@ -1485,9 +1485,9 @@ public A1 method(A1 a1) { """, """ package org.foo; - + import a.A2; - + public class Example { public A2 method(A2 a1) { return a1; @@ -1499,7 +1499,7 @@ public A2 method(A2 a1) { """ import a.A1; import org.foo.Example; - + public class Test { A1 local = new Example().method(null); } @@ -1507,7 +1507,7 @@ public class Test { """ import a.A2; import org.foo.Example; - + public class Test { A2 local = new Example().method(null); } @@ -1535,14 +1535,14 @@ void updateVariableType() { java( """ import a.A1; - + public class Test { A1 a; } """, """ import a.A2; - + public class Test { A2 a; } @@ -1565,7 +1565,7 @@ void boundedGenericType() { java( """ import a.A1; - + public class Test { <T extends A1> T method(T t) { return t; @@ -1574,7 +1574,7 @@ <T extends A1> T method(T t) { """, """ import a.A2; - + public class Test { <T extends A2> T method(T t) { return t; @@ -1597,7 +1597,7 @@ void changeConstructor() { java( """ package a; - + public class A1 { public A1() { } @@ -1605,7 +1605,7 @@ public A1() { """, """ package a; - + public class A2 { public A2() { } @@ -1628,12 +1628,12 @@ void updateJavaTypeClassKindAnnotation() { java( """ package org.openrewrite; - + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - + @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Test1 {} @@ -1642,12 +1642,12 @@ void updateJavaTypeClassKindAnnotation() { java( """ package org.openrewrite; - + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - + @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Test2 {} @@ -1656,7 +1656,7 @@ void updateJavaTypeClassKindAnnotation() { java( """ import org.openrewrite.Test1; - + public class A { @Test1 void method() {} @@ -1664,7 +1664,7 @@ void method() {} """, """ import org.openrewrite.Test2; - + public class A { @Test2 void method() {} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java index 0dac8e3035b..09701306db2 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java @@ -21,7 +21,6 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.search.UsesType; -import org.openrewrite.java.service.ImportService; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.marker.SearchResult; @@ -176,14 +175,10 @@ public J visitImport(J.Import import_, ExecutionContext ctx) { JavaType.FullyQualified owningClass = fullyQualifiedTarget.getOwningClass(); if (!(owningClass != null && topLevelClassnames.contains(getTopLevelClassName(fullyQualifiedTarget).getFullyQualifiedName()))) { if (owningClass != null && !"java.lang".equals(fullyQualifiedTarget.getPackageName())) { - assert sf != null; - sf = (JavaSourceFile) sf.service(ImportService.class).addImportVisitor(owningClass.getPackageName(), owningClass.getClassName(), null, true) - .visit(sf, ctx, getCursor()); + maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, true); } if (!"java.lang".equals(fullyQualifiedTarget.getPackageName())) { - assert sf != null; - sf = (JavaSourceFile) sf.service(ImportService.class).addImportVisitor(fullyQualifiedTarget.getPackageName(), fullyQualifiedTarget.getClassName(), null, true) - .visit(sf, ctx, getCursor()); + maybeAddImport(fullyQualifiedTarget.getPackageName(), fullyQualifiedTarget.getClassName(), null, true); } } } From 1170bae5419cbfaf2bea0762da70cb4b0b4ff113 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 11 Aug 2023 14:09:00 +0200 Subject: [PATCH 135/447] Make sure `G.CompilationUnit#withClasses()` inserts after imports --- .../src/main/java/org/openrewrite/groovy/tree/G.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/tree/G.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/tree/G.java index ea217e15e6e..2c48ae4fe8a 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/tree/G.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/tree/G.java @@ -247,9 +247,16 @@ public G.CompilationUnit withClasses(List<JRightPadded<ClassDeclaration>> classe List<JRightPadded<Statement>> statements = t.statements.stream() .filter(s -> !(s.getElement() instanceof J.ClassDeclaration)) .collect(Collectors.toList()); + int insertionIdx = 0; + for (JRightPadded<Statement> statement : statements) { + if (!(statement.getElement() instanceof J.Import)) { + break; + } + insertionIdx++; + } //noinspection unchecked - statements.addAll(0, classes.stream() + statements.addAll(insertionIdx, classes.stream() .map(i -> (JRightPadded<Statement>) (Object) i) .collect(Collectors.toList())); From 06cbe9a86cc52925dc83c91a08ed1d95a1c41967 Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Fri, 11 Aug 2023 17:25:25 -0500 Subject: [PATCH 136/447] Actually use the group and artifact selectors to filter the dependency configuration changes --- .../gradle/ChangeDependencyConfiguration.java | 81 +++++++++- .../ChangeDependencyConfigurationTest.java | 143 ++++++------------ 2 files changed, 125 insertions(+), 99 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java index 2263d987c96..bc6d91ecce0 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyConfiguration.java @@ -18,14 +18,19 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.*; +import org.openrewrite.gradle.util.Dependency; +import org.openrewrite.gradle.util.DependencyStringNotationConverter; import org.openrewrite.groovy.GroovyVisitor; +import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.semver.DependencyMatcher; import java.time.Duration; +import java.util.List; @Value @EqualsAndHashCode(callSuper = true) @@ -85,11 +90,81 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext conte return m; } - if (!newConfiguration.equals(m.getSimpleName())) { - m = m.withName(m.getName().withSimpleName(newConfiguration)); + DependencyMatcher dependencyMatcher = new DependencyMatcher(groupId, artifactId, null); + List<Expression> args = m.getArguments(); + if (args.get(0) instanceof J.Literal) { + J.Literal arg = (J.Literal) args.get(0); + if (!(arg.getValue() instanceof String)) { + return m; + } + + Dependency dependency = DependencyStringNotationConverter.parse((String) arg.getValue()); + if (!dependencyMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { + return m; + } + } else if (args.get(0) instanceof G.GString) { + G.GString gString = (G.GString) args.get(0); + List<J> strings = gString.getStrings(); + if (strings.size() != 2 || !(strings.get(0) instanceof J.Literal) || !(strings.get(1) instanceof G.GString.Value)) { + return m; + } + J.Literal groupArtifact = (J.Literal) strings.get(0); + if (!(groupArtifact.getValue() instanceof String)) { + return m; + } + + Dependency dependency = DependencyStringNotationConverter.parse((String) groupArtifact.getValue()); + if (!dependencyMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { + return m; + } + } else if (args.get(0) instanceof G.MapEntry && args.size() >= 2) { + Expression groupValue = ((G.MapEntry) args.get(0)).getValue(); + Expression artifactValue = ((G.MapEntry) args.get(1)).getValue(); + if (!(groupValue instanceof J.Literal) || !(artifactValue instanceof J.Literal)) { + return m; + } + J.Literal groupLiteral = (J.Literal) groupValue; + J.Literal artifactLiteral = (J.Literal) artifactValue; + if (!(groupLiteral.getValue() instanceof String) || !(artifactLiteral.getValue() instanceof String)) { + return m; + } + + if (!dependencyMatcher.matches((String) groupLiteral.getValue(), (String) artifactLiteral.getValue())) { + return m; + } + } else if (args.get(0) instanceof J.MethodInvocation) { + J.MethodInvocation inner = (J.MethodInvocation) args.get(0); + if (!(inner.getSimpleName().equals("project") || inner.getSimpleName().equals("platform") || inner.getSimpleName().equals("enforcedPlatform"))) { + return m; + } + List<Expression> innerArgs = inner.getArguments(); + if (!(innerArgs.get(0) instanceof J.Literal)) { + return m; + } + J.Literal value = (J.Literal) innerArgs.get(0); + if (!(value.getValue() instanceof String)) { + return m; + } + + Dependency dependency; + if (inner.getSimpleName().equals("project")) { + dependency = new Dependency("", ((String) value.getValue()).substring(1), null, null, null); + } else { + dependency = DependencyStringNotationConverter.parse((String) value.getValue()); + } + + if (!dependencyMatcher.matches(dependency.getGroupId(), dependency.getArtifactId())) { + return m; + } + } else { + return m; + } + + if (newConfiguration.equals(m.getSimpleName())) { + return m; } - return m; + return m.withName(m.getName().withSimpleName(newConfiguration)); } }); } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyConfigurationTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyConfigurationTest.java index 830d43778f7..47fdc0c9e31 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyConfigurationTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeDependencyConfigurationTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite.gradle; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -27,7 +26,7 @@ class ChangeDependencyConfigurationTest implements RewriteTest { @DocumentExample @Test - void worksWithEmptyStringConfig() { + void changeConfiguration() { rewriteRun( spec -> spec.recipe(new ChangeDependencyConfiguration("org.openrewrite", "*", "implementation", "")), buildGradle( @@ -61,11 +60,10 @@ void worksWithEmptyStringConfig() { ); } - @ParameterizedTest - @CsvSource(value = {"org.openrewrite:rewrite-core", "*:*"}, delimiterString = ":") - void findDependency(String group, String artifact) { + @Test + void worksWithEmptyStringConfig() { rewriteRun( - spec -> spec.recipe(new ChangeDependencyConfiguration(group, artifact, "implementation", null)), + spec -> spec.recipe(new ChangeDependencyConfiguration("org.openrewrite", "*", "implementation", "")), buildGradle( """ plugins { @@ -77,8 +75,7 @@ void findDependency(String group, String artifact) { } dependencies { - api 'org.openrewrite:rewrite-core:latest.release' - api "org.openrewrite:rewrite-core:latest.release" + api 'org.openrewrite:rewrite-gradle:latest.release' } """, """ @@ -91,8 +88,7 @@ void findDependency(String group, String artifact) { } dependencies { - implementation 'org.openrewrite:rewrite-core:latest.release' - implementation "org.openrewrite:rewrite-core:latest.release" + implementation 'org.openrewrite:rewrite-gradle:latest.release' } """ ) @@ -101,7 +97,7 @@ void findDependency(String group, String artifact) { @ParameterizedTest @CsvSource(value = {"org.openrewrite:rewrite-core", "*:*"}, delimiterString = ":") - void findMapStyleDependency(String group, String artifact) { + void changeStringStyleDependency(String group, String artifact) { rewriteRun( spec -> spec.recipe(new ChangeDependencyConfiguration(group, artifact, "implementation", null)), buildGradle( @@ -115,8 +111,8 @@ void findMapStyleDependency(String group, String artifact) { } dependencies { - api group: 'org.openrewrite', name: 'rewrite-core', version: 'latest.release' - api group: "org.openrewrite", name: "rewrite-core", version: "latest.release" + api 'org.openrewrite:rewrite-core:latest.release' + api "org.openrewrite:rewrite-core:latest.release" } """, """ @@ -129,18 +125,17 @@ void findMapStyleDependency(String group, String artifact) { } dependencies { - implementation group: 'org.openrewrite', name: 'rewrite-core', version: 'latest.release' - implementation group: "org.openrewrite", name: "rewrite-core", version: "latest.release" + implementation 'org.openrewrite:rewrite-core:latest.release' + implementation "org.openrewrite:rewrite-core:latest.release" } """ ) ); } - @Disabled @ParameterizedTest @CsvSource(value = {"org.openrewrite:rewrite-core", "*:*"}, delimiterString = ":") - void withoutVersionShouldNotChange(String group, String artifact) { + void changeMapStyleDependency(String group, String artifact) { rewriteRun( spec -> spec.recipe(new ChangeDependencyConfiguration(group, artifact, "implementation", null)), buildGradle( @@ -154,10 +149,8 @@ void withoutVersionShouldNotChange(String group, String artifact) { } dependencies { - api 'org.openrewrite:rewrite-core' - api "org.openrewrite:rewrite-core" - api group: 'org.openrewrite', name: 'rewrite-core' - api group: "org.openrewrite", name: "rewrite-core" + api group: 'org.openrewrite', name: 'rewrite-core', version: 'latest.release' + api group: "org.openrewrite", name: "rewrite-core", version: "latest.release" } """, """ @@ -170,10 +163,8 @@ void withoutVersionShouldNotChange(String group, String artifact) { } dependencies { - implementation 'org.openrewrite:rewrite-core' - implementation "org.openrewrite:rewrite-core" - implementation group: 'org.openrewrite', name: 'rewrite-core' - implementation group: "org.openrewrite", name: "rewrite-core" + implementation group: 'org.openrewrite', name: 'rewrite-core', version: 'latest.release' + implementation group: "org.openrewrite", name: "rewrite-core", version: "latest.release" } """ ) @@ -181,8 +172,8 @@ void withoutVersionShouldNotChange(String group, String artifact) { } @ParameterizedTest - @CsvSource(value = {"org.eclipse.jetty:jetty-servlet", "*:*"}, delimiterString = ":") - void worksWithClassifier(String group, String artifact) { + @CsvSource(value = {"org.openrewrite:rewrite-core", "*:*"}, delimiterString = ":") + void changeGStringStyleDependency(String group, String artifact) { rewriteRun( spec -> spec.recipe(new ChangeDependencyConfiguration(group, artifact, "implementation", null)), buildGradle( @@ -195,11 +186,9 @@ void worksWithClassifier(String group, String artifact) { mavenCentral() } + def version = "latest.release" dependencies { - api 'org.eclipse.jetty:jetty-servlet:9.4.9.v20180320:tests' - api "org.eclipse.jetty:jetty-servlet:9.4.9.v20180320:tests" - api group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.9.v20180320', classifier: 'tests' - api group: "org.eclipse.jetty", name: "jetty-servlet", version: "9.4.9.v20180320", classifier: "tests" + api "org.openrewrite:rewrite-core:${version}" } """, """ @@ -211,11 +200,9 @@ void worksWithClassifier(String group, String artifact) { mavenCentral() } + def version = "latest.release" dependencies { - implementation 'org.eclipse.jetty:jetty-servlet:9.4.9.v20180320:tests' - implementation "org.eclipse.jetty:jetty-servlet:9.4.9.v20180320:tests" - implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.9.v20180320', classifier: 'tests' - implementation group: "org.eclipse.jetty", name: "jetty-servlet", version: "9.4.9.v20180320", classifier: "tests" + implementation "org.openrewrite:rewrite-core:${version}" } """ ) @@ -223,10 +210,29 @@ void worksWithClassifier(String group, String artifact) { } @ParameterizedTest - @CsvSource(value = {"org.eclipse.jetty:jetty-servlet", "*:*"}, delimiterString = ":") - void worksWithExt(String group, String artifact) { + @CsvSource(value = {"*:a", "*:*"}, delimiterString = ":") + void worksForProjectDependencies(String group, String artifact) { rewriteRun( spec -> spec.recipe(new ChangeDependencyConfiguration(group, artifact, "implementation", null)), + buildGradle( + """ + dependencies { + compile project(":a") + } + """, + """ + dependencies { + implementation project(":a") + } + """ + ) + ); + } + + @Test + void onlyChangeSpecificDependency() { + rewriteRun( + spec -> spec.recipe(new ChangeDependencyConfiguration("org.openrewrite", "rewrite-core", "implementation", null)), buildGradle( """ plugins { @@ -238,18 +244,8 @@ void worksWithExt(String group, String artifact) { } dependencies { - api 'org.eclipse.jetty:jetty-servlet@jar' - api "org.eclipse.jetty:jetty-servlet@jar" - api 'org.eclipse.jetty:jetty-servlet:9.4.9.v20180320@jar' - api "org.eclipse.jetty:jetty-servlet:9.4.9.v20180320@jar" - api 'org.eclipse.jetty:jetty-servlet:9.4.9.v20180320:tests@jar' - api "org.eclipse.jetty:jetty-servlet:9.4.9.v20180320:tests@jar" - api group: 'org.eclipse.jetty', name: 'jetty-servlet', ext: 'jar' - api group: "org.eclipse.jetty", name: "jetty-servlet", ext: "jar" - api group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.9.v20180320', ext: 'jar' - api group: "org.eclipse.jetty", name: "jetty-servlet", version: "9.4.9.v20180320", ext: "jar" - api group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.9.v20180320', classifier: 'tests', ext: 'jar' - api group: "org.eclipse.jetty", name: "jetty-servlet", version: "9.4.9.v20180320", classifier: "tests", ext: "jar" + api group: 'org.openrewrite', name: 'rewrite-core', version: 'latest.release' + testImplementation group: "org.openrewrite", name: "rewrite-test", version: "latest.release" } """, """ @@ -262,53 +258,8 @@ void worksWithExt(String group, String artifact) { } dependencies { - implementation 'org.eclipse.jetty:jetty-servlet@jar' - implementation "org.eclipse.jetty:jetty-servlet@jar" - implementation 'org.eclipse.jetty:jetty-servlet:9.4.9.v20180320@jar' - implementation "org.eclipse.jetty:jetty-servlet:9.4.9.v20180320@jar" - implementation 'org.eclipse.jetty:jetty-servlet:9.4.9.v20180320:tests@jar' - implementation "org.eclipse.jetty:jetty-servlet:9.4.9.v20180320:tests@jar" - implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', ext: 'jar' - implementation group: "org.eclipse.jetty", name: "jetty-servlet", ext: "jar" - implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.9.v20180320', ext: 'jar' - implementation group: "org.eclipse.jetty", name: "jetty-servlet", version: "9.4.9.v20180320", ext: "jar" - implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.9.v20180320', classifier: 'tests', ext: 'jar' - implementation group: "org.eclipse.jetty", name: "jetty-servlet", version: "9.4.9.v20180320", classifier: "tests", ext: "jar" - } - """ - ) - ); - } - - @Test - void worksForProjectDependencies() { - rewriteRun( - spec -> spec.recipe(new ChangeDependencyConfiguration("*", "*", "implementation", null)), - buildGradle( - """ - dependencies { - compile project(":a") - compile "org.openrewrite:rewrite-core:7.40.0" - compile "org.openrewrite:rewrite-core:7.40.0", { - exclude name: "foo" - } - compile group: "org.openrewrite", name: "rewrite-core", version: "7.40.0" - compile group: "org.openrewrite", name: "rewrite-core", version: "7.40.0", { - exclude name: "foo" - } - } - """, - """ - dependencies { - implementation project(":a") - implementation "org.openrewrite:rewrite-core:7.40.0" - implementation "org.openrewrite:rewrite-core:7.40.0", { - exclude name: "foo" - } - implementation group: "org.openrewrite", name: "rewrite-core", version: "7.40.0" - implementation group: "org.openrewrite", name: "rewrite-core", version: "7.40.0", { - exclude name: "foo" - } + implementation group: 'org.openrewrite', name: 'rewrite-core', version: 'latest.release' + testImplementation group: "org.openrewrite", name: "rewrite-test", version: "latest.release" } """ ) From 2f19d8c710bfb6fab83e0a5d19a336c553a72aec Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sun, 13 Aug 2023 08:37:21 -0400 Subject: [PATCH 137/447] Polish --- .../java/org/openrewrite/java/search/FindCallGraph.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindCallGraph.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindCallGraph.java index 054e7a8d5be..2bd52c332de 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindCallGraph.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindCallGraph.java @@ -15,9 +15,8 @@ */ package org.openrewrite.java.search; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; +import org.openrewrite.internal.TreeVisitorAdapter; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaTypeSignatureBuilder; @@ -25,6 +24,7 @@ import org.openrewrite.java.table.MethodCallGraph; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; +import org.openrewrite.marker.SearchResult; import java.util.Collections; import java.util.IdentityHashMap; From 68a3cd0b2af8d06435bcb30f13ad38968fb7c09c Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sun, 13 Aug 2023 09:42:29 -0400 Subject: [PATCH 138/447] Don't use jgit FileMode in FindParseToPrintInequality --- .../org/openrewrite/search/FindParseToPrintInequality.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/search/FindParseToPrintInequality.java b/rewrite-core/src/main/java/org/openrewrite/search/FindParseToPrintInequality.java index 2f7012092cd..e9872a9da3f 100644 --- a/rewrite-core/src/main/java/org/openrewrite/search/FindParseToPrintInequality.java +++ b/rewrite-core/src/main/java/org/openrewrite/search/FindParseToPrintInequality.java @@ -15,7 +15,6 @@ */ package org.openrewrite.search; -import org.eclipse.jgit.lib.FileMode; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; import org.openrewrite.Tree; @@ -53,17 +52,13 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree instanceof ParseError) { ParseError parseError = (ParseError) tree; if (parseError.getErroneous() != null) { - FileMode mode = parseError.getFileAttributes() != null && parseError.getFileAttributes() - .isExecutable() ? FileMode.EXECUTABLE_FILE : FileMode.REGULAR_FILE; try (InMemoryDiffEntry diffEntry = new InMemoryDiffEntry( parseError.getSourcePath(), parseError.getSourcePath(), null, parseError.getText(), parseError.getErroneous().printAll(), - Collections.emptySet(), - mode, - mode + Collections.emptySet() )) { inequalities.insertRow(ctx, new ParseToPrintInequalities.Row( parseError.getSourcePath().toString(), From 62bcd36950a9ac2effabce617491bb00abd27ce2 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 14 Aug 2023 14:20:16 -0400 Subject: [PATCH 139/447] FindMavenSettings --- .../maven/search/FindMavenSettings.java | 66 +++++++++++++ .../maven/table/EffectiveMavenSettings.java | 42 ++++++++ .../maven/search/FindMavenSettingsTest.java | 95 +++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/table/EffectiveMavenSettings.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java new file mode 100644 index 00000000000..6f592542fd3 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.search; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.maven.MavenIsoVisitor; +import org.openrewrite.maven.table.EffectiveMavenSettings; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.xml.tree.Xml; + +import java.io.UncheckedIOException; + +public class FindMavenSettings extends Recipe { + final transient EffectiveMavenSettings settings = new EffectiveMavenSettings(this); + + @Override + public String getDisplayName() { + return "Find effective maven settings"; + } + + @Override + public String getDescription() { + return "List the effective maven settings file for the current project."; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + XmlMapper mapper = new XmlMapper(); + + return new MavenIsoVisitor<ExecutionContext>() { + @Override + public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + MavenResolutionResult mrr = getResolutionResult(); + if (mrr.getMavenSettings() != null) { + try { + settings.insertRow(ctx, new EffectiveMavenSettings.Row( + document.getSourcePath().toString(), + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mrr.getMavenSettings()) + )); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + } + return document; + } + }; + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/table/EffectiveMavenSettings.java b/rewrite-maven/src/main/java/org/openrewrite/maven/table/EffectiveMavenSettings.java new file mode 100644 index 00000000000..15ba35d63eb --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/table/EffectiveMavenSettings.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.table; + +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +public class EffectiveMavenSettings extends DataTable<EffectiveMavenSettings.Row> { + + public EffectiveMavenSettings(Recipe recipe) { + super(recipe, EffectiveMavenSettings.Row.class, + EffectiveMavenSettings.class.getName(), + "Effective maven settings", + "The maven settings file used by each pom."); + } + + @Value + public static class Row { + @Column(displayName = "POM", + description = "The location of the `pom.xml`.") + String pom; + + @Column(displayName = "Maven settings", + description = "Effective maven settings.") + String mavenSettings; + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java new file mode 100644 index 00000000000..05f3adcfa00 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2022 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.maven.MavenExecutionContextView; +import org.openrewrite.maven.MavenSettings; +import org.openrewrite.maven.table.EffectiveMavenSettings; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.maven.Assertions.pomXml; + +public class FindMavenSettingsTest implements RewriteTest { + + @SuppressWarnings("ConstantConditions") + //language=xml + private static final MavenSettings SPRING_MILESTONES_SETTINGS = MavenSettings.parse(Parser.Input.fromString(""" + <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> + <activeProfiles> + <activeProfile> + repo + </activeProfile> + </activeProfiles> + <profiles> + <profile> + <id>repo</id> + <repositories> + <repository> + <id>spring-milestones</id> + <name>Spring Milestones</name> + <url>https://repo.spring.io/milestone</url> + </repository> + </repositories> + </profile> + </profiles> + </settings> + """), new InMemoryExecutionContext()); + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new FindMavenSettings()); + } + + @Test + void producesDataTable() { + rewriteRun( + spec -> spec + .executionContext(MavenExecutionContextView.view(new InMemoryExecutionContext()) + .setMavenSettings(SPRING_MILESTONES_SETTINGS, "repo")) + .dataTable(EffectiveMavenSettings.Row.class, rows -> assertThat(rows).hasSize(2)), + pomXml( + """ + <project> + <groupId>org.openrewrite.example</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <modules> + <module>module</module> + </modules> + </project> + """, + spec -> spec.path("pom.xml") + ), + pomXml( + """ + <project> + <groupId>org.openrewrite.example</groupId> + <artifactId>module</artifactId> + <version>1</version> + </project> + """, + spec -> spec.path("module/pom.xml") + ) + ); + } +} From 917e013b7dc1fa9e233359542f6780f5a3b002ad Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 14 Aug 2023 14:31:42 -0400 Subject: [PATCH 140/447] One more test --- .../maven/search/FindMavenSettingsTest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java index 05f3adcfa00..6f48c6466f7 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java @@ -92,4 +92,39 @@ void producesDataTable() { ) ); } + + @Test + void producesDataTableImplicitSubmodule() { + rewriteRun( + spec -> spec + .executionContext(MavenExecutionContextView.view(new InMemoryExecutionContext()) + .setMavenSettings(SPRING_MILESTONES_SETTINGS, "repo")) + .dataTable(EffectiveMavenSettings.Row.class, rows -> assertThat(rows).hasSize(2)), + pomXml( + """ + <project> + <groupId>org.openrewrite.example</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </project> + """, + spec -> spec.path("pom.xml") + ), + pomXml( + """ + <project> + <parent> + <groupId>org.openrewrite.example</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </parent> + <groupId>org.openrewrite.example</groupId> + <artifactId>module</artifactId> + <version>1</version> + </project> + """, + spec -> spec.path("module/pom.xml") + ) + ); + } } From c77790ebbf4c69b095619a7ad4193d7550b3ef45 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 14 Aug 2023 14:55:59 -0400 Subject: [PATCH 141/447] Always write a row for poms in FindMavenSettings --- .../java/org/openrewrite/maven/search/FindMavenSettings.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java index 6f592542fd3..04f288c2540 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java @@ -58,6 +58,11 @@ public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { } catch (JsonProcessingException e) { throw new UncheckedIOException(e); } + } else { + settings.insertRow(ctx, new EffectiveMavenSettings.Row( + document.getSourcePath().toString(), + "" + )); } return document; } From d4a05a4c1606e3deb671019ecb8a50e34ed9aa78 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Tue, 15 Aug 2023 17:52:24 +0200 Subject: [PATCH 142/447] Rely on parser to catch print inequality (#3476) --- .../main/java/org/openrewrite/yaml/FormatPreservingReader.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java index 3d1dd62b121..7ed1244cf81 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java @@ -78,9 +78,6 @@ public int read(@NonNull char[] cbuf, int off, int len) throws IOException { buffer.ensureCapacity(buffer.size() + read); for (int i = 0; i < read; i++) { char e = cbuf[i]; - if (Character.UnicodeBlock.of(e) != Character.UnicodeBlock.BASIC_LATIN) { - throw new IllegalArgumentException("Only ASCII characters are supported for now"); - } buffer.add(e); } } From 0b4287b6d0e229492fc72c8d82674bd38bfc6e5d Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Tue, 15 Aug 2023 17:53:28 +0200 Subject: [PATCH 143/447] Add columns for type and method to `MethodCalls` datatable produced by `FindMethods` (#3474) * Add FindMethods MethodCalls columns for type and method Particularly helps cases with wildcards, as `*..*Utils *(..)` does not always produce a data table row that can be traced back without context. Having the type and method name available, and split out from additional context, makes it easier to sort & filter on values as opposed to the usage line only. * Add argument types * Do not filter nulls; instead use String::valueOf --- .../java/search/FindMethodsTest.java | 38 +++++++++++++++++++ .../openrewrite/java/search/FindMethods.java | 25 ++++++++++-- .../openrewrite/java/table/MethodCalls.java | 12 ++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodsTest.java index d373d1bf78f..eeb5dd14384 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodsTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.java.table.MethodCalls; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; @@ -208,4 +209,41 @@ public void foo() {} ) ); } + + @Test + void datatableFormat() { + rewriteRun( + spec -> spec.dataTableAsCsv(MethodCalls.class.getName(), + """ + sourceFile,method,className,methodName,argumentTypes + A.java,"new B.C().foo(bar, 123)","B$C",foo,"java.lang.String, int" + """ + ).recipe(new FindMethods("B.C foo(..)", false)), + java( + """ + public class B { + public static class C { + public void foo(String bar, int baz) {} + } + } + """ + ), + java( + """ + public class A { + void test(String bar) { + new B.C().foo(bar, 123); + } + } + """, + """ + public class A { + void test(String bar) { + /*~~>*/new B.C().foo(bar, 123); + } + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethods.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethods.java index b3fae2b4d07..0b56fe082bb 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethods.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethods.java @@ -22,6 +22,7 @@ import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.table.MethodCalls; +import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.marker.SearchResult; @@ -76,7 +77,13 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu if (javaSourceFile != null) { methodCalls.insertRow(ctx, new MethodCalls.Row( javaSourceFile.getSourcePath().toString(), - method.printTrimmed(getCursor()) + method.printTrimmed(getCursor()), + method.getMethodType().getDeclaringType().getFullyQualifiedName(), + method.getSimpleName(), + method.getArguments().stream() + .map(Expression::getType) + .map(String::valueOf) + .collect(Collectors.joining(", ")) )); } m = SearchResult.found(m); @@ -92,7 +99,13 @@ public J.MemberReference visitMemberReference(J.MemberReference memberRef, Execu if (javaSourceFile != null) { methodCalls.insertRow(ctx, new MethodCalls.Row( javaSourceFile.getSourcePath().toString(), - memberRef.printTrimmed(getCursor()) + memberRef.printTrimmed(getCursor()), + memberRef.getMethodType().getDeclaringType().getFullyQualifiedName(), + memberRef.getMethodType().getName(), + memberRef.getArguments().stream() + .map(Expression::getType) + .map(String::valueOf) + .collect(Collectors.joining(", ")) )); } m = m.withReference(SearchResult.found(m.getReference())); @@ -108,7 +121,13 @@ public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext ctx) { if (javaSourceFile != null) { methodCalls.insertRow(ctx, new MethodCalls.Row( javaSourceFile.getSourcePath().toString(), - newClass.printTrimmed(getCursor()) + newClass.printTrimmed(getCursor()), + newClass.getType().toString(), + "<constructor>", + newClass.getArguments().stream() + .map(Expression::getType) + .map(String::valueOf) + .collect(Collectors.joining(", ")) )); } n = SearchResult.found(n); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/table/MethodCalls.java b/rewrite-java/src/main/java/org/openrewrite/java/table/MethodCalls.java index 2a66e66b41a..48a71ace298 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/table/MethodCalls.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/table/MethodCalls.java @@ -39,5 +39,17 @@ public static class Row { @Column(displayName = "Method call", description = "The text of the method call.") String method; + + @Column(displayName = "Class name", + description = "The class name of the method call.") + String className; + + @Column(displayName = "Method name", + description = "The method name of the method call.") + String methodName; + + @Column(displayName = "Argument types", + description = "The argument types of the method call.") + String argumentTypes; } } From a64748fa90d580a87efe8471daedbd2e115c9d70 Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Tue, 15 Aug 2023 14:07:42 -0500 Subject: [PATCH 144/447] Don't allow downgrade Gradle plugin when an exact version is provided --- .../gradle/plugins/AddPluginVisitor.java | 2 +- .../gradle/plugins/UpgradePluginVersionTest.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java index 35df134b4c0..f15fbd763c3 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java @@ -69,7 +69,7 @@ public static Optional<String> resolvePluginVersion(String pluginId, String curr Optional<String> version; if (versionComparator instanceof ExactVersion) { - version = Optional.of(newVersion); + version = versionComparator.upgrade(currentVersion, Collections.singletonList(newVersion)); } else if (versionComparator instanceof LatestPatch && !versionComparator.isValid(currentVersion, currentVersion)) { // in the case of "latest.patch", a new version can only be derived if the // current version is a semantic version diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/UpgradePluginVersionTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/UpgradePluginVersionTest.java index cbd27a67ddc..59457780e64 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/UpgradePluginVersionTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/UpgradePluginVersionTest.java @@ -141,4 +141,18 @@ void defaultsToLatestRelease() { ) ); } + + @Test + void dontDowngradeWhenExactVersion() { + rewriteRun( + spec -> spec.recipe(new UpgradePluginVersion("io.spring.dependency-management", "1.0.15.RELEASE", null)), + buildGradle( + """ + plugins { + id 'io.spring.dependency-management' version '1.1.0' + } + """ + ) + ); + } } From 53f809532841d7d239241a21cad547366464c1e3 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Tue, 15 Aug 2023 21:38:48 -0400 Subject: [PATCH 145/447] Add existence only check to FindMavenSettings --- .../maven/search/FindMavenSettings.java | 19 ++++++++++++++++--- .../maven/search/FindMavenSettingsTest.java | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java index 04f288c2540..80badc27103 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindMavenSettings.java @@ -16,11 +16,14 @@ package org.openrewrite.maven.search; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import lombok.EqualsAndHashCode; +import lombok.Value; import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.maven.MavenIsoVisitor; import org.openrewrite.maven.table.EffectiveMavenSettings; import org.openrewrite.maven.tree.MavenResolutionResult; @@ -28,8 +31,16 @@ import java.io.UncheckedIOException; +@Value +@EqualsAndHashCode(callSuper = false) public class FindMavenSettings extends Recipe { - final transient EffectiveMavenSettings settings = new EffectiveMavenSettings(this); + @Option(displayName = "Existence check only", + description = "Only record that a maven settings file exists; do not include its contents.", + required = false) + @Nullable + Boolean existenceCheckOnly; + + transient EffectiveMavenSettings settings = new EffectiveMavenSettings(this); @Override public String getDisplayName() { @@ -53,7 +64,9 @@ public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { try { settings.insertRow(ctx, new EffectiveMavenSettings.Row( document.getSourcePath().toString(), - mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mrr.getMavenSettings()) + Boolean.TRUE.equals(existenceCheckOnly) ? + "exists" : + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mrr.getMavenSettings()) )); } catch (JsonProcessingException e) { throw new UncheckedIOException(e); diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java index 6f48c6466f7..7c390e4a093 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindMavenSettingsTest.java @@ -57,7 +57,7 @@ public class FindMavenSettingsTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new FindMavenSettings()); + spec.recipe(new FindMavenSettings(null)); } @Test From 92dbac23f4e3941c38a7e4fcf717f68264e0ba04 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 16 Aug 2023 10:41:25 +0200 Subject: [PATCH 146/447] Report parse-to-print failures in `RewriteTest` Also attempt at solving JGit problem when running tests in `rewrite` --- rewrite-test/build.gradle.kts | 8 +-- .../org/openrewrite/test/RewriteTest.java | 59 ++++++++++++++----- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/rewrite-test/build.gradle.kts b/rewrite-test/build.gradle.kts index 54ca69f4b24..e7f48222158 100644 --- a/rewrite-test/build.gradle.kts +++ b/rewrite-test/build.gradle.kts @@ -11,11 +11,5 @@ dependencies { implementation("org.assertj:assertj-core:latest.release") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv") implementation("org.slf4j:slf4j-api:1.7.36") - - if (System.getProperty("idea.active") != null && - System.getProperty("idea.sync.active") != null) { - // because the shaded jgit will not be available on the classpath - // for the IntelliJ runner - runtimeOnly("org.eclipse.jgit:org.eclipse.jgit:5.13.+") - } + implementation("org.eclipse.jgit:org.eclipse.jgit:5.13.+") } diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java index 977c3daeeda..f107c16d250 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java @@ -22,6 +22,7 @@ import org.openrewrite.config.CompositeRecipe; import org.openrewrite.config.Environment; import org.openrewrite.config.OptionDescriptor; +import org.openrewrite.internal.InMemoryDiffEntry; import org.openrewrite.internal.RecipeIntrospectionUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.NonNull; @@ -30,6 +31,7 @@ import org.openrewrite.marker.Markers; import org.openrewrite.quark.Quark; import org.openrewrite.remote.Remote; +import org.openrewrite.tree.ParseError; import java.io.ByteArrayInputStream; import java.nio.file.Path; @@ -435,9 +437,22 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) nextSourceFile: for (Map.Entry<SourceFile, SourceSpec<?>> specForSourceFile : specBySourceFile.entrySet()) { SourceSpec<?> sourceSpec = specForSourceFile.getValue(); + SourceFile source = specForSourceFile.getKey(); + if (source instanceof ParseError) { + ParseError parseError = (ParseError) source; + if (parseError.getErroneous() != null) { + assertContentEquals( + parseError, + parseError.getText(), + parseError.getErroneous().printAll(), + "Bug in source parser or printer resulted in the following difference for" + ); + } + } + for (Result result : allResults) { - if ((result.getBefore() == null && specForSourceFile.getKey() == null) || - (result.getBefore() != null && result.getBefore().getId().equals(specForSourceFile.getKey().getId()))) { + if ((result.getBefore() == null && source == null) || + (result.getBefore() != null && result.getBefore().getId().equals(source.getId()))) { if (result.getAfter() != null) { String expectedAfter = sourceSpec.after == null ? null : sourceSpec.after.apply(result.getAfter().printAll(out.clone())); @@ -446,15 +461,7 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) String expected = sourceSpec.noTrim ? expectedAfter : trimIndentPreserveCRLF(expectedAfter); - assertThat(actual) - .as(() -> { - SourceFile expectedSourceFile = new DelegateSourceFileForDiff(result.getAfter(), expected); - String diff = new Result(expectedSourceFile, result.getAfter(), Collections.emptyList()).diff(); - return String.format("Unexpected result in \"%s\"%s", - result.getAfter().getSourcePath(), - diff.isEmpty() ? "" : "\n" + diff); - }) - .isEqualTo(expected); + assertContentEquals(result.getAfter(), expected, actual, "Unexpected result in"); sourceSpec.eachResult.accept(result.getAfter(), testMethodSpec, testClassSpec); } else { boolean isRemote = result.getAfter() instanceof Remote; @@ -503,8 +510,8 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) // if we get here, there was no result. if (sourceSpec.after != null) { String actual = sourceSpec.noTrim ? - specForSourceFile.getKey().printAll(out.clone()) : - trimIndentPreserveCRLF(specForSourceFile.getKey().printAll(out.clone())); + source.printAll(out.clone()) : + trimIndentPreserveCRLF(source.printAll(out.clone())); String before = sourceSpec.noTrim ? sourceSpec.before : trimIndentPreserveCRLF(sourceSpec.before); @@ -515,11 +522,11 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) .as("To assert that a Recipe makes no change, supply only \"before\" source.") .isNotEqualTo(before); assertThat(actual) - .as("The recipe should have made the following change to \"" + specForSourceFile.getKey().getSourcePath() + "\"") + .as("The recipe should have made the following change to \"" + source.getSourcePath() + "\"") .isEqualTo(expected); } //noinspection unchecked - ((Consumer<SourceFile>) sourceSpec.afterRecipe).accept(specForSourceFile.getKey()); + ((Consumer<SourceFile>) sourceSpec.afterRecipe).accept(source); } SoftAssertions newFilesGenerated = new SoftAssertions(); @@ -544,6 +551,28 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) } } + static void assertContentEquals(SourceFile sourceFile, String expected, String actual, String errorMessagePrefix) { + try { + try (InMemoryDiffEntry diffEntry = new InMemoryDiffEntry( + sourceFile.getSourcePath(), + sourceFile.getSourcePath(), + null, + expected, + actual, + Collections.emptySet() + )) { + assertThat(actual) + .as(errorMessagePrefix + " \"%s\":\n%s", sourceFile.getSourcePath(), diffEntry.getDiff()) + .isEqualTo(expected); + } + } catch (LinkageError e) { + // in case JGit fails to load properly + assertThat(actual) + .as(errorMessagePrefix + " \"%s\"", sourceFile.getSourcePath()) + .isEqualTo(expected); + } + } + default void rewriteRun(SourceSpec<?>... sources) { rewriteRun(spec -> { }, sources); From 5064a30e71612f463b82eb5bd3b5c00475d5a2af Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 16 Aug 2023 12:45:05 -0700 Subject: [PATCH 147/447] Fix EffectiveMavenRepositories test failing on Windows --- .../maven/search/EffectiveMavenRepositories.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositories.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositories.java index 1ec47738844..5271f57de55 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositories.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveMavenRepositories.java @@ -17,10 +17,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Option; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.SearchResult; import org.openrewrite.maven.MavenExecutionContextView; @@ -31,6 +28,8 @@ import java.util.StringJoiner; +import static org.openrewrite.PathUtils.separatorsToUnix; + @Value @EqualsAndHashCode(callSuper = false) public class EffectiveMavenRepositories extends Recipe { @@ -65,19 +64,19 @@ public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { for (MavenRepository repository : mrr.getPom().getRepositories()) { repositories.add(repository.getUri()); table.insertRow(ctx, new EffectiveMavenRepositoriesTable.Row( - document.getSourcePath().toString(), + separatorsToUnix(document.getSourcePath().toString()), repository.getUri())); } for (MavenRepository repository : MavenExecutionContextView.view(ctx) .getRepositories(mrr.getMavenSettings(), mrr.getActiveProfiles())) { repositories.add(repository.getUri()); table.insertRow(ctx, new EffectiveMavenRepositoriesTable.Row( - document.getSourcePath().toString(), + separatorsToUnix(document.getSourcePath().toString()), repository.getUri())); } repositories.add(MavenRepository.MAVEN_CENTRAL.getUri()); table.insertRow(ctx, new EffectiveMavenRepositoriesTable.Row( - document.getSourcePath().toString(), + separatorsToUnix(document.getSourcePath().toString()), MavenRepository.MAVEN_CENTRAL.getUri())); if (Boolean.TRUE.equals(useMarkers)) { return SearchResult.found(document, repositories.toString()); From 941b6b23ba717f746be51e035f401f1b24e27347 Mon Sep 17 00:00:00 2001 From: Kun Li <122563761+kunli2@users.noreply.github.com> Date: Wed, 16 Aug 2023 16:08:38 -0700 Subject: [PATCH 148/447] For language-specific services, change to compare class-name to get rid of class loading issues. Also, add autoFormat to the service. (#3482) --- .../org/openrewrite/java/JavaVisitor.java | 5 ++-- .../java/service/AutoFormatService.java | 30 +++++++++++++++++++ .../openrewrite/java/tree/JavaSourceFile.java | 5 +++- 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/service/AutoFormatService.java diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index 1e660b2e816..f11ed21db64 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -21,7 +21,7 @@ import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.format.AutoFormatVisitor; +import org.openrewrite.java.service.AutoFormatService; import org.openrewrite.java.service.ImportService; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; @@ -92,7 +92,8 @@ public <J2 extends J> J2 autoFormat(J2 j, P p, Cursor cursor) { @SuppressWarnings({"ConstantConditions", "unchecked"}) public <J2 extends J> J2 autoFormat(J2 j, @Nullable J stopAfter, P p, Cursor cursor) { - return (J2) new AutoFormatVisitor<>(stopAfter).visit(j, p, cursor); + AutoFormatService service = getCursor().firstEnclosingOrThrow(JavaSourceFile.class).service(AutoFormatService.class); + return (J2) service.autoFormatVisitor(stopAfter).visit(j, p, cursor); } /** diff --git a/rewrite-java/src/main/java/org/openrewrite/java/service/AutoFormatService.java b/rewrite-java/src/main/java/org/openrewrite/java/service/AutoFormatService.java new file mode 100644 index 00000000000..4126b7093cc --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/service/AutoFormatService.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.service; + +import org.openrewrite.Incubating; +import org.openrewrite.Tree; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.format.AutoFormatVisitor; + +@Incubating(since = "8.2.0") +public class AutoFormatService { + + public <P> JavaVisitor<P> autoFormatVisitor(@Nullable Tree stopAfter) { + return new AutoFormatVisitor<>(stopAfter); + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java index 95bd23e2184..581924e6cfc 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java @@ -19,6 +19,7 @@ import org.openrewrite.SourceFile; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.TypesInUse; +import org.openrewrite.java.service.AutoFormatService; import org.openrewrite.java.service.ImportService; import java.nio.file.Path; @@ -56,8 +57,10 @@ public interface JavaSourceFile extends J { @Incubating(since = "8.2.0") @SuppressWarnings("unchecked") default <S> S service(Class<S> service) { - if (service == ImportService.class) { + if (ImportService.class.getName().equals(service.getName())) { return (S) new ImportService(); + } else if (AutoFormatService.class.getName().equals(service.getName())) { + return (S) new AutoFormatService(); } throw new UnsupportedOperationException("Service " + service + " not supported"); } From d2b31a85ce17be43893b333e1ef42b5cdad8da3d Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Wed, 16 Aug 2023 22:07:29 -0400 Subject: [PATCH 149/447] EffectiveDependencies: --- .../gradle/search/DependencyInsight.java | 2 - .../maven/search/EffectiveDependencies.java | 97 +++++++++++++++++++ .../maven/table/DependencyGraph.java | 7 +- 3 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveDependencies.java diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java index df7dd7a02b3..d9bd5c3f60e 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java @@ -30,7 +30,6 @@ import org.openrewrite.java.tree.J; import org.openrewrite.marker.SearchResult; import org.openrewrite.maven.table.DependenciesInUse; -import org.openrewrite.maven.table.DependencyGraph; import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.maven.tree.ResolvedDependency; @@ -45,7 +44,6 @@ @EqualsAndHashCode(callSuper = true) public class DependencyInsight extends Recipe { transient DependenciesInUse dependenciesInUse = new DependenciesInUse(this); - transient DependencyGraph dependencyGraph = new DependencyGraph(this); private static final MethodMatcher DEPENDENCY_CONFIGURATION_MATCHER = new MethodMatcher("DependencyHandlerSpec *(..)"); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveDependencies.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveDependencies.java new file mode 100644 index 00000000000..8c08e6a14b8 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveDependencies.java @@ -0,0 +1,97 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.java.marker.JavaProject; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.maven.MavenIsoVisitor; +import org.openrewrite.maven.table.DependencyGraph; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; +import org.openrewrite.maven.tree.Scope; +import org.openrewrite.xml.tree.Xml; + +import java.util.List; +import java.util.Map; + +@Value +@EqualsAndHashCode(callSuper = false) +public class EffectiveDependencies extends Recipe { + transient DependencyGraph dependencyGraph = new DependencyGraph(this); + + @Option(displayName = "Scope", + description = "Match dependencies with the specified scope", + valid = {"compile", "test", "runtime", "provided"}, + example = "compile") + String scope; + + @Override + public String getDisplayName() { + return "Effective dependencies"; + } + + @Override + public String getDescription() { + return "Emit the data of binary dependency relationships."; + } + + @Override + public Validated<Object> validate() { + return super.validate().and(Validated.test("scope", "scope is a valid Maven scope", scope, + s -> Scope.fromName(s) != Scope.Invalid)); + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + Scope aScope = Scope.fromName(scope); + + return new MavenIsoVisitor<ExecutionContext>() { + @Override + public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + MavenResolutionResult mrr = getResolutionResult(); + String javaProject = getCursor().firstEnclosingOrThrow(Xml.Document.class).getMarkers() + .findFirst(JavaProject.class).map(JavaProject::getProjectName).orElse(""); + String javaSourceSet = getCursor().firstEnclosingOrThrow(Xml.Document.class).getMarkers() + .findFirst(JavaSourceSet.class).map(JavaSourceSet::getName).orElse("main"); + + for (Map.Entry<Scope, List<ResolvedDependency>> scopedDependencies : mrr.getDependencies().entrySet()) { + if (!scopedDependencies.getKey().isInClasspathOf(aScope)) { + continue; + } + emitDependency(mrr.getPom().getGav(), scopedDependencies.getValue(), ctx, javaProject, javaSourceSet); + } + return document; + } + }; + } + + private void emitDependency(ResolvedGroupArtifactVersion gav, List<ResolvedDependency> dependencies, + ExecutionContext ctx, String javaProject, String javaSourceSet) { + for (ResolvedDependency d : dependencies) { + dependencyGraph.insertRow(ctx, new DependencyGraph.Row( + javaProject, + javaSourceSet, + String.format("%s:%s:%s", gav.getGroupId(), gav.getArtifactId(), gav.getVersion()), + String.format("%s:%s:%s", d.getGav().getGroupId(), d.getGav().getArtifactId(), d.getGav().getVersion()) + )); + emitDependency(d.getGav(), d.getDependencies(), ctx, javaProject, javaSourceSet); + } + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/table/DependencyGraph.java b/rewrite-maven/src/main/java/org/openrewrite/maven/table/DependencyGraph.java index 4e2d3cf8fdf..1ba53788e17 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/table/DependencyGraph.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/table/DependencyGraph.java @@ -25,7 +25,8 @@ public class DependencyGraph extends DataTable<DependencyGraph.Row> { public DependencyGraph(Recipe recipe) { super(recipe, DependencyGraph.Row.class, DependenciesInUse.class.getName(), - "Dependency graph", "Relationships between dependencies."); + "Dependency graph", + "Relationships between dependencies."); } @Value @@ -39,11 +40,11 @@ public static class Row { String sourceSet; @Column(displayName = "From dependency", - description = "A dependency that depends on the 'to' dependency.") + description = "What depends on the 'to' dependency.") String from; @Column(displayName = "From dependency", - description = "A dependency that depends on the 'to' dependency.") + description = "A dependency.") String to; } } From 61705a5c6c70ca41ccb2c11b573e621ebd39eb0b Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 16 Aug 2023 19:27:38 -0700 Subject: [PATCH 150/447] Add CobolCli LstProvenance type --- .../src/main/java/org/openrewrite/marker/LstProvenance.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java b/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java index 05da361ec33..d59e9f775da 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/LstProvenance.java @@ -43,6 +43,7 @@ public enum Type { Gradle, Maven, Bazel, - Cli + Cli, + CobolCli } } From b4a6b7be6e7e92119c3a15f104de106015b642e2 Mon Sep 17 00:00:00 2001 From: Kun Li <122563761+kunli2@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:52:58 -0700 Subject: [PATCH 151/447] Fix a bug of last comment suffix (#3484) * Fix the last comment suffix bug * use text block instead --- .../java/org/openrewrite/java/tree/Space.java | 1 + .../org/openrewrite/java/tree/SpaceTest.java | 73 +++++++++++++++---- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index b5fcc783d8a..086cd60c8a3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -205,6 +205,7 @@ public static Space format(String formatting) { // If a file ends with a single-line comment there may be no terminating newline if (!comment.toString().isEmpty()) { comments.add(new TextComment(false, comment.toString(), prefix.toString(), Markers.EMPTY)); + prefix = new StringBuilder(); } // Shift the whitespace on each comment forward to be a suffix of the comment before it, and the diff --git a/rewrite-java/src/test/java/org/openrewrite/java/tree/SpaceTest.java b/rewrite-java/src/test/java/org/openrewrite/java/tree/SpaceTest.java index 75db8b35ec8..57f4dd802bb 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/tree/SpaceTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/tree/SpaceTest.java @@ -49,10 +49,14 @@ void spaceWithSameCommentsDoesntChangeReference() { @Test void singleLineComment() { - @SuppressWarnings("TextBlockMigration") var cf = Space.format(" \n" + - "// I'm a little // teapot\n" + - "// Short and stout //\n " + - "// Here is my handle\n "); + var cf = Space.format(""" + \s + // I'm a little // teapot + // Short and stout // + \ + // Here is my handle + \ + """); assertThat(cf.getComments()).hasSize(3); @@ -73,12 +77,15 @@ void singleLineComment() { @Test void multiLineComment() { - @SuppressWarnings("TextBlockMigration") var cf = Space.format(" \n" + - "/* /* Here is my spout */\n" + - "/* When I get all steamed up */\n" + - "/* /*\n" + - "Here me shout\n" + - "*/\n "); + var cf = Space.format(""" + \s + /* /* Here is my spout */ + /* When I get all steamed up */ + /* /* + Here me shout + */ + \ + """); assertThat(cf.getComments()).hasSize(3); @@ -99,12 +106,14 @@ void multiLineComment() { @Test void javadocComment() { - @SuppressWarnings("TextBlockMigration") var cf = Space.format( - " \n" + - "/**\n" + - " * /** Tip me over and pour me out!\n" + - " * https://somewhere/over/the/rainbow.txt\n" + - " */\n " + var cf = Space.format(""" + \s + /** + * /** Tip me over and pour me out! + * https://somewhere/over/the/rainbow.txt + */ + \ + """ ); assertThat(cf.getComments()).hasSize(1); @@ -143,4 +152,36 @@ void findIndent() { assertThat(Space.build(" \n \n ", emptyList()).getIndent()) .isEqualTo(" "); } + + @Test + void singleLineCommentSuffix() { + var s1 = Space.format(""" + + //comment"""); + TextComment c1 = (TextComment) s1.getComments().get(0); + assertThat(c1.getSuffix()).isEqualTo(""); + + var s2 = Space.format(""" + + //comment + """); + TextComment c2 = (TextComment) s2.getComments().get(0); + assertThat(c2.getSuffix()).isEqualTo("\n"); + } + + @Test + void MultiCommentsSuffix() { + String input = """ + + //c1 + //c2"""; + var space = Space.format(input); + TextComment c1 = (TextComment) space.getComments().get(0); + assertThat(c1.getText()).isEqualTo("c1"); + assertThat(c1.getSuffix()).isEqualTo("\n "); + + TextComment c2 = (TextComment) space.getComments().get(1); + assertThat(c2.getText()).isEqualTo("c2"); + assertThat(c2.getSuffix()).isEqualTo(""); + } } From b5901966b97610ddf57b67cb2bf41955c716d67c Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Wed, 16 Aug 2023 22:31:45 -0400 Subject: [PATCH 152/447] EffectiveManagedDependencies --- .../search/EffectiveManagedDependencies.java | 72 +++++++++++++++++++ .../maven/table/ManagedDependencyGraph.java | 47 ++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveManagedDependencies.java create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/table/ManagedDependencyGraph.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveManagedDependencies.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveManagedDependencies.java new file mode 100644 index 00000000000..50f313d4c4d --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/EffectiveManagedDependencies.java @@ -0,0 +1,72 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.maven.MavenIsoVisitor; +import org.openrewrite.maven.table.ManagedDependencyGraph; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedManagedDependency; +import org.openrewrite.xml.tree.Xml; + +@Value +@EqualsAndHashCode(callSuper = false) +public class EffectiveManagedDependencies extends Recipe { + transient ManagedDependencyGraph dependencyGraph = new ManagedDependencyGraph(this); + + @Override + public String getDisplayName() { + return "Effective dependencies"; + } + + @Override + public String getDescription() { + return "Emit the data of binary dependency relationships."; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new MavenIsoVisitor<ExecutionContext>() { + @Override + public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + MavenResolutionResult mrr = getResolutionResult(); + emitParent(mrr, ctx); + return document; + } + }; + } + + private void emitParent(MavenResolutionResult mrr, ExecutionContext ctx) { + if (mrr.getParent() != null) { + dependencyGraph.insertRow(ctx, new ManagedDependencyGraph.Row( + String.format("%s:%s:%s", mrr.getPom().getGroupId(), mrr.getPom().getArtifactId(), mrr.getPom().getVersion()), + String.format("%s:%s:%s", mrr.getParent().getPom().getGroupId(), + mrr.getParent().getPom().getArtifactId(), mrr.getParent().getPom().getVersion()) + )); + + for (ResolvedManagedDependency managed : mrr.getPom().getDependencyManagement()) { + dependencyGraph.insertRow(ctx, new ManagedDependencyGraph.Row( + String.format("%s:%s:%s", mrr.getPom().getGroupId(), mrr.getPom().getArtifactId(), mrr.getPom().getVersion()), + String.format("%s:%s:%s", managed.getGroupId(), managed.getArtifactId(), managed.getVersion()) + )); + } + } + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/table/ManagedDependencyGraph.java b/rewrite-maven/src/main/java/org/openrewrite/maven/table/ManagedDependencyGraph.java new file mode 100644 index 00000000000..349c90203a1 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/table/ManagedDependencyGraph.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.table; + +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +public class ManagedDependencyGraph extends DataTable<ManagedDependencyGraph.Row> { + + public ManagedDependencyGraph(Recipe recipe) { + super(recipe, ManagedDependencyGraph.Row.class, + DependenciesInUse.class.getName(), + "Managed dependency graph", + "Relationships between POMs and their ancestors that define managed dependencies."); + } + + @Value + public static class Row { + @Column(displayName = "From dependency", + description = "What depends on the 'to' dependency.") + String from; + + @Column(displayName = "From dependency", + description = "A dependency.") + String to; + } + + public enum Relationship { + Parent, + ManagedDependency + } +} From c3dc5e30fb699870f8995564d6fc2bc5a7bd18c1 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Thu, 17 Aug 2023 19:59:02 -0400 Subject: [PATCH 153/447] Polish --- .../openrewrite/yaml/tree/MappingTest.java | 151 +++++++++--------- 1 file changed, 72 insertions(+), 79 deletions(-) diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java index c6b78abe98c..03709c13f24 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java @@ -15,7 +15,6 @@ */ package org.openrewrite.yaml.tree; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -32,13 +31,15 @@ class MappingTest implements RewriteTest { @Issue("https://github.com/openrewrite/rewrite/issues/423") @Test void emptyObject() { - rewriteRun(yaml("workflow_dispatch: {}")); + rewriteRun( + yaml("workflow_dispatch: {}")); } @Issue("https://github.com/openrewrite/rewrite/issues/423") @Test void flowStyleMapping() { - rewriteRun(yaml( + rewriteRun( + yaml( """ { "data": { @@ -52,7 +53,8 @@ void flowStyleMapping() { @Test void multipleEntries() { - rewriteRun(yaml( + rewriteRun( + yaml( """ type : specs.openrewrite.org/v1beta/visitor # comment with colon : name : org.openrewrite.text.ChangeTextToJon @@ -87,29 +89,30 @@ void deep() { @Test void largeScalar() { - rewriteRun(yaml( + rewriteRun( + yaml( """ - spring: - cloud: - config: - server: - composite: - - type: git - uri: git@gitserver.com:team/repo1.git - ignoreLocalSshSettings: true - greater-than-block-text-password: > - {cipher}d1b2458ccede07c856ff952bd841638ff4dd12ed1d36812663c3c7262d57bf46 - privateKey: "-----BEGIN RSA PRIVATE KEY-----\\nMIIEpAIBAAKCAQEAoqyz6YaYMTr7L8GLPSQpAQXaM04gRx4CCsGK2kfLQdw4BlqI\\n2U7EeNwxq1I1L3Ag6E7wH4BHLHd4TKaZR6agFkn8oomz71yZPGjuZQ==\\n-----END RSA PRIVATE KEY-----" - repos: - repo1: - uri: git@gitserver.com:team/repo2.git - hostKey: someHostKey - hostKeyAlgorithm: ssh-rsa - privateKey: | - -----BEGIN RSA PRIVATE KEY----- - MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX - 69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT - -----END RSA PRIVATE KEY----- + spring: + cloud: + config: + server: + composite: + - type: git + uri: git@gitserver.com:team/repo1.git + ignoreLocalSshSettings: true + greater-than-block-text-password: > + {cipher}d1b2458ccede07c856ff952bd841638ff4dd12ed1d36812663c3c7262d57bf46 + privateKey: "-----BEGIN RSA PRIVATE KEY-----\\nMIIEpAIBAAKCAQEAoqyz6YaYMTr7L8GLPSQpAQXaM04gRx4CCsGK2kfLQdw4BlqI\\n2U7EeNwxq1I1L3Ag6E7wH4BHLHd4TKaZR6agFkn8oomz71yZPGjuZQ==\\n-----END RSA PRIVATE KEY-----" + repos: + repo1: + uri: git@gitserver.com:team/repo2.git + hostKey: someHostKey + hostKeyAlgorithm: ssh-rsa + privateKey: | + -----BEGIN RSA PRIVATE KEY----- + MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX + 69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT + -----END RSA PRIVATE KEY----- """ ) ); @@ -120,9 +123,9 @@ void mappingContainingSequence() { rewriteRun( yaml( """ - foo: - - bar: qwer - asdf: hjkl + foo: + - bar: qwer + asdf: hjkl """ ) ); @@ -152,7 +155,8 @@ void commentWithColon() { @Issue("https://github.com/openrewrite/rewrite/issues/1469") @Test void emptyDocument() { - rewriteRun(yaml( + rewriteRun( + yaml( "", spec -> spec.afterRecipe(documents -> assertThat(documents.getDocuments()).isNotEmpty()) ) @@ -161,7 +165,8 @@ void emptyDocument() { @Test void multiDocOnlyComments() { - rewriteRun(yaml( + rewriteRun( + yaml( """ # doc-1-pre --- @@ -184,7 +189,8 @@ void multiDocOnlyComments() { @Test void singleDocOnlyComments() { - rewriteRun(yaml( + rewriteRun( + yaml( """ # doc-1-pre """, @@ -228,12 +234,12 @@ void literals() { rewriteRun( yaml( """ - data: - prometheus.yml: |- - global: - scrape_interval: 10s - scrape_timeout: 9s - evaluation_interval: 10s + data: + prometheus.yml: |- + global: + scrape_interval: 10s + scrape_timeout: 9s + evaluation_interval: 10s """ ) ); @@ -268,11 +274,11 @@ void scalarKeyAnchor() { rewriteRun( yaml( """ - foo: - - start: start - - &anchor buz: buz - - *anchor: baz - - end: end + foo: + - start: start + - &anchor buz: buz + - *anchor: baz + - end: end """ ) ); @@ -283,11 +289,11 @@ void scalarEntryValue() { rewriteRun( yaml( """ - foo: - - start: start - - buz: &anchor ooo - - fuz: *anchor - - end: end + foo: + - start: start + - buz: &anchor ooo + - fuz: *anchor + - end: end """ ) ); @@ -298,10 +304,10 @@ void aliasEntryKey() { rewriteRun( yaml( """ - bar: - &abc yo: friend - baz: - *abc: friendly + bar: + &abc yo: friend + baz: + *abc: friendly """ ) ); @@ -309,12 +315,14 @@ void aliasEntryKey() { @Test void scalarKeyAnchorInBrackets() { - rewriteRun(yaml("foo: [start: start, &anchor buz: buz, *anchor: baz, end: end]")); + rewriteRun( + yaml("foo: [start: start, &anchor buz: buz, *anchor: baz, end: end]")); } @Test void scalarEntryValueAnchorInBrackets() { - rewriteRun(yaml("foo: [start: start, &anchor buz: buz, baz: *anchor, end: end]")); + rewriteRun( + yaml("foo: [start: start, &anchor buz: buz, baz: *anchor, end: end]")); } @Test @@ -374,29 +382,13 @@ void mappingAnchor() { rewriteRun( yaml( """ - defaults: &defaults - A: 1 - B: 2 - mapping: - << : *defaults - A: 23 - C: 99 - """ - ) - ); - } - - @Disabled - @Test - void mappingKey() { - rewriteRun( - yaml( - """ - ? - - key-1 - - key-2 - : - - value + defaults: &defaults + A: 1 + B: 2 + mapping: + << : *defaults + A: 23 + C: 99 """ ) ); @@ -434,7 +426,8 @@ void mappingKey() { " \"\\L\" ", " \"\\P\" ", }) - void escapeSequences(String string) { - rewriteRun(yaml("escaped-value: $string")); + void escapeSequences() { + rewriteRun( + yaml("escaped-value: $string")); } } From 1a8934cd5b6aafbbe24789570197e733e8514a30 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Thu, 17 Aug 2023 20:49:09 -0400 Subject: [PATCH 154/447] Fix parsing of multiline YAML scalars --- .../yaml/FormatPreservingReader.java | 3 ++ .../java/org/openrewrite/yaml/YamlParser.java | 3 ++ .../org/openrewrite/yaml/tree/ScalarTest.java | 36 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/ScalarTest.java diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java index 7ed1244cf81..e25872e7bb9 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java @@ -15,6 +15,7 @@ */ package org.openrewrite.yaml; +import lombok.Getter; import org.openrewrite.internal.lang.NonNull; import org.yaml.snakeyaml.events.Event; @@ -31,6 +32,8 @@ class FormatPreservingReader extends Reader { private final Reader delegate; private ArrayList<Character> buffer = new ArrayList<>(); + + @Getter private int bufferIndex = 0; FormatPreservingReader(Reader delegate) { diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index 8051fbac92c..d12dc207537 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -221,6 +221,9 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr case PLAIN: default: style = Yaml.Scalar.Style.PLAIN; + if (!scalarValue.startsWith("@") && event.getStartMark().getIndex() >= reader.getBufferIndex()) { + scalarValue = reader.readStringFromBuffer(event.getStartMark().getIndex(), event.getEndMark().getIndex() - 1); + } break; } BlockBuilder builder = blockStack.isEmpty() ? null : blockStack.peek(); diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/ScalarTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/ScalarTest.java new file mode 100644 index 00000000000..0be008600bf --- /dev/null +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/ScalarTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.yaml.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.yaml.Assertions.yaml; + +public class ScalarTest implements RewriteTest { + + @Test + void multilineScalar() { + rewriteRun( + yaml( + """ + key: value that spans + multiple lines + """ + ) + ); + } +} From 0d5cf43f5a4c1defdd2b192b5f08292ce6618606 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 18 Aug 2023 10:28:15 +0200 Subject: [PATCH 155/447] Add `Space#format(String, int, int)` for performance (#3485) * Add `Space#format(String, int, int)` for performance * Also optimize `Space#format()` a bit * Put range check after two most common cases * Minor optimization for `Space#firstPrefix(List<J>)` * Rebase on main --- .../ReloadableJava17ParserVisitor.java | 29 ++++++------ .../java/org/openrewrite/java/tree/Space.java | 47 +++++++++++++------ 2 files changed, 48 insertions(+), 28 deletions(-) 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 32a32893f37..0ddd7bd2493 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 @@ -546,7 +546,7 @@ public J visitCompilationUnit(CompilationUnitTree node, Space fmt) { if (node.getTypeDecls().isEmpty() || cu.getPackageName() != null || !node.getImports().isEmpty()) { // if the package and imports are empty, allow the formatting to apply to the first class declaration. // in this way, javadoc comments are interpreted as javadocs on that class declaration. - fmt = format(source.substring(0, cu.getStartPosition())); + fmt = format(source, 0, cu.getStartPosition()); cursor(cu.getStartPosition()); } @@ -578,7 +578,7 @@ public J visitCompilationUnit(CompilationUnitTree node, Space fmt) { packageDecl == null ? null : padRight(packageDecl, sourceBefore(";")), convertAll(node.getImports(), this::statementDelim, this::statementDelim), convertAll(node.getTypeDecls().stream().filter(JCClassDecl.class::isInstance).collect(toList())), - format(source.substring(cursor)) + format(source, cursor, source.length()) ); } @@ -1075,9 +1075,9 @@ public J visitNewArray(NewArrayTree node, Space fmt) { int endBracket = indexOfNextNonWhitespace(beginBracket + 1, source); dimensions.add(new J.ArrayDimension( randomId(), - format(source.substring(cursor, beginBracket)), + format(source, cursor, beginBracket), Markers.EMPTY, - padRight(new J.Empty(randomId(), format(source.substring(beginBracket + 1, endBracket)), Markers.EMPTY), EMPTY))); + padRight(new J.Empty(randomId(), format(source, beginBracket + 1, endBracket), Markers.EMPTY), EMPTY))); cursor = endBracket + 1; } else { break; @@ -1341,7 +1341,7 @@ private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) fullName += "." + part; int endOfPrefix = indexOfNextNonWhitespace(0, part); - Space identFmt = endOfPrefix > 0 ? format(part.substring(0, endOfPrefix)) : EMPTY; + Space identFmt = endOfPrefix > 0 ? format(part, 0, endOfPrefix) : EMPTY; Matcher whitespaceSuffix = whitespaceSuffixPattern.matcher(part); //noinspection ResultOfMethodCallIgnored @@ -1476,7 +1476,7 @@ private J.VariableDeclarations visitVariables(List<VariableTree> nodes, Space fm if (typeExpr != null && typeExpr.getMarkers().findFirst(JavaVarKeyword.class).isEmpty()) { int varargStart = indexOfNextNonWhitespace(vartype.getStartPosition(), source); if (source.startsWith("...", varargStart)) { - varargs = format(source.substring(cursor, varargStart)); + varargs = format(source, cursor, varargStart); cursor = varargStart + 3; } } @@ -1518,8 +1518,8 @@ private List<JLeftPadded<Space>> arrayDimensions() { if (dims == null) { dims = new ArrayList<>(2); } - dims.add(padLeft(format(source.substring(cursor, beginBracket)), - format(source.substring(beginBracket + 1, endBracket)))); + dims.add(padLeft(format(source, cursor, beginBracket), + format(source, beginBracket + 1, endBracket))); cursor = endBracket + 1; } else { break; @@ -1750,9 +1750,10 @@ private Space sourceBefore(String untilDelim, @Nullable Character stop) { cursor += untilDelim.length(); return EMPTY; } - String prefix = source.substring(cursor, delimIndex); - cursor += prefix.length() + untilDelim.length(); // advance past the delimiter - return Space.format(prefix); + + Space space = format(source, cursor, delimIndex); + cursor = delimIndex + untilDelim.length(); // advance past the delimiter + return space; } private <T> JRightPadded<T> padRight(T tree, Space right) { @@ -1822,9 +1823,9 @@ private Space whitespace() { if (nextNonWhitespace == cursor) { return EMPTY; } - String prefix = source.substring(cursor, nextNonWhitespace); - cursor += prefix.length(); - return format(prefix); + Space space = format(source, cursor, nextNonWhitespace); + cursor = nextNonWhitespace; + return space; } private String skip(@Nullable String token) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index 086cd60c8a3..fefa198a56b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -64,7 +64,7 @@ public static Space build(@Nullable String whitespace, List<Comment> comments) { return Space.EMPTY; } else if (whitespace.length() <= 100) { //noinspection StringOperationCanBeSimplified - return flyweights.computeIfAbsent(new String(whitespace), k -> new Space(whitespace, comments)); + return flyweights.computeIfAbsent(whitespace, k -> new Space(new String(whitespace), comments)); } } return new Space(whitespace, comments); @@ -130,26 +130,32 @@ public boolean isEmpty() { } public static Space firstPrefix(@Nullable List<? extends J> trees) { - return trees == null || trees.isEmpty() ? Space.EMPTY : trees.iterator().next().getPrefix(); + return trees == null || trees.isEmpty() ? Space.EMPTY : trees.get(0).getPrefix(); } public static Space format(String formatting) { - if (formatting.isEmpty()) { + return format(formatting, 0, formatting.length()); + } + + public static Space format(String formatting, int beginIndex, int toIndex) { + if (beginIndex == toIndex) { return Space.EMPTY; - } else if (" ".equals(formatting)) { + } else if (toIndex == beginIndex + 1 && ' ' == formatting.charAt(beginIndex)) { return Space.SINGLE_SPACE; + } else { + rangeCheck(formatting.length(), beginIndex, toIndex); } StringBuilder prefix = new StringBuilder(); StringBuilder comment = new StringBuilder(); - List<Comment> comments = new ArrayList<>(); + List<Comment> comments = new ArrayList<>(1); boolean inSingleLineComment = false; boolean inMultiLineComment = false; char last = 0; - for (int i = 0; i < formatting.length(); i++) { + for (int i = beginIndex; i < toIndex; i++) { char c = formatting.charAt(i); switch (c) { case '/': @@ -157,13 +163,13 @@ public static Space format(String formatting) { comment.append(c); } else if (last == '/' && !inMultiLineComment) { inSingleLineComment = true; - comment = new StringBuilder(); + comment.setLength(0); } else if (last == '*' && inMultiLineComment && comment.length() > 0) { inMultiLineComment = false; comment.setLength(comment.length() - 1); // trim the last '*' comments.add(new TextComment(true, comment.toString(), prefix.toString(), Markers.EMPTY)); - prefix = new StringBuilder(); - comment = new StringBuilder(); + prefix.setLength(0); + comment.setLength(0); continue; } else { comment.append(c); @@ -174,8 +180,8 @@ public static Space format(String formatting) { if (inSingleLineComment) { inSingleLineComment = false; comments.add(new TextComment(false, comment.toString(), prefix.toString(), Markers.EMPTY)); - prefix = new StringBuilder(); - comment = new StringBuilder(); + prefix.setLength(0); + comment.setLength(0); prefix.append(c); } else if (!inMultiLineComment) { prefix.append(c); @@ -188,7 +194,7 @@ public static Space format(String formatting) { comment.append(c); } else if (last == '/' && !inMultiLineComment) { inMultiLineComment = true; - comment = new StringBuilder(); + comment.setLength(0); } else { comment.append(c); } @@ -203,9 +209,9 @@ public static Space format(String formatting) { last = c; } // If a file ends with a single-line comment there may be no terminating newline - if (!comment.toString().isEmpty()) { + if (comment.length() > 0) { comments.add(new TextComment(false, comment.toString(), prefix.toString(), Markers.EMPTY)); - prefix = new StringBuilder(); + prefix.setLength(0); } // Shift the whitespace on each comment forward to be a suffix of the comment before it, and the @@ -437,4 +443,17 @@ public enum Location { WILDCARD_PREFIX, YIELD_PREFIX, } + + static void rangeCheck(int arrayLength, int fromIndex, int toIndex) { + if (fromIndex > toIndex) { + throw new IllegalArgumentException( + "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); + } + if (fromIndex < 0) { + throw new StringIndexOutOfBoundsException(fromIndex); + } + if (toIndex > arrayLength) { + throw new StringIndexOutOfBoundsException(toIndex); + } + } } From 2e03134fd39eb6ed450ca21f6929777000dfe9cc Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Fri, 18 Aug 2023 15:18:48 +0200 Subject: [PATCH 156/447] Updating method type in ReorderMethodArguments (#3486) --- .../java/ReorderMethodArgumentsTest.java | 30 +++++-------------- .../java/ReorderMethodArguments.java | 20 +++++++++++-- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java index ea3953e35c1..e17ce740684 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java @@ -16,13 +16,9 @@ package org.openrewrite.java; import org.junit.jupiter.api.Test; -import org.openrewrite.ExecutionContext; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; -import static org.openrewrite.test.RewriteTest.toRecipe; class ReorderMethodArgumentsTest implements RewriteTest { @@ -31,17 +27,7 @@ void reorderArguments() { rewriteRun( spec -> spec.recipes( new ReorderMethodArguments("a.A foo(String, Integer, Integer)", - new String[]{"n", "m", "s"}, null, null, null), - toRecipe(() -> new JavaVisitor<>() { - @Override - public J visitLiteral(J.Literal literal, ExecutionContext p) { - if (literal.getType() == JavaType.Primitive.String) { - doAfterVisit(new ChangeLiteral<>(literal, l -> "anotherstring")); - } - return super.visitLiteral(literal, p); - } - }) - ).cycles(1).expectedCyclesThatMakeChanges(1), + new String[]{"n", "m", "s"}, null, null, null)), java( """ package a; @@ -73,7 +59,7 @@ public void test() { a.foo( 2, 1, - "anotherstring" + "mystring" ); } } @@ -85,9 +71,8 @@ public void test() { @Test void reorderArgumentsWithNoSourceAttachment() { rewriteRun( - spec -> spec.recipe(new ReorderMethodArguments("a.A foo(..)", - new String[]{"s", "n"}, new String[]{"n", "s"}, null, null)) - .cycles(1).expectedCyclesThatMakeChanges(1), + spec -> spec.recipe(new ReorderMethodArguments("a.A foo(String,..)", + new String[]{"s", "n"}, new String[]{"n", "s"}, null, null)), java( """ package a; @@ -123,15 +108,14 @@ public void test() { @Test void reorderArgumentsWhereOneOfTheOriginalArgumentsIsVararg() { rewriteRun( - spec -> spec.recipe(new ReorderMethodArguments("a.A foo(..)", - new String[]{"s", "o", "n"}, null, null, null)) - .cycles(1).expectedCyclesThatMakeChanges(1), + spec -> spec.recipe(new ReorderMethodArguments("a.A foo(String,Integer,..)", + new String[]{"s", "o", "n"}, null, null, null)), java( """ package a; public class A { public void foo(String s, Integer n, Object... o) {} - public void bar(String s, Object... o) {} + public void foo(String s, Object... o) {} } """ ), diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java b/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java index a7f5819b19c..405b4c70a56 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java @@ -68,7 +68,7 @@ public class ReorderMethodArguments extends Recipe { @Option(displayName = "Ignore type definition", description = "When set to `true` the definition of the old type will be left untouched. " + - "This is useful when you're replacing usage of a class but don't want to rename it.", + "This is useful when you're replacing usage of a class but don't want to rename it.", required = false) @Nullable Boolean ignoreDefinition; @@ -135,6 +135,8 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu int i = 0; List<JRightPadded<Expression>> reordered = new ArrayList<>(originalArgs.size()); + List<String> reorderedNames = new ArrayList<>(originalArgs.size()); + List<JavaType> reorderedTypes = new ArrayList<>(originalArgs.size()); List<Space> formattings = new ArrayList<>(originalArgs.size()); List<Space> rightFormattings = new ArrayList<>(originalArgs.size()); @@ -144,12 +146,19 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu // this is a varargs argument List<JRightPadded<Expression>> varargs = originalArgs.subList(fromPos, originalArgs.size()); reordered.addAll(varargs); + for (int j = 0; j < varargs.size(); j++) { + reorderedNames.add(name + j); + reorderedTypes.add(varargs.get(j).getElement().getType()); + } for (JRightPadded<Expression> exp : originalArgs.subList(i, (i++) + varargs.size())) { formattings.add(exp.getElement().getPrefix()); rightFormattings.add(exp.getAfter()); } } else if (fromPos >= 0 && originalArgs.size() > fromPos) { - reordered.add(originalArgs.get(fromPos)); + JRightPadded<Expression> originalArg = originalArgs.get(fromPos); + reordered.add(originalArg); + reorderedNames.add(name); + reorderedTypes.add(originalArg.getElement().getType()); formattings.add(originalArgs.get(i).getElement().getPrefix()); rightFormattings.add(originalArgs.get(i++).getAfter()); } @@ -169,7 +178,12 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu } if (changed) { - m = m.getPadding().withArguments(m.getPadding().getArguments().getPadding().withElements(reordered)); + m = m.getPadding() + .withArguments(m.getPadding().getArguments().getPadding().withElements(reordered)) + .withMethodType(m.getMethodType() + .withParameterNames(reorderedNames) + .withParameterTypes(reorderedTypes) + ); } } return m; From 1d2415b4cfa66d42e81e84de4a122e2433462d20 Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Fri, 18 Aug 2023 12:32:09 -0700 Subject: [PATCH 157/447] Fix a bug of minus continuation indent in AutoDetect --- .../java/style/AutodetectTest.java | 25 +++++++++++++++++++ .../openrewrite/java/style/Autodetect.java | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java index 97ca783cc7b..f74c2547758 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java @@ -32,6 +32,31 @@ private static JavaParser jp() { return JavaParser.fromJavaVersion().build(); } + @Test + void continuationIndent() { + var cus = jp().parse( + """ + class Test { + boolean eq(){ + return (1 == 1 && + 2 == 2 && + 3 == 3); + } + } + """ + ); + + var detector = Autodetect.detector(); + cus.forEach(detector::sample); + var styles = detector.build(); + var tabsAndIndents = NamedStyles.merge(TabsAndIndentsStyle.class, singletonList(styles)); + + assertThat(tabsAndIndents.getUseTabCharacter()).isTrue(); + assertThat(tabsAndIndents.getTabSize()).isEqualTo(4); + assertThat(tabsAndIndents.getIndentSize()).isEqualTo(4); + assertThat(tabsAndIndents.getContinuationIndent()).isEqualTo(8); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1221") @Test void springDemoApp() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java index 4e7ba8843e7..7d76e240d36 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java @@ -241,8 +241,8 @@ public TabsAndIndentsStyle getTabsAndIndentsStyle() { int tabSize = (moreFrequentTabSize == 0) ? 4 : moreFrequentTabSize; IndentStatistic continuationFrequencies = useTabs ? tabContinuationIndentFrequencies : spaceContinuationIndentFrequencies; + int continuationIndent = continuationFrequencies.continuationIndent(useTabs ? 1 : tabSize) * (useTabs ? tabSize : 1); - int continuationIndent = continuationFrequencies.continuationIndent(tabSize); return new TabsAndIndentsStyle( useTabs, tabSize, From 889393587499680f0ff9abfef13bc97af77729b4 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Sat, 19 Aug 2023 15:01:48 +0200 Subject: [PATCH 158/447] Simplify boolean expressions comparing with null and `isEmpty` (#3489) For https://github.com/openrewrite/rewrite-templating/issues/28 --- ...SimplifyBooleanExpressionVisitorTest.java} | 74 +++- .../SimplifyBooleanExpressionVisitor.java | 324 +++++++++++------- .../org/openrewrite/test/RewriteTest.java | 25 +- 3 files changed, 283 insertions(+), 140 deletions(-) rename rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/{SimplifyBooleanExpressionTest.java => SimplifyBooleanExpressionVisitorTest.java} (64%) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java similarity index 64% rename from rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionTest.java rename to rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java index f0f0941d0fb..49c15245cb7 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java @@ -16,6 +16,8 @@ package org.openrewrite.java.cleanup; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.openrewrite.DocumentExample; import org.openrewrite.Issue; import org.openrewrite.test.RecipeSpec; @@ -25,7 +27,7 @@ import static org.openrewrite.test.RewriteTest.toRecipe; @SuppressWarnings("ALL") -class SimplifyBooleanExpressionTest implements RewriteTest { +class SimplifyBooleanExpressionVisitorTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { @@ -346,4 +348,74 @@ public class A { ) ); } + + @ParameterizedTest + @Issue("https://github.com/openrewrite/rewrite-templating/issues/28") + // Mimic what would be inserted by a Refaster template using two nullable parameters, with the second one a literal + @CsvSource(delimiterString = "//", textBlock = """ + a == null || a.isEmpty() // a == null || a.isEmpty() + a == null || !a.isEmpty() // a == null || !a.isEmpty() + a != null && a.isEmpty() // a != null && a.isEmpty() + a != null && !a.isEmpty() // a != null && !a.isEmpty() + + "" == null || "".isEmpty() // true + "" == null || !"".isEmpty() // false + "" != null && "".isEmpty() // true + "" != null && !"".isEmpty() // false + + "b" == null || "b".isEmpty() // false + "b" == null || !"b".isEmpty() // true + "b" != null && "b".isEmpty() // false + "b" != null && !"b".isEmpty() // true + + a == null || a.isEmpty() || "" == null || "".isEmpty() // true + a == null || a.isEmpty() || "" == null || !"".isEmpty() // a == null || a.isEmpty() + a == null || a.isEmpty() || "" != null && "".isEmpty() // true + a == null || a.isEmpty() || "" != null && !"".isEmpty() // a == null || a.isEmpty() + a == null || a.isEmpty() && "" == null || "".isEmpty() // true + a == null || a.isEmpty() && "" == null || !"".isEmpty() // a == null + a == null || a.isEmpty() && "" != null && "".isEmpty() // a == null || a.isEmpty() + a == null || a.isEmpty() && "" != null && !"".isEmpty() // a == null + a == null || !a.isEmpty() || "" == null || "".isEmpty() // true + a == null || !a.isEmpty() || "" == null || !"".isEmpty() // a == null || !a.isEmpty() + a == null || !a.isEmpty() || "" != null && "".isEmpty() // true + a == null || !a.isEmpty() || "" != null && !"".isEmpty() // a == null || !a.isEmpty() + a == null || !a.isEmpty() && "" == null || "".isEmpty() // true + a == null || !a.isEmpty() && "" == null || !"".isEmpty() // a == null + a == null || !a.isEmpty() && "" != null && "".isEmpty() // a == null || !a.isEmpty() + a == null || !a.isEmpty() && "" != null && !"".isEmpty() // a == null + + a == null || a.isEmpty() || "b" == null || "b".isEmpty() // a == null || a.isEmpty() + a == null || a.isEmpty() || "b" == null || !"b".isEmpty() // true + a == null || a.isEmpty() || "b" != null && "b".isEmpty() // a == null || a.isEmpty() + a == null || a.isEmpty() || "b" != null && !"b".isEmpty() // true + a == null || a.isEmpty() && "b" == null || "b".isEmpty() // a == null + a == null || a.isEmpty() && "b" == null || !"b".isEmpty() // true + a == null || a.isEmpty() && "b" != null && "b".isEmpty() // a == null + a == null || a.isEmpty() && "b" != null && !"b".isEmpty() // a == null || a.isEmpty() + a == null || !a.isEmpty() || "b" == null || "b".isEmpty() // a == null || !a.isEmpty() + a == null || !a.isEmpty() || "b" == null || !"b".isEmpty() // true + a == null || !a.isEmpty() || "b" != null && "b".isEmpty() // a == null || !a.isEmpty() + a == null || !a.isEmpty() || "b" != null && !"b".isEmpty() // true + a == null || !a.isEmpty() && "b" == null || "b".isEmpty() // a == null + a == null || !a.isEmpty() && "b" == null || !"b".isEmpty() // true + a == null || !a.isEmpty() && "b" != null && "b".isEmpty() // a == null + a == null || !a.isEmpty() && "b" != null && !"b".isEmpty() // a == null || !a.isEmpty() + """) + void simplifyLiteralNull(String before, String after) { + //language=java + String template = """ + class A { + void foo(String a) { + boolean c = %s; + } + } + """; + String beforeJava = template.formatted(before); + if (before.equals(after)) { + rewriteRun(java(beforeJava)); + } else { + rewriteRun(java(beforeJava, template.formatted(after))); + } + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java index 81b200fa775..e5fe977895b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java @@ -15,149 +15,221 @@ */ package org.openrewrite.java.cleanup; -import org.openrewrite.*; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Tree; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.UnwrapParentheses; import org.openrewrite.java.format.AutoFormatVisitor; -import org.openrewrite.java.tree.Expression; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaSourceFile; -import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.*; + +import java.util.Collections; import static java.util.Objects.requireNonNull; -public class SimplifyBooleanExpressionVisitor extends JavaVisitor<ExecutionContext> { - private static final String MAYBE_AUTO_FORMAT_ME = "MAYBE_AUTO_FORMAT_ME"; +public class SimplifyBooleanExpressionVisitor extends JavaVisitor<ExecutionContext> { + private static final String MAYBE_AUTO_FORMAT_ME = "MAYBE_AUTO_FORMAT_ME"; - @Override - public J visit(@Nullable Tree tree, ExecutionContext ctx) { - if (tree instanceof JavaSourceFile) { - JavaSourceFile cu = (JavaSourceFile) requireNonNull(super.visit(tree, ctx)); - if (tree != cu) { - // recursive simplification - cu = (JavaSourceFile) visitNonNull(cu, ctx); - } - return cu; - } - return super.visit(tree, ctx); + @Override + public J visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof JavaSourceFile) { + JavaSourceFile cu = (JavaSourceFile) requireNonNull(super.visit(tree, ctx)); + if (tree != cu) { + // recursive simplification + cu = (JavaSourceFile) visitNonNull(cu, ctx); } + return cu; + } + return super.visit(tree, ctx); + } - @Override - public J visitBinary(J.Binary binary, ExecutionContext ctx) { - J j = super.visitBinary(binary, ctx); - J.Binary asBinary = (J.Binary) j; - - if (asBinary.getOperator() == J.Binary.Type.And) { - if (isLiteralFalse(asBinary.getLeft())) { - maybeUnwrapParentheses(); - j = asBinary.getLeft(); - } else if (isLiteralFalse(asBinary.getRight())) { - maybeUnwrapParentheses(); - j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); - } else if (removeAllSpace(asBinary.getLeft()).printTrimmed(getCursor()) - .equals(removeAllSpace(asBinary.getRight()).printTrimmed(getCursor()))) { - maybeUnwrapParentheses(); - j = asBinary.getLeft(); - } - } else if (asBinary.getOperator() == J.Binary.Type.Or) { - if (isLiteralTrue(asBinary.getLeft())) { - maybeUnwrapParentheses(); - j = asBinary.getLeft(); - } else if (isLiteralTrue(asBinary.getRight())) { - maybeUnwrapParentheses(); - j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); - } else if (removeAllSpace(asBinary.getLeft()).printTrimmed(getCursor()) - .equals(removeAllSpace(asBinary.getRight()).printTrimmed(getCursor()))) { - maybeUnwrapParentheses(); - j = asBinary.getLeft(); - } - } else if (asBinary.getOperator() == J.Binary.Type.Equal) { - if (isLiteralTrue(asBinary.getLeft())) { - maybeUnwrapParentheses(); - j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); - } else if (isLiteralTrue(asBinary.getRight())) { - maybeUnwrapParentheses(); - j = asBinary.getLeft(); - } - } else if (asBinary.getOperator() == J.Binary.Type.NotEqual) { - if (isLiteralFalse(asBinary.getLeft())) { - maybeUnwrapParentheses(); - j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); - } else if (isLiteralFalse(asBinary.getRight())) { - maybeUnwrapParentheses(); - j = asBinary.getLeft(); - } - } - if (asBinary != j) { - getCursor().getParentTreeCursor().putMessage(MAYBE_AUTO_FORMAT_ME, ""); - } - return j; - } + @Override + public J visitBinary(J.Binary binary, ExecutionContext ctx) { + J j = super.visitBinary(binary, ctx); + J.Binary asBinary = (J.Binary) j; - @Override - public J postVisit(J tree, ExecutionContext ctx) { - J j = super.postVisit(tree, ctx); - if (getCursor().pollMessage(MAYBE_AUTO_FORMAT_ME) != null) { - j = new AutoFormatVisitor<>().visit(j, ctx, getCursor().getParentOrThrow()); - } - return j; + if (asBinary.getOperator() == J.Binary.Type.And) { + if (isLiteralFalse(asBinary.getLeft())) { + maybeUnwrapParentheses(); + j = asBinary.getLeft(); + } else if (isLiteralFalse(asBinary.getRight())) { + maybeUnwrapParentheses(); + j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); + } else if (isLiteralTrue(asBinary.getLeft())) { + maybeUnwrapParentheses(); + j = asBinary.getRight(); + } else if (isLiteralTrue(asBinary.getRight())) { + maybeUnwrapParentheses(); + j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace("")); + } else if (removeAllSpace(asBinary.getLeft()).printTrimmed(getCursor()) + .equals(removeAllSpace(asBinary.getRight()).printTrimmed(getCursor()))) { + maybeUnwrapParentheses(); + j = asBinary.getLeft(); } - - @Override - public J visitUnary(J.Unary unary, ExecutionContext ctx) { - J j = super.visitUnary(unary, ctx); - J.Unary asUnary = (J.Unary) j; - - if (asUnary.getOperator() == J.Unary.Type.Not) { - if (isLiteralTrue(asUnary.getExpression())) { - maybeUnwrapParentheses(); - j = ((J.Literal) asUnary.getExpression()).withValue(false).withValueSource("false"); - } else if (isLiteralFalse(asUnary.getExpression())) { - maybeUnwrapParentheses(); - j = ((J.Literal) asUnary.getExpression()).withValue(true).withValueSource("true"); - } else if (asUnary.getExpression() instanceof J.Unary && ((J.Unary) asUnary.getExpression()).getOperator() == J.Unary.Type.Not) { - maybeUnwrapParentheses(); - j = ((J.Unary) asUnary.getExpression()).getExpression(); - } - } - if (asUnary != j) { - getCursor().getParentTreeCursor().putMessage(MAYBE_AUTO_FORMAT_ME, ""); - } - return j; + } else if (asBinary.getOperator() == J.Binary.Type.Or) { + if (isLiteralTrue(asBinary.getLeft())) { + maybeUnwrapParentheses(); + j = asBinary.getLeft(); + } else if (isLiteralTrue(asBinary.getRight())) { + maybeUnwrapParentheses(); + j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); + } else if (isLiteralFalse(asBinary.getLeft())) { + maybeUnwrapParentheses(); + j = asBinary.getRight(); + } else if (isLiteralFalse(asBinary.getRight())) { + maybeUnwrapParentheses(); + j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace("")); + } else if (removeAllSpace(asBinary.getLeft()).printTrimmed(getCursor()) + .equals(removeAllSpace(asBinary.getRight()).printTrimmed(getCursor()))) { + maybeUnwrapParentheses(); + j = asBinary.getLeft(); } - - /** - * Specifically for removing immediately-enclosing parentheses on Identifiers and Literals. - * This queues a potential unwrap operation for the next visit. After unwrapping something, it's possible - * there are more Simplifications this recipe can identify and perform, which is why visitCompilationUnit - * checks for any changes to the entire Compilation Unit, and if so, queues up another SimplifyBooleanExpression - * recipe call. This convergence loop eventually reconciles any remaining Boolean Expression Simplifications - * the recipe can perform. - */ - private void maybeUnwrapParentheses() { - Cursor c = getCursor().getParentOrThrow().getParentTreeCursor(); - if (c.getValue() instanceof J.Parentheses) { - doAfterVisit(new UnwrapParentheses<>(c.getValue())); - } + } else if (asBinary.getOperator() == J.Binary.Type.Equal) { + if (isLiteralTrue(asBinary.getLeft())) { + maybeUnwrapParentheses(); + j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); + } else if (isLiteralTrue(asBinary.getRight())) { + maybeUnwrapParentheses(); + j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace(" ")); + } else { + j = maybeReplaceCompareWithNull(asBinary, true); } - - private boolean isLiteralTrue(@Nullable Expression expression) { - return expression instanceof J.Literal && ((J.Literal) expression).getValue() == Boolean.valueOf(true); + } else if (asBinary.getOperator() == J.Binary.Type.NotEqual) { + if (isLiteralFalse(asBinary.getLeft())) { + maybeUnwrapParentheses(); + j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); + } else if (isLiteralFalse(asBinary.getRight())) { + maybeUnwrapParentheses(); + j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace(" ")); + } else { + j = maybeReplaceCompareWithNull(asBinary, false); } + } + if (asBinary != j) { + getCursor().getParentTreeCursor().putMessage(MAYBE_AUTO_FORMAT_ME, ""); + } + return j; + } - private boolean isLiteralFalse(@Nullable Expression expression) { - return expression instanceof J.Literal && ((J.Literal) expression).getValue() == Boolean.valueOf(false); - } + @Override + public J postVisit(J tree, ExecutionContext ctx) { + J j = super.postVisit(tree, ctx); + if (getCursor().pollMessage(MAYBE_AUTO_FORMAT_ME) != null) { + j = new AutoFormatVisitor<>().visit(j, ctx, getCursor().getParentOrThrow()); + } + return j; + } + + @Override + public J visitUnary(J.Unary unary, ExecutionContext ctx) { + J j = super.visitUnary(unary, ctx); + J.Unary asUnary = (J.Unary) j; - private J removeAllSpace(J j) { - //noinspection ConstantConditions - return new JavaIsoVisitor<Integer>() { - @Override - public Space visitSpace(Space space, Space.Location loc, Integer integer) { - return Space.EMPTY; - } - }.visit(j, 0); + if (asUnary.getOperator() == J.Unary.Type.Not) { + if (isLiteralTrue(asUnary.getExpression())) { + maybeUnwrapParentheses(); + j = ((J.Literal) asUnary.getExpression()).withValue(false).withValueSource("false"); + } else if (isLiteralFalse(asUnary.getExpression())) { + maybeUnwrapParentheses(); + j = ((J.Literal) asUnary.getExpression()).withValue(true).withValueSource("true"); + } else if (asUnary.getExpression() instanceof J.Unary && ((J.Unary) asUnary.getExpression()).getOperator() == J.Unary.Type.Not) { + maybeUnwrapParentheses(); + j = ((J.Unary) asUnary.getExpression()).getExpression(); } } + if (asUnary != j) { + getCursor().getParentTreeCursor().putMessage(MAYBE_AUTO_FORMAT_ME, ""); + } + return j; + } + + private final MethodMatcher isEmpty = new MethodMatcher("java.lang.String isEmpty()"); + + @Override + public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { + J j = super.visitMethodInvocation(method, executionContext); + J.MethodInvocation asMethod = (J.MethodInvocation) j; + Expression select = asMethod.getSelect(); + if (isEmpty.matches(asMethod) + && select instanceof J.Literal + && select.getType() == JavaType.Primitive.String) { + maybeUnwrapParentheses(); + return booleanLiteral(method, J.Literal.isLiteralValue(select, "")); + } + return j; + } + + /** + * Specifically for removing immediately-enclosing parentheses on Identifiers and Literals. + * This queues a potential unwrap operation for the next visit. After unwrapping something, it's possible + * there are more Simplifications this recipe can identify and perform, which is why visitCompilationUnit + * checks for any changes to the entire Compilation Unit, and if so, queues up another SimplifyBooleanExpression + * recipe call. This convergence loop eventually reconciles any remaining Boolean Expression Simplifications + * the recipe can perform. + */ + private void maybeUnwrapParentheses() { + Cursor c = getCursor().getParentOrThrow().getParentTreeCursor(); + if (c.getValue() instanceof J.Parentheses) { + doAfterVisit(new UnwrapParentheses<>(c.getValue())); + } + } + + private boolean isLiteralTrue(@Nullable Expression expression) { + return expression instanceof J.Literal && ((J.Literal) expression).getValue() == Boolean.valueOf(true); + } + + private boolean isLiteralFalse(@Nullable Expression expression) { + return expression instanceof J.Literal && ((J.Literal) expression).getValue() == Boolean.valueOf(false); + } + + private boolean isNullLiteral(Expression expression) { + return expression instanceof J.Literal && ((J.Literal) expression).getType() == JavaType.Primitive.Null; + } + + private boolean isNonNullLiteral(Expression expression) { + return expression instanceof J.Literal && ((J.Literal) expression).getType() != JavaType.Primitive.Null; + } + + private J maybeReplaceCompareWithNull(J.Binary asBinary, boolean valueIfEqual) { + Expression left = asBinary.getLeft(); + Expression right = asBinary.getRight(); + + boolean leftIsNull = isNullLiteral(left); + boolean rightIsNull = isNullLiteral(right); + if (leftIsNull && rightIsNull) { + maybeUnwrapParentheses(); + return booleanLiteral(asBinary, valueIfEqual); + } + boolean leftIsNonNullLiteral = isNonNullLiteral(left); + boolean rightIsNonNullLiteral = isNonNullLiteral(right); + if ((leftIsNull && rightIsNonNullLiteral) || (rightIsNull && leftIsNonNullLiteral)) { + maybeUnwrapParentheses(); + return booleanLiteral(asBinary, !valueIfEqual); + } + + return asBinary; + } + + private J.Literal booleanLiteral(J j, boolean value) { + return new J.Literal(Tree.randomId(), + j.getPrefix(), + j.getMarkers(), + value, + String.valueOf(value), + Collections.emptyList(), + JavaType.Primitive.Boolean); + } + + private J removeAllSpace(J j) { + //noinspection ConstantConditions + return new JavaIsoVisitor<Integer>() { + @Override + public Space visitSpace(Space space, Space.Location loc, Integer integer) { + return Space.EMPTY; + } + }.visit(j, 0); + } +} diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java index f107c16d250..26ff51becf9 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java @@ -552,19 +552,18 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) } static void assertContentEquals(SourceFile sourceFile, String expected, String actual, String errorMessagePrefix) { - try { - try (InMemoryDiffEntry diffEntry = new InMemoryDiffEntry( - sourceFile.getSourcePath(), - sourceFile.getSourcePath(), - null, - expected, - actual, - Collections.emptySet() - )) { - assertThat(actual) - .as(errorMessagePrefix + " \"%s\":\n%s", sourceFile.getSourcePath(), diffEntry.getDiff()) - .isEqualTo(expected); - } + try (InMemoryDiffEntry diffEntry = new InMemoryDiffEntry( + sourceFile.getSourcePath(), + sourceFile.getSourcePath(), + null, + expected, + actual, + Collections.emptySet() + )) { + assertThat(actual) + .as(errorMessagePrefix + " \"%s\":\n%s", sourceFile.getSourcePath(), diffEntry.getDiff()) + .isEqualTo(expected); + } catch (LinkageError e) { // in case JGit fails to load properly assertThat(actual) From 0483771825339acb5dc6b72b744f65f7932b7590 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Mon, 21 Aug 2023 08:14:40 +0200 Subject: [PATCH 159/447] Add utility `JavaVisitor#service(Class)` method --- .../main/java/org/openrewrite/java/JavaVisitor.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index f11ed21db64..41cd05b02d4 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -15,10 +15,7 @@ */ package org.openrewrite.java; -import org.openrewrite.Cursor; -import org.openrewrite.SourceFile; -import org.openrewrite.Tree; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.service.AutoFormatService; @@ -131,13 +128,17 @@ public void maybeAddImport(String fullyQualifiedName, @Nullable String member, b } public void maybeAddImport(@Nullable String packageName, String typeName, @Nullable String member, boolean onlyIfReferenced) { - ImportService service = getCursor().firstEnclosingOrThrow(JavaSourceFile.class).service(ImportService.class); - JavaVisitor<P> visitor = service.addImportVisitor(packageName, typeName, member, onlyIfReferenced); + JavaVisitor<P> visitor = service(ImportService.class).addImportVisitor(packageName, typeName, member, onlyIfReferenced); if (!getAfterVisit().contains(visitor)) { doAfterVisit(visitor); } } + @Incubating(since = "8.2.0") + public <S> S service(Class<S> service) { + return getCursor().firstEnclosingOrThrow(JavaSourceFile.class).service(service); + } + public void maybeRemoveImport(@Nullable JavaType.FullyQualified clazz) { if (clazz != null) { maybeRemoveImport(clazz.getFullyQualifiedName()); From 982c1896d7ab5308f5d3231ba7c9754858a6f6e4 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Mon, 21 Aug 2023 08:19:30 +0200 Subject: [PATCH 160/447] Allow rewrite-static-analysis to reuse `SimplifyBooleanExpressionVisitor` (#3490) * Allow rewrite-static-analysis to reuse SimplifyBooleanExpressionVisitor Such that we don't have to maintain two separate copies while we wait for rewrite-kotlin to merge into OpenRewrite/rewrite. Method modeled after what we already have in https://github.com/openrewrite/rewrite-static-analysis/blob/772c82062d277e3cab1a1006904817a84b484c8c/src/main/java/org/openrewrite/staticanalysis/SimplifyBooleanExpression.java#L200-L213 * Call shouldSimplifyEqualsOn with the correct side of J.Binary --- .../SimplifyBooleanExpressionVisitor.java | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java index e5fe977895b..a88312bed0f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java @@ -89,21 +89,29 @@ public J visitBinary(J.Binary binary, ExecutionContext ctx) { } } else if (asBinary.getOperator() == J.Binary.Type.Equal) { if (isLiteralTrue(asBinary.getLeft())) { - maybeUnwrapParentheses(); - j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); + if (shouldSimplifyEqualsOn(asBinary.getRight())) { + maybeUnwrapParentheses(); + j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); + } } else if (isLiteralTrue(asBinary.getRight())) { - maybeUnwrapParentheses(); - j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace(" ")); + if (shouldSimplifyEqualsOn(asBinary.getLeft())) { + maybeUnwrapParentheses(); + j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace(" ")); + } } else { j = maybeReplaceCompareWithNull(asBinary, true); } } else if (asBinary.getOperator() == J.Binary.Type.NotEqual) { if (isLiteralFalse(asBinary.getLeft())) { - maybeUnwrapParentheses(); - j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); + if (shouldSimplifyEqualsOn(asBinary.getRight())) { + maybeUnwrapParentheses(); + j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); + } } else if (isLiteralFalse(asBinary.getRight())) { - maybeUnwrapParentheses(); - j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace(" ")); + if (shouldSimplifyEqualsOn(asBinary.getLeft())) { + maybeUnwrapParentheses(); + j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace(" ")); + } } else { j = maybeReplaceCompareWithNull(asBinary, false); } @@ -232,4 +240,21 @@ public Space visitSpace(Space space, Space.Location loc, Integer integer) { } }.visit(j, 0); } + + /** + * Override this method to disable simplification of equals expressions, + * specifically for Kotlin while that is not yet part of the OpenRewrite/rewrite. + * + * Comparing Kotlin nullable type `?` with tree/false can not be simplified, + * e.g. `X?.fun() == true` is not equivalent to `X?.fun()` + * + * Subclasses will want to check if the `org.openrewrite.kotlin.marker.IsNullSafe` + * marker is present. + * + * @param j the expression to simplify + * @return true by default, unless overridden + */ + protected boolean shouldSimplifyEqualsOn(J j) { + return true; + } } From 7f6a9c727f70b2ddabf837de8019ac05d079df10 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Mon, 21 Aug 2023 12:17:16 +0200 Subject: [PATCH 161/447] Do not trim actual either with no trim (#3492) For #3491 --- .../java/org/openrewrite/text/CreateTextFile.java | 2 +- .../main/java/org/openrewrite/test/RewriteTest.java | 4 +++- .../org/openrewrite/text/CreateTextFileTest.java | 12 ++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/CreateTextFile.java b/rewrite-core/src/main/java/org/openrewrite/text/CreateTextFile.java index 1e9606eec06..2e3ecba140b 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/CreateTextFile.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/CreateTextFile.java @@ -82,7 +82,7 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { @Override public Collection<SourceFile> generate(AtomicBoolean shouldCreate, ExecutionContext ctx) { if(shouldCreate.get()) { - return new PlainTextParser().parse(fileContents) + return PlainTextParser.builder().build().parse(fileContents) .map(brandNewFile -> (SourceFile) brandNewFile.withSourcePath(Paths.get(relativeFileName))) .collect(Collectors.toList()); } diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java index 26ff51becf9..83ea21ed8e6 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java @@ -382,7 +382,8 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) .as("Expected a new file for the source path but there was an existing file already present: " + sourceSpec.getSourcePath()) .isNull(); - String actual = result.getAfter().printAll(out.clone()).trim(); + String actual = result.getAfter().printAll(out.clone()); + actual = sourceSpec.noTrim ? actual : actual.trim(); String expected = sourceSpec.noTrim ? sourceSpec.after.apply(actual) : trimIndentPreserveCRLF(sourceSpec.after.apply(actual)); @@ -648,6 +649,7 @@ public <P> String printAll(PrintOutputCapture<P> out) { return out.getOut(); } + @SuppressWarnings("unused") // Lombok delegate exclude interface PrintAll { <P> String printAll(PrintOutputCapture<P> out); } diff --git a/rewrite-test/src/test/java/org/openrewrite/text/CreateTextFileTest.java b/rewrite-test/src/test/java/org/openrewrite/text/CreateTextFileTest.java index 459719dbee8..4205b722046 100644 --- a/rewrite-test/src/test/java/org/openrewrite/text/CreateTextFileTest.java +++ b/rewrite-test/src/test/java/org/openrewrite/text/CreateTextFileTest.java @@ -35,6 +35,18 @@ void hasCreatedFile() { ); } + @Test + void hasCreatedFileWithTrailingNewline() { + rewriteRun( + spec -> spec.recipe(new CreateTextFile("foo\n", ".github/CODEOWNERS", false)), + text( + null, + "foo\n", + spec -> spec.path(".github/CODEOWNERS").noTrim() + ) + ); + } + @DocumentExample @Test void hasOverwrittenFile() { From b388be49b2a12774f3bdca83f0841dfec83a5f6f Mon Sep 17 00:00:00 2001 From: Marit van Dijk <mlvandijk@gmail.com> Date: Mon, 21 Aug 2023 17:06:30 +0200 Subject: [PATCH 162/447] Fix typos (#3493) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cf50996ca4f..ad6417f1734 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ ## More tech. Less debt. -The OpenRewrite project is a mass source code refactoring ecosystem. Reduce 1000s of hours of static code analysis fixes to minutes. Turn a four month migration project into four hours of work. Patch security vulnerabilities across 100s of repositories at once. OpenRewrite automates code refactoring and remediation tasks for you, enabling developers to deliver more business. +The OpenRewrite project is a mass source code refactoring ecosystem. Reduce 1000s of hours of static code analysis fixes to minutes. Turn a four-month migration project into four hours of work. Patch security vulnerabilities across 100s of repositories at once. OpenRewrite automates code refactoring and remediation tasks for you, enabling developers to deliver more business value. Start with our [quickstart guide](https://docs.openrewrite.org/running-recipes/getting-started) and let OpenRewrite start handling the boring parts of software development for you. Full documentation available at [docs.openrewrite.org](https://docs.openrewrite.org/). @@ -29,7 +29,7 @@ See this [doc page](https://docs.openrewrite.org/reference/building-openrewrite- ## Refactoring at scale with Moderne -OpenRewrite's refactoring engine and recipes will always be open source. Build tool plugins like [OpenRewrite Gradle Plugin](https://docs.openrewrite.org/reference/gradle-plugin-configuration) and [OpenRewrite Maven Plugin](https://docs.openrewrite.org/reference/rewrite-maven-plugin) help you run these recipes on one repository at a time. Moderne is a complementary product that executes OpenRewrite recipes at scale on hundreds of millions of lines of code and enables mass committing of results. Moderne freely runs a [public service](https://public.moderne.io) for the benefit of thousands of open source projects. +OpenRewrite's refactoring engine and recipes will always be open source. Build tool plugins like [OpenRewrite Gradle Plugin](https://docs.openrewrite.org/reference/gradle-plugin-configuration) and [OpenRewrite Maven Plugin](https://docs.openrewrite.org/reference/rewrite-maven-plugin) help you run these recipes on one repository at a time. Moderne is a complementary product that executes OpenRewrite recipes at scale on hundreds of millions of lines of code and enables mass-committing of results. Moderne freely runs a [public service](https://public.moderne.io) for the benefit of thousands of open source projects. [![Moderne](./doc/video_preview.png)](https://youtu.be/Mq6bKAeGCz0) From 50d3b54b25eb02899fc1d9e77ed3191fa82266d6 Mon Sep 17 00:00:00 2001 From: Marit van Dijk <mlvandijk@gmail.com> Date: Mon, 21 Aug 2023 18:42:55 +0200 Subject: [PATCH 163/447] Use the correct product name "IntelliJ IDEA" (#3494) --- .../src/main/java/org/openrewrite/style/NamedStyles.java | 2 +- rewrite-java-tck/build.gradle.kts | 2 +- .../src/test/java/org/openrewrite/java/EnvironmentTest.java | 2 +- .../test/java/org/openrewrite/java/style/AutodetectTest.java | 2 +- .../src/main/java/org/openrewrite/java/OrderImports.java | 2 +- .../main/java/org/openrewrite/java/format/SpacesVisitor.java | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/style/NamedStyles.java b/rewrite-core/src/main/java/org/openrewrite/style/NamedStyles.java index 710151b3799..35f167c0309 100644 --- a/rewrite-core/src/main/java/org/openrewrite/style/NamedStyles.java +++ b/rewrite-core/src/main/java/org/openrewrite/style/NamedStyles.java @@ -30,7 +30,7 @@ import static java.util.stream.Collectors.toSet; /** - * A collection of styles by name, e.g. IntelliJ or Google Java Format. + * A collection of styles by name, e.g., IntelliJ IDEA or Google Java Format. */ @Data @EqualsAndHashCode(onlyExplicitlyIncluded = true) diff --git a/rewrite-java-tck/build.gradle.kts b/rewrite-java-tck/build.gradle.kts index 5ca06621f5e..231c68c6938 100644 --- a/rewrite-java-tck/build.gradle.kts +++ b/rewrite-java-tck/build.gradle.kts @@ -11,7 +11,7 @@ dependencies { if (System.getProperty("idea.active") != null || System.getProperty("idea.sync.active") != null) { - // so we can run tests in the IDE with the IntelliJ runner + // so we can run tests in the IDE with the IntelliJ IDEA runner runtimeOnly(project(":rewrite-java-17")) } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/EnvironmentTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/EnvironmentTest.java index b40d45e2fcc..4bd163ca5c2 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/EnvironmentTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/EnvironmentTest.java @@ -50,7 +50,7 @@ void listStyles() { .findAny() .orElseThrow(); assertThat(intelliJStyle) - .as("Environment should be able to find and activate the IntelliJ style") + .as("Environment should be able to find and activate the IntelliJ IDEA style") .isNotNull(); } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java index f74c2547758..cf69fe19f4e 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java @@ -951,7 +951,7 @@ void fooBar() { .as("While there are outlier 3 and 9 space indents, the most prevalent indentation is 6") .isEqualTo(6); assertThat(tabsAndIndents.getContinuationIndent()) - .as("With no actual continuation indents to go off of, assume IntelliJ default of 2x the normal indent") + .as("With no actual continuation indents to go off of, assume IntelliJ IDEA default of 2x the normal indent") .isEqualTo(12); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java b/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java index 6a607f94a0a..3937062d307 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java @@ -62,7 +62,7 @@ public String getDisplayName() { public String getDescription() { return "Groups and orders import statements. If a [style has been defined](https://docs.openrewrite.org/concepts-explanations/styles), this recipe will order the imports " + "according to that style. If no style is detected, this recipe will default to ordering imports in " + - "the same way that IntelliJ does."; + "the same way that IntelliJ IDEA does."; } @Override diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java index 90ccd78e2f1..ff73aaadd8e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java @@ -916,9 +916,9 @@ public J.NewArray visitNewArray(J.NewArray newArray, P p) { J.NewArray n = super.visitNewArray(newArray, p); if (getCursor().getParent() != null && getCursor().getParent().firstEnclosing(J.class) instanceof J.Annotation) { /* - * IntelliJ setting + * IntelliJ IDEA setting * Spaces -> Within -> Annotation parentheses - * when enabled supercedes + * when enabled supersedes * Spaces -> Before left brace -> Annotation array initializer left brace */ if (!style.getWithin().getAnnotationParentheses()) { From 052d67ceff96055076c371515d54e3877473744e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Carpenter=E2=84=A2=EF=B8=8F?= <kevin@moderne.io> Date: Mon, 21 Aug 2023 16:25:12 -0300 Subject: [PATCH 164/447] Add utility to get original source file from LSS (#3495) --- .../main/java/org/openrewrite/LargeSourceSet.java | 9 +++++++++ .../internal/InMemoryLargeSourceSet.java | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/rewrite-core/src/main/java/org/openrewrite/LargeSourceSet.java b/rewrite-core/src/main/java/org/openrewrite/LargeSourceSet.java index e8d4e9be47f..20115e86aa7 100644 --- a/rewrite-core/src/main/java/org/openrewrite/LargeSourceSet.java +++ b/rewrite-core/src/main/java/org/openrewrite/LargeSourceSet.java @@ -17,6 +17,7 @@ import org.openrewrite.internal.lang.Nullable; +import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.function.UnaryOperator; @@ -74,4 +75,12 @@ default void afterCycle(boolean lastCycle) { * to the initial state. */ Changeset getChangeset(); + + /** + * Get the original source file, before any edits. Returns null if it is not present. + * @param sourcePath The path of the source file to retrieve. + * @return The original source file. Null if not present. + */ + @Nullable + SourceFile getBefore(Path sourcePath); } diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryLargeSourceSet.java b/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryLargeSourceSet.java index fb29d8de246..5c68c0797c6 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryLargeSourceSet.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/InMemoryLargeSourceSet.java @@ -21,6 +21,7 @@ import org.openrewrite.marker.Generated; import org.openrewrite.marker.RecipesThatMadeChanges; +import java.nio.file.Path; import java.util.*; import java.util.function.UnaryOperator; @@ -123,6 +124,18 @@ public Changeset getChangeset() { return new InMemoryChangeset(changes); } + @Nullable + @Override + public SourceFile getBefore(Path sourcePath) { + List<SourceFile> sourceFiles = getInitialState().ls; + for (SourceFile s : sourceFiles) { + if (s.getSourcePath().equals(sourcePath)) { + return s; + } + } + return null; + } + @RequiredArgsConstructor private static class InMemoryChangeset implements Changeset { final List<Result> change; From b6603bee4e864e54e04c4ceaf821545bb678ea79 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 21 Aug 2023 17:07:57 -0400 Subject: [PATCH 165/447] Fix class cast exceptio0n in MergeYamlVisitor --- .../src/main/java/org/openrewrite/yaml/MergeYamlVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYamlVisitor.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYamlVisitor.java index 0aee495e9d7..f5367cd2fe2 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYamlVisitor.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/MergeYamlVisitor.java @@ -76,7 +76,7 @@ public Yaml visitSequence(Yaml.Sequence existingSeq, P p) { @Override public Yaml visitMapping(Yaml.Mapping existingMapping, P p) { - if (scope.isScope(existingMapping)) { + if (scope.isScope(existingMapping) && incoming instanceof Yaml.Mapping) { return mergeMapping(existingMapping, (Yaml.Mapping) incoming, p, getCursor()); } return super.visitMapping(existingMapping, p); From b0036bbf92effa3984a6f1314768742b9d885e5a Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 21 Aug 2023 21:09:49 -0400 Subject: [PATCH 166/447] Polish --- .../main/java/org/openrewrite/FindParseFailures.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/FindParseFailures.java b/rewrite-core/src/main/java/org/openrewrite/FindParseFailures.java index dd408f46a88..8dcc2c3294a 100644 --- a/rewrite-core/src/main/java/org/openrewrite/FindParseFailures.java +++ b/rewrite-core/src/main/java/org/openrewrite/FindParseFailures.java @@ -32,14 +32,15 @@ public class FindParseFailures extends Recipe { @Nullable Integer maxSnippetLength; - @Option(displayName = "Parser name", - description = "Only display specified parser failures.", - required = false) + @Option(displayName = "Parser type", + description = "Only display failures from parsers with this fully qualified name.", + required = false, + example = "org.openrewrite.yaml.YamlParser") @Nullable String parserType; @Option(displayName = "Stack trace", - description = "Only mark specified stack traces.", + description = "Only mark stack traces with a message containing this text.", required = false) @Nullable String stackTrace; @@ -74,7 +75,7 @@ public Tree postVisit(Tree tree, ExecutionContext ctx) { } String snippet = tree instanceof SourceFile ? null : tree.printTrimmed(getCursor()); - if(snippet != null && maxSnippetLength != null && snippet.length() > maxSnippetLength) { + if (snippet != null && maxSnippetLength != null && snippet.length() > maxSnippetLength) { snippet = snippet.substring(0, maxSnippetLength); } From 27e6103f4e5a9117853e07dcf8ee00bd19d15e38 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 21 Aug 2023 21:36:17 -0400 Subject: [PATCH 167/447] Better error message on AddDependencyVisitor --- .../gradle/AddDependencyVisitor.java | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java index cfea91a4d83..dba4be114a7 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java @@ -16,8 +16,7 @@ package org.openrewrite.gradle; import lombok.RequiredArgsConstructor; -import org.openrewrite.Cursor; -import org.openrewrite.ExecutionContext; +import org.openrewrite.*; import org.openrewrite.gradle.internal.InsertDependencyComparator; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; @@ -36,7 +35,9 @@ import org.openrewrite.maven.table.MavenMetadataFailures; import org.openrewrite.maven.tree.*; import org.openrewrite.semver.*; +import org.openrewrite.tree.ParseError; +import java.text.ParseException; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -116,10 +117,10 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon if (g != cu) { String versionWithPattern = StringUtils.isBlank(resolvedVersion) || resolvedVersion.startsWith("$") ? null : resolvedVersion; GradleProject newGp = addDependency(gp, - gdc, - new GroupArtifactVersion(groupId, artifactId, versionWithPattern), - classifier, - ctx); + gdc, + new GroupArtifactVersion(groupId, artifactId, versionWithPattern), + classifier, + ctx); g = g.withMarkers(g.getMarkers().setByType(newGp)); } return g; @@ -129,11 +130,11 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionCon * Update the dependency model, adding the specified dependency to the specified configuration and all configurations * which extend from it. * - * @param gp marker with the current, pre-update dependency information + * @param gp marker with the current, pre-update dependency information * @param configuration the configuration to add the dependency to - * @param gav the group, artifact, and version of the dependency to add - * @param classifier the classifier of the dependency to add - * @param ctx context which will be used to download the pom for the dependency + * @param gav the group, artifact, and version of the dependency to add + * @param classifier the classifier of the dependency to add + * @param ctx context which will be used to download the pom for the dependency * @return a copy of gp with the dependency added */ static GradleProject addDependency( @@ -143,7 +144,7 @@ static GradleProject addDependency( @Nullable String classifier, ExecutionContext ctx) { try { - if(gav.getGroupId() == null || gav.getArtifactId() == null || configuration == null) { + if (gav.getGroupId() == null || gav.getArtifactId() == null || configuration == null) { return gp; } ResolvedGroupArtifactVersion resolvedGav; @@ -194,7 +195,7 @@ static GradleProject addDependency( return resolved; }), new ResolvedDependency(null, resolvedGav, newRequested, transitiveDependencies, - emptyList(), "jar", classifier, null, 0, null))); + emptyList(), "jar", classifier, null, 0, null))); } newNameToConfiguration.put(newGdc.getName(), newGdc); } @@ -238,18 +239,33 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu DependencyStyle style = autodetectDependencyStyle(body.getStatements()); if (style == DependencyStyle.String) { codeTemplate = "dependencies {\n" + - configuration + " \"" + groupId + ":" + artifactId + (resolvedVersion == null ? "" : ":" + resolvedVersion) + (resolvedVersion == null || classifier == null ? "" : ":" + classifier) + (extension == null ? "" : "@" + extension) + "\"" + - "\n}"; + configuration + " \"" + groupId + ":" + artifactId + (resolvedVersion == null ? "" : ":" + resolvedVersion) + (resolvedVersion == null || classifier == null ? "" : ":" + classifier) + (extension == null ? "" : "@" + extension) + "\"" + + "\n}"; } else { codeTemplate = "dependencies {\n" + - configuration + " group: \"" + groupId + "\", name: \"" + artifactId + "\"" + (resolvedVersion == null ? "" : ", version: \"" + resolvedVersion + "\"") + (classifier == null ? "" : ", classifier: \"" + classifier + "\"") + (extension == null ? "" : ", ext: \"" + extension + "\"") + - "\n}"; + configuration + " group: \"" + groupId + "\", name: \"" + artifactId + "\"" + (resolvedVersion == null ? "" : ", version: \"" + resolvedVersion + "\"") + (classifier == null ? "" : ", classifier: \"" + classifier + "\"") + (extension == null ? "" : ", ext: \"" + extension + "\"") + + "\n}"; } - J.MethodInvocation addDependencyInvocation = requireNonNull((J.MethodInvocation) ((J.Return) (((J.Block) ((J.Lambda) ((J.MethodInvocation) GRADLE_PARSER.parse(codeTemplate) + + ExecutionContext parseCtx = new InMemoryExecutionContext(); + parseCtx.putMessage(ExecutionContext.REQUIRE_PRINT_EQUALS_INPUT, false); + SourceFile parsed = GRADLE_PARSER.parse(parseCtx, codeTemplate) .findFirst() - .map(G.CompilationUnit.class::cast) - .orElseThrow(() -> new IllegalArgumentException("Could not parse as Gradle")) - .getStatements().get(0)).getArguments().get(0)).getBody()).getStatements().get(0))).getExpression()); + .orElseThrow(() -> new IllegalArgumentException("Could not parse as Gradle")); + + if (parsed instanceof ParseError) { + ParseError error = (ParseError) parsed; + if (error.getErroneous() != null) { + throw new IllegalStateException("Failed to parse the generated dependency because of parse-to-print idempotence."); + } else { + ParseExceptionResult ex = error.getMarkers().findFirst(ParseExceptionResult.class) + .orElseThrow(() -> new IllegalStateException("No ParseExceptionResult marker on parser failure.")); + throw new IllegalStateException(ex.getExceptionType() + ": " + ex.getMessage()); + } + } + + J.MethodInvocation addDependencyInvocation = requireNonNull((J.MethodInvocation) ((J.Return) (((J.Block) ((J.Lambda) ((J.MethodInvocation) + ((G.CompilationUnit) parsed).getStatements().get(0)).getArguments().get(0)).getBody()).getStatements().get(0))).getExpression()); addDependencyInvocation = autoFormat(addDependencyInvocation, ctx, new Cursor(getCursor(), body)); InsertDependencyComparator dependencyComparator = new InsertDependencyComparator(body.getStatements(), addDependencyInvocation); From 84fac75d1bd9ddb916287c415f2532375ee9ed1f Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 22 Aug 2023 08:30:06 +0200 Subject: [PATCH 168/447] Remove object allocations from `StringUtils#indexOfNextNonWhitespace()` This method is commonly used by the parser's `whitespace()` implementation and is thus on the critical path. The changes should make it about an order of magnitude faster. --- .../org/openrewrite/internal/StringUtils.java | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java index dc7fd5cc668..08c50b3d28e 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java @@ -263,7 +263,7 @@ public static String readFully(InputStream inputStream, Charset charset) { } byte[] bytes = bos.toByteArray(); - return new String(bytes, 0, bytes.length, charset); + return new String(bytes, charset); } catch (IOException e) { throw new UnsupportedOperationException(e); } @@ -713,36 +713,32 @@ public static int indexOfNextNonWhitespace(int cursor, String source) { boolean inMultiLineComment = false; boolean inSingleLineComment = false; - int delimIndex = cursor; - for (; delimIndex < source.length(); delimIndex++) { + int length = source.length(); + for (; cursor < length; cursor++) { + char current = source.charAt(cursor); if (inSingleLineComment) { - if (source.charAt(delimIndex) == '\n') { - inSingleLineComment = false; - } - } else { - if (source.length() > delimIndex + 1) { - switch (source.substring(delimIndex, delimIndex + 2)) { - case "//": - inSingleLineComment = true; - delimIndex++; - continue; - case "/*": - inMultiLineComment = true; - delimIndex++; - continue; - case "*/": - inMultiLineComment = false; - delimIndex++; - continue; - } + inSingleLineComment = current != '\n'; + continue; + } else if (length > cursor + 1) { + char next = source.charAt(cursor + 1); + if (current == '/' && next == '/') { + inSingleLineComment = true; + cursor++; + continue; + } else if (current == '/' && next == '*') { + inMultiLineComment = true; + cursor++; + continue; + } else if (current == '*' && next == '/') { + inMultiLineComment = false; + cursor++; + continue; } } - if (!inMultiLineComment && !inSingleLineComment) { - if (!Character.isWhitespace(source.substring(delimIndex, delimIndex + 1).charAt(0))) { - break; // found it! - } + if (!inMultiLineComment && !Character.isWhitespace(current)) { + break; // found it! } } - return delimIndex; + return cursor; } } From b78f4d50aa51c4b6baeb1cb7b990b9e4a33e5915 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 22 Aug 2023 14:44:21 +0200 Subject: [PATCH 169/447] Add handling of `continue` statement to Groovy parser --- .../groovy/GroovyParserVisitor.java | 13 +++++++++++ .../openrewrite/groovy/tree/ForLoopTest.java | 22 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index 51924e21c79..71ce2bb1879 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -1156,6 +1156,19 @@ public void visitConstructorCallExpression(ConstructorCallExpression ctor) { clazz, args, body, typeMapping.methodType(methodNode))); } + @Override + public void visitContinueStatement(ContinueStatement statement) { + queue.add(new J.Continue(randomId(), + sourceBefore("continue"), + Markers.EMPTY, + (statement.getLabel() == null) ? + null : + new J.Identifier(randomId(), + sourceBefore(statement.getLabel()), + Markers.EMPTY, emptyList(), statement.getLabel(), null, null)) + ); + } + @Override public void visitNotExpression(NotExpression expression) { Space fmt = sourceBefore("!"); diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ForLoopTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ForLoopTest.java index d06607649ac..9ca41926564 100755 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ForLoopTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ForLoopTest.java @@ -194,4 +194,26 @@ void forEachWithIn() { ) ); } + + @Test + void forWithContinue() { + rewriteRun( + groovy( + """ + for(int i in [1, 2, 3]) {continue} + """ + ) + ); + } + + @Test + void forWithLabeledContinue() { + rewriteRun( + groovy( + """ + f: for(int i in [1, 2, 3]) {continue f} + """ + ) + ); + } } From 44d49afb7fd76aeaf475e63d1f393d5d0ac22a9f Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 22 Aug 2023 17:03:03 +0200 Subject: [PATCH 170/447] `@Matches` and `@NotMatches` annotations used by Refaster-based recipes (#3488) * `@Matches` and `@NotMatches` annotations used by Refaster-based recipes * Add `@Incubating` --- .../openrewrite/java/template/Matcher.java | 25 +++++++++++++++++ .../openrewrite/java/template/Matches.java | 28 +++++++++++++++++++ .../openrewrite/java/template/NotMatches.java | 28 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/template/Matcher.java create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/template/Matches.java create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/template/NotMatches.java diff --git a/rewrite-java/src/main/java/org/openrewrite/java/template/Matcher.java b/rewrite-java/src/main/java/org/openrewrite/java/template/Matcher.java new file mode 100644 index 00000000000..c2c377fc8b8 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/template/Matcher.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.template; + +import org.openrewrite.Incubating; +import org.openrewrite.java.tree.J; + +@Incubating(since = "8.3.0") +@FunctionalInterface +public interface Matcher<T extends J> { + boolean matches(T t); +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/template/Matches.java b/rewrite-java/src/main/java/org/openrewrite/java/template/Matches.java new file mode 100644 index 00000000000..348333353a5 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/template/Matches.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.template; + +import org.openrewrite.Incubating; +import org.openrewrite.java.tree.Expression; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Incubating(since = "8.3.0") +@Target(ElementType.PARAMETER) +public @interface Matches { + Class<? extends Matcher<? super Expression>> value(); +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/template/NotMatches.java b/rewrite-java/src/main/java/org/openrewrite/java/template/NotMatches.java new file mode 100644 index 00000000000..17935203b7c --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/template/NotMatches.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.template; + +import org.openrewrite.Incubating; +import org.openrewrite.java.tree.Expression; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Incubating(since = "8.3.0") +@Target(ElementType.PARAMETER) +public @interface NotMatches { + Class<? extends Matcher<? super Expression>> value(); +} From 9fd38a8178aee1ea4b41b0bcabaf7e93d11844f5 Mon Sep 17 00:00:00 2001 From: Alexis Tual <atual@gradle.com> Date: Tue, 22 Aug 2023 18:23:27 +0200 Subject: [PATCH 171/447] Fix AddGradleEnterpriseMavenExtension optional version to get the latest (#3487) --- .../AddGradleEnterpriseMavenExtension.java | 47 +++++++++++++++---- ...AddGradleEnterpriseMavenExtensionTest.java | 30 ++++++++---- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java index 7603973c244..3a0bf982a70 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java @@ -26,9 +26,21 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.intellij.lang.annotations.Language; -import org.openrewrite.*; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.PathUtils; +import org.openrewrite.ScanningRecipe; +import org.openrewrite.SourceFile; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.internal.MavenXmlMapper; +import org.openrewrite.maven.tree.GroupArtifact; +import org.openrewrite.maven.tree.MavenMetadata; +import org.openrewrite.maven.tree.MavenRepository; +import org.openrewrite.semver.LatestRelease; +import org.openrewrite.semver.VersionComparator; import org.openrewrite.style.GeneralFormatStyle; import org.openrewrite.xml.AddToTagVisitor; import org.openrewrite.xml.XPathMatcher; @@ -38,7 +50,11 @@ import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; @@ -61,12 +77,6 @@ public class AddGradleEnterpriseMavenExtension extends ScanningRecipe<AddGradleE " <version>%s</version>\n" + "</extension>"; - @Language("xml") - private static final String ENTERPRISE_TAG_FORMAT_WITHOUT_VERSION = "<extension>\n" + - " <groupId>com.gradle</groupId>\n" + - " <artifactId>gradle-enterprise-maven-extension</artifactId>\n" + - "</extension>"; - @Option(displayName = "Extension version", description = "A maven-compatible version number to select the gradle-enterprise-maven-extension version.", example = "1.17.4") @@ -127,7 +137,7 @@ public enum PublishCriteria { @Override public String getDisplayName() { - return "Add Gradle Enterprise Maven extension to maven projects"; + return "Add Gradle Enterprise Maven extension"; } @Override @@ -325,10 +335,27 @@ public Xml.Tag visitTag(Xml.Tag tag, AtomicBoolean found) { */ private Xml.Document addEnterpriseExtension(Xml.Document extensionsXml, ExecutionContext ctx) { @Language("xml") - String tagSource = version != null ? String.format(ENTERPRISE_TAG_FORMAT, version) : ENTERPRISE_TAG_FORMAT_WITHOUT_VERSION; + String tagSource = version != null ? String.format(ENTERPRISE_TAG_FORMAT, version) : String.format(ENTERPRISE_TAG_FORMAT, getLatestVersion(ctx)); AddToTagVisitor<ExecutionContext> addToTagVisitor = new AddToTagVisitor<>( extensionsXml.getRoot(), Xml.Tag.build(tagSource)); return (Xml.Document) addToTagVisitor.visitNonNull(extensionsXml, ctx); } + + private String getLatestVersion(ExecutionContext ctx) { + MavenPomDownloader pomDownloader = new MavenPomDownloader(Collections.emptyMap(), ctx, null, null); + VersionComparator versionComparator = new LatestRelease(null); + GroupArtifact gradleEnterpriseExtension = new GroupArtifact("com.gradle", "gradle-enterprise-maven-extension"); + try { + MavenMetadata extensionMetadata = pomDownloader.downloadMetadata(gradleEnterpriseExtension, null, Collections.singletonList(MavenRepository.MAVEN_CENTRAL)); + return extensionMetadata.getVersioning() + .getVersions() + .stream() + .filter(v -> versionComparator.isValid(null, v)) + .max((v1, v2) -> versionComparator.compare(null, v1, v2)) + .orElseThrow(() -> new IllegalStateException("Expected to find at least one Gradle Enterprise Maven extension version to select from.")); + } catch (MavenDownloadingException e) { + throw new IllegalStateException("Could not download Maven metadata", e); + } + } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtensionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtensionTest.java index 6d353c770b4..06083f5ece3 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtensionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtensionTest.java @@ -21,6 +21,10 @@ import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpecs; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.maven.Assertions.pomXml; import static org.openrewrite.xml.Assertions.xml; @@ -127,16 +131,22 @@ void noVersionSpecified() { POM_XML_SOURCE_SPEC, xml( null, - """ - <?xml version="1.0" encoding="UTF-8"?> - <extensions> - <extension> - <groupId>com.gradle</groupId> - <artifactId>gradle-enterprise-maven-extension</artifactId> - </extension> - </extensions> - """, - spec -> spec.path(".mvn/extensions.xml") + spec -> spec.path(".mvn/extensions.xml").after(after -> { + Matcher versionMatcher = Pattern.compile("<version>(.*)</version>").matcher(after); + assertThat(versionMatcher.find()).isTrue(); + String version = versionMatcher.group(1); + assertThat(version).isNotBlank(); + return """ + <?xml version="1.0" encoding="UTF-8"?> + <extensions> + <extension> + <groupId>com.gradle</groupId> + <artifactId>gradle-enterprise-maven-extension</artifactId> + <version>%s</version> + </extension> + </extensions> + """.formatted(version); + }) ), xml( null, From 2298bd747231730a1aa4b02374d3c13af4468ae5 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 22 Aug 2023 18:54:06 +0200 Subject: [PATCH 172/447] Handle ternary condition in `UnnecessaryParenthesesVisitor` --- .../cleanup/UnnecessaryParenthesesTest.java | 23 +++++++++++++++++++ .../UnnecessaryParenthesesVisitor.java | 9 ++++++++ 2 files changed, 32 insertions(+) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java index e1e40f6c567..3ea94ee4fda 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java @@ -898,4 +898,27 @@ void test(String s) { ) ); } + + @Test + @SuppressWarnings("SimplifiableConditionalExpression") + void ternaryCondition() { + rewriteRun( + java( + """ + class Test { + boolean test(String s) { + return (s == null) ? true : false; + } + } + """, + """ + class Test { + boolean test(String s) { + return s == null ? true : false; + } + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java index 9747bbdb5c6..cf6bbc633a3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java @@ -196,4 +196,13 @@ public J visitForControl(J.ForLoop.Control control, ExecutionContext ctx) { } return fc; } + + @Override + public J visitTernary(J.Ternary ternary, ExecutionContext ctx) { + J.Ternary te = (J.Ternary) super.visitTernary(ternary, ctx); + if (te.getCondition() instanceof J.Parentheses) { + te = (J.Ternary) new UnwrapParentheses<>((J.Parentheses<?>) te.getCondition()).visitNonNull(te, ctx, getCursor().getParentOrThrow()); + } + return te; + } } \ No newline at end of file From 4a2e1908d1d443e7bc7262ae9a8bff7174a8cf9f Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 22 Aug 2023 10:16:48 -0700 Subject: [PATCH 173/447] Add comment explaining context-sensitivity in templating --- .../java/org/openrewrite/java/JavaTemplate.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java index 61fcb34a7a4..0c1b994605a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java @@ -137,6 +137,20 @@ public static class Builder { this.code = code.trim(); } + /** + * A template snippet is context-sensitive when it refers to the class, variables, methods, or other symbols + * visible from its insertion scope. When a template is completely self-contained, it is not context-sensitive. + * Context-free template snippets can be cached, since it does not matter where the resulting LST elements will + * be inserted. Since the LST elements in a context-sensitive snippet vary depending on where they are inserted + * the resulting LST elements cannot be reused between different insertion points and are not cached. + * <p> + * An example of a context-free snippet might be something like this, to be used as a local variable declaration: + * <code>int i = 1</code>; + * <p> + * An example of a context-sensitive snippet is: + * <code>int i = a</code>; + * This cannot be made sense of without the surrounding scope which includes the declaration of "a". + */ public Builder contextSensitive() { this.contextSensitive = true; return this; From e473e323a3b6e8beb9b40bdbe5216d5905dc056d Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 22 Aug 2023 18:04:39 -0700 Subject: [PATCH 174/447] Add column to RecipeRunStates recording how many files were changed by a recipe run (#3483) * Add column to RecipeRunStates recording how many files were changed by a recipe run * Update rewrite-core/src/main/java/org/openrewrite/table/RecipeRunStats.java Co-authored-by: Knut Wannheden <knut@moderne.io> * Add CobolCli LstProvenance type --------- Co-authored-by: Knut Wannheden <knut@moderne.io> --- .../java/org/openrewrite/RecipeScheduler.java | 1 + .../org/openrewrite/table/RecipeRunStats.java | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java b/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java index 57b9b90e565..e6b1a78c01a 100644 --- a/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java +++ b/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java @@ -241,6 +241,7 @@ public LargeSourceSet editSources(LargeSourceSet sourceSet, int cycle) { // that later fails to apply on a freshly cloned repository return sourceFile; } + recipeRunStats.recordSourceFileChanged(sourceFile, after); } } catch (Throwable t) { after = handleError(recipe, sourceFile, after, t); diff --git a/rewrite-core/src/main/java/org/openrewrite/table/RecipeRunStats.java b/rewrite-core/src/main/java/org/openrewrite/table/RecipeRunStats.java index 5b2ef5c2145..c70c4b413f7 100644 --- a/rewrite-core/src/main/java/org/openrewrite/table/RecipeRunStats.java +++ b/rewrite-core/src/main/java/org/openrewrite/table/RecipeRunStats.java @@ -22,8 +22,11 @@ import org.openrewrite.*; import org.openrewrite.internal.lang.Nullable; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -32,6 +35,7 @@ public class RecipeRunStats extends DataTable<RecipeRunStats.Row> { private final MeterRegistry registry = new SimpleMeterRegistry(); + private final Set<Path> sourceFileChanged = new HashSet<>(); public RecipeRunStats(Recipe recipe) { super(recipe, @@ -39,6 +43,14 @@ public RecipeRunStats(Recipe recipe) { "Statistics used in analyzing the performance of recipes."); } + public void recordSourceFileChanged(@Nullable SourceFile before, @Nullable SourceFile after) { + if(after != null) { + sourceFileChanged.add(after.getSourcePath()); + } else if(before != null) { + sourceFileChanged.add(before.getSourcePath()); + } + } + public void recordScan(Recipe recipe, Callable<SourceFile> scan) throws Exception { Timer.builder("rewrite.recipe.scan") .tag("name", recipe.getName()) @@ -63,6 +75,7 @@ public void flush(ExecutionContext ctx) { Row row = new Row( recipeName, Long.valueOf(editor.count()).intValue(), + sourceFileChanged.size(), scanner == null ? 0 : (long) scanner.totalTime(TimeUnit.NANOSECONDS), scanner == null ? 0 : scanner.takeSnapshot().percentileValues()[0].percentile(), scanner == null ? 0 : (long) scanner.max(TimeUnit.NANOSECONDS), @@ -85,10 +98,14 @@ public static class Row { description = "The recipe whose stats are being measured both individually and cumulatively.") String recipe; - @Column(displayName = "Source files", + @Column(displayName = "Source file count", description = "The number of source files the recipe ran over.") Integer sourceFiles; + @Column(displayName = "Source file changed count", + description = "The number of source files which were changed in the recipe run. Includes files created, deleted, and edited.") + Integer sourceFilesChanged; + @Column(displayName = "Cumulative scanning time", description = "The total time spent across the scanning phase of this recipe.") Long scanTotalTime; From 57d8cfff3e1cf0bab29ba2959cb5411ce9dbe90a Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 22 Aug 2023 18:49:30 -0700 Subject: [PATCH 175/447] Add disclaimer about non-removal of unused imports in sources with missing types --- .../org/openrewrite/java/RemoveUnusedImports.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java index f947cbfba60..ed3b9ab71af 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java @@ -26,7 +26,6 @@ import java.time.Duration; import java.util.*; -import java.util.function.Predicate; import static java.util.Collections.emptySet; import static org.openrewrite.java.style.ImportLayoutStyle.isPackageAlwaysFolded; @@ -45,7 +44,9 @@ public String getDisplayName() { @Override public String getDescription() { - return "Remove imports for types that are not referenced."; + return "Remove imports for types that are not referenced. As a precaution against incorrect changes no imports " + + "will be removed from any source where unknown types are referenced. The most common cause of unknown " + + "types is the use of annotation processors not supported by OpenRewrite, such as lombok."; } @Override @@ -287,12 +288,6 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon } } - private static String packageKey(String packageName, String className) { - return className.contains(".") ? - packageName + "." + className.substring(0, className.lastIndexOf('.')) : - packageName; - } - private static class ImportUsage { final List<JRightPadded<J.Import>> imports = new ArrayList<>(); boolean used = true; From 68aa7e9bf68b695e470b312da6b18bd96b27ba74 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 23 Aug 2023 09:12:09 +0200 Subject: [PATCH 176/447] Fix `JavaTemplate` when generating statement inside `if` block --- .../openrewrite/java/JavaTemplateTest.java | 58 +++++++++++++++++-- .../BlockStatementTemplateGenerator.java | 7 +-- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest.java index 4bab358dd35..df1bfcf0288 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateTest.java @@ -16,13 +16,11 @@ package org.openrewrite.java; import org.junit.jupiter.api.Test; +import org.openrewrite.Cursor; import org.openrewrite.DocumentExample; import org.openrewrite.ExecutionContext; import org.openrewrite.Issue; -import org.openrewrite.java.tree.Expression; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.java.tree.*; import org.openrewrite.test.RewriteTest; import static org.assertj.core.api.Assertions.assertThat; @@ -991,4 +989,56 @@ class T { ) ); } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test + void addStatementInIfBlock() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() { + final MethodMatcher lowerCaseMatcher = new MethodMatcher("java.lang.String toLowerCase()"); @Override + public J.Block visitBlock(J.Block block, ExecutionContext ctx) { + J.Block newBlock = super.visitBlock(block, ctx); + if (newBlock.getStatements().stream().noneMatch(J.VariableDeclarations.class::isInstance)) { + return newBlock; + } + for (Statement statement : newBlock.getStatements()) { + if (statement instanceof J.MethodInvocation && lowerCaseMatcher.matches((J.MethodInvocation) statement)) { + return newBlock; + } + } + Statement lastStatement = newBlock.getStatements().get(newBlock.getStatements().size() - 1); + String code = "s.toLowerCase();"; + return JavaTemplate + .builder(code) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion()) + .build() + .apply(new Cursor(getCursor().getParent(), newBlock), lastStatement.getCoordinates().before()); + } + })), + java( + """ + class Test { + void test() { + if (true) { + String s = "Hello world!"; + System.out.println(s); + } + } + } + """, + """ + class Test { + void test() { + if (true) { + String s = "Hello world!"; + s.toLowerCase(); + System.out.println(s); + } + } + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java index 1fb335e2eae..18fe6af8b36 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java @@ -300,18 +300,13 @@ private void contextTemplate(Cursor cursor, J prior, StringBuilder before, Strin .withLeadingAnnotations(emptyList()) .withPrefix(Space.EMPTY) .printTrimmed(cursor).trim() + '{'); - } else if (parent instanceof J.Block || parent instanceof J.Lambda || parent instanceof J.Label || parent instanceof Loop) { + } else { J.Block b = (J.Block) j; // variable declarations up to the point of insertion addLeadingVariableDeclarations(cursor, prior, b, before, insertionPoint); before.insert(0, "{\n"); - if (b.isStatic()) { - before.insert(0, "static"); - } - } else { - before.insert(0, "{\n"); } if (prior == insertionPoint && prior instanceof Expression) { From 4aa68ab418b24d81e7c74f4a4bc29f8a6f7f61ed Mon Sep 17 00:00:00 2001 From: Alexis Tual <atual@gradle.com> Date: Wed, 23 Aug 2023 12:07:48 +0200 Subject: [PATCH 177/447] Set AddGradleEnterpriseMavenExtension version attribute as optional (#3497) --- .../org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java index 3a0bf982a70..0c4d0fb6754 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java @@ -79,6 +79,7 @@ public class AddGradleEnterpriseMavenExtension extends ScanningRecipe<AddGradleE @Option(displayName = "Extension version", description = "A maven-compatible version number to select the gradle-enterprise-maven-extension version.", + required = false, example = "1.17.4") @Nullable String version; From 3278d283640b45d21f262ac0844c16998976ac87 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Thu, 24 Aug 2023 10:27:31 +0200 Subject: [PATCH 178/447] Use tag groupId and artifactId to change version (#3498) * use tag groupId and artifactId to change version * updated descriptions * Added test for properties --- .../maven/UpgradePluginVersion.java | 13 +- .../maven/UpgradePluginVersionTest.java | 137 ++++++++++++++++++ 2 files changed, 146 insertions(+), 4 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradePluginVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradePluginVersion.java index 85c58b5cf0c..99511151f5d 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradePluginVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradePluginVersion.java @@ -45,17 +45,22 @@ public class UpgradePluginVersion extends Recipe { MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this); @Option(displayName = "Group", - description = "The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'.", + description = "The first part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'. " + + "Supports globs.", example = "org.openrewrite.maven") String groupId; @Option(displayName = "Artifact", - description = "The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'.", + description = "The second part of a dependency coordinate 'org.openrewrite.maven:rewrite-maven-plugin:VERSION'. " + + "Supports globs.", example = "rewrite-maven-plugin") String artifactId; @Option(displayName = "New version", - description = "An exact version number or node-style semver selector used to select the version number.", + description = "An exact version number or node-style semver selector used to select the version number. " + + "You can also use `latest.release` for the latest available version and `latest.patch` if " + + "the current version is a valid semantic version. For more details, you can look at the documentation " + + "page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors)", example = "29.X") String newVersion; @@ -122,7 +127,7 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { assert tagGroupId != null; assert tagArtifactId != null; findNewerDependencyVersion(tagGroupId, tagArtifactId, versionLookup, ctx).ifPresent(newer -> { - ChangePluginVersionVisitor changeDependencyVersion = new ChangePluginVersionVisitor(groupId, artifactId, newer); + ChangePluginVersionVisitor changeDependencyVersion = new ChangePluginVersionVisitor(tagGroupId, tagArtifactId, newer); doAfterVisit(changeDependencyVersion); }); } catch (MavenDownloadingException e) { diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradePluginVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradePluginVersionTest.java index 23df369c217..e9acd9edbb8 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradePluginVersionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradePluginVersionTest.java @@ -468,4 +468,141 @@ void upgradePluginInParent() { ) ); } + + @Test + void upgradeMultiplePluginsWithDifferentVersions() { + rewriteRun( + spec -> spec.recipe(new UpgradePluginVersion( + "*", + "*", + "2.4.x", + null, + null + )), + pomXml( + """ + <project> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.openrewrite.example</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + + <build> + <plugins> + <plugin> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-maven-plugin</artifactId> + <version>1.13.3.Final</version> + </plugin> + <plugin> + <groupId>org.openrewrite.maven</groupId> + <artifactId>rewrite-maven-plugin</artifactId> + <version>1.0.0</version> + </plugin> + </plugins> + </build> + </project> + """, + """ + <project> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.openrewrite.example</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + + <build> + <plugins> + <plugin> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-maven-plugin</artifactId> + <version>2.4.2.Final</version> + </plugin> + <plugin> + <groupId>org.openrewrite.maven</groupId> + <artifactId>rewrite-maven-plugin</artifactId> + <version>2.4.13</version> + </plugin> + </plugins> + </build> + </project> + """ + ) + ); + } + + @Test + void upgradePluginsVersionOnProperties() { + rewriteRun( + spec -> spec.recipe(new UpgradePluginVersion( + "*", + "*", + "2.4.x", + null, + null + )), + pomXml( + """ + <project> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.openrewrite.example</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + + <properties> + <quarkus-maven-plugin.version>1.13.3.Final</quarkus-maven-plugin.version> + <rewrite-maven-plugin.version>1.0.0</rewrite-maven-plugin.version> + </properties> + + <build> + <plugins> + <plugin> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-maven-plugin</artifactId> + <version>${quarkus-maven-plugin.version}</version> + </plugin> + <plugin> + <groupId>org.openrewrite.maven</groupId> + <artifactId>rewrite-maven-plugin</artifactId> + <version>${rewrite-maven-plugin.version}</version> + </plugin> + </plugins> + </build> + </project> + """, + """ + <project> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.openrewrite.example</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + + <properties> + <quarkus-maven-plugin.version>2.4.2.Final</quarkus-maven-plugin.version> + <rewrite-maven-plugin.version>2.4.13</rewrite-maven-plugin.version> + </properties> + + <build> + <plugins> + <plugin> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-maven-plugin</artifactId> + <version>${quarkus-maven-plugin.version}</version> + </plugin> + <plugin> + <groupId>org.openrewrite.maven</groupId> + <artifactId>rewrite-maven-plugin</artifactId> + <version>${rewrite-maven-plugin.version}</version> + </plugin> + </plugins> + </build> + </project> + """ + ) + ); + } + } From 744baf2b0d2a733453e5eccddb05d37ba441187c Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Thu, 24 Aug 2023 15:06:30 +0200 Subject: [PATCH 179/447] Upgrade Maven plugin dependencies (#3481) * Upgrade Maven plugin dependencies * Update UpgradeDependencyVersionTest.java Drop qualifier * update plugin dependencies * newline --------- Co-authored-by: joanvr <joan@moderne.io> --- .../org/openrewrite/maven/MavenVisitor.java | 10 ++ .../maven/UpgradeDependencyVersion.java | 36 +++++- .../maven/UpgradeDependencyVersionTest.java | 121 ++++++++++++++++++ 3 files changed, 163 insertions(+), 4 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenVisitor.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenVisitor.java index 65b501275a5..df99125fe87 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenVisitor.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenVisitor.java @@ -38,6 +38,7 @@ @SuppressWarnings("NotNullFieldNotInitialized") public class MavenVisitor<P> extends XmlVisitor<P> { static final XPathMatcher DEPENDENCY_MATCHER = new XPathMatcher("/project/dependencies/dependency"); + static final XPathMatcher PLUGIN_DEPENDENCY_MATCHER = new XPathMatcher("/project/*/plugins/plugin/dependencies/dependency"); static final XPathMatcher MANAGED_DEPENDENCY_MATCHER = new XPathMatcher("/project/dependencyManagement/dependencies/dependency"); static final XPathMatcher PROPERTY_MATCHER = new XPathMatcher("/project/properties/*"); static final XPathMatcher PLUGIN_MATCHER = new XPathMatcher("/project/*/plugins/plugin"); @@ -120,6 +121,15 @@ public boolean isDependencyTag(String groupId, String artifactId) { return false; } + public boolean isPluginDependencyTag(String groupId, String artifactId) { + if (!PLUGIN_DEPENDENCY_MATCHER.matches(getCursor())) { + return false; + } + Xml.Tag tag = getCursor().getValue(); + return matchesGlob(tag.getChildValue("groupId").orElse(null), groupId) && + matchesGlob(tag.getChildValue("artifactId").orElse(null), artifactId); + } + public boolean isManagedDependencyTag() { return MANAGED_DEPENDENCY_MATCHER.matches(getCursor()); } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java index c8ee8cd4dec..0aafc3d670d 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java @@ -142,6 +142,8 @@ public TreeVisitor<?, ExecutionContext> getVisitor(Set<GroupArtifact> projectArt assert versionComparator != null; return new MavenIsoVisitor<ExecutionContext>() { + private final XPathMatcher PROJECT_MATCHER = new XPathMatcher("/project"); + @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { Xml.Tag t = super.visitTag(tag, ctx); @@ -158,6 +160,8 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { maybeUpdateModel(); } } + } else if (isPluginDependencyTag(groupId, artifactId)) { + t = upgradePluginDependency(ctx, t); } } catch (MavenDownloadingException e) { return e.warn(t); @@ -271,6 +275,34 @@ private TreeVisitor<Xml, ExecutionContext> upgradeManagedDependency(Xml.Tag tag, return null; } + private Xml.Tag upgradePluginDependency(ExecutionContext ctx, Xml.Tag t) throws MavenDownloadingException { + String groupId = t.getChildValue("groupId").orElse(null); + String artifactId = t.getChildValue("artifactId").orElse(null); + String version = t.getChildValue("version").orElse(null); + if (groupId != null && artifactId != null && version != null) { + String newerVersion = findNewerVersion(groupId, artifactId, resolveVersion(version), ctx); + if (newerVersion != null) { + if (version.startsWith("${") && !implicitlyDefinedVersionProperties.contains(version)) { + doAfterVisit(new ChangePropertyValue(version.substring(2, version.length() - 1), newerVersion, overrideManagedVersion, false).getVisitor()); + } else { + Optional<Xml.Tag> versionTag = t.getChild("version"); + assert versionTag.isPresent(); + t = (Xml.Tag) new ChangeTagValueVisitor<>(versionTag.get(), newerVersion).visitNonNull(t, 0, getCursor().getParentOrThrow()); + } + } + } + return t; + } + + private String resolveVersion(String version) { + if (version.startsWith("${") && !implicitlyDefinedVersionProperties.contains(version)) { + Map<String, String> properties = getResolutionResult().getPom().getProperties(); + String property = version.substring(2, version.length() - 1); + return properties.getOrDefault(property, version); + } + return version; + } + @Nullable public TreeVisitor<Xml, ExecutionContext> upgradeVersion(ExecutionContext ctx, Xml.Tag tag, @Nullable String requestedVersion, String groupId, String artifactId, String version2) throws MavenDownloadingException { String newerVersion = findNewerVersion(groupId, artifactId, version2, ctx); @@ -320,8 +352,4 @@ private String findNewerVersion(String groupId, String artifactId, String versio } }; } - - private static final XPathMatcher PROJECT_MATCHER = new XPathMatcher("/project"); - private static final String DEPENDENCIES_XPATH = "/project/dependencies"; - private static final String DEPENDENCY_MANAGEMENT_XPATH = "/project/dependencyManagement"; } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeDependencyVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeDependencyVersionTest.java index 5e8499ca688..d186ed5ba0b 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeDependencyVersionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeDependencyVersionTest.java @@ -523,6 +523,127 @@ void upgradeGuava() { ); } + @Test + void upgradePluginDependencies() { + rewriteRun( + spec -> spec.recipe(new UpgradeDependencyVersion("org.openrewrite.recipe", "rewrite-spring", "5.0.6", "", null, null)), + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.openrewrite.maven</groupId> + <artifactId>rewrite-maven-plugin</artifactId> + <version>5.4.1</version> + <dependencies> + <dependency> + <groupId>org.openrewrite.recipe</groupId> + <artifactId>rewrite-spring</artifactId> + <version>5.0.5</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> + </project> + """, + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.openrewrite.maven</groupId> + <artifactId>rewrite-maven-plugin</artifactId> + <version>5.4.1</version> + <dependencies> + <dependency> + <groupId>org.openrewrite.recipe</groupId> + <artifactId>rewrite-spring</artifactId> + <version>5.0.6</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> + </project> + """ + ) + ); + } + + @Test + void upgradePluginDependenciesOnProperty() { + rewriteRun( + // Using latest.patch to validate the version property resolution, since it's needed for matching the valid patch. + spec -> spec.recipe(new UpgradeDependencyVersion("org.openrewrite.recipe", "rewrite-spring", "latest.patch", "", null, null)), + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + + <properties> + <rewrite-spring.version>4.33.0</rewrite-spring.version> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.openrewrite.maven</groupId> + <artifactId>rewrite-maven-plugin</artifactId> + <version>5.4.1</version> + <dependencies> + <dependency> + <groupId>org.openrewrite.recipe</groupId> + <artifactId>rewrite-spring</artifactId> + <version>${rewrite-spring.version}</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> + </project> + """, + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + + <properties> + <rewrite-spring.version>4.33.2</rewrite-spring.version> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.openrewrite.maven</groupId> + <artifactId>rewrite-maven-plugin</artifactId> + <version>5.4.1</version> + <dependencies> + <dependency> + <groupId>org.openrewrite.recipe</groupId> + <artifactId>rewrite-spring</artifactId> + <version>${rewrite-spring.version}</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> + </project> + """ + ) + ); + } + @Test @Issue("https://github.com/openrewrite/rewrite/issues/1334") void upgradeGuavaWithExplicitBlankVersionPattern() { From 83b75bc2432fd4319e79184d00830649696daa10 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 24 Aug 2023 21:56:16 +0200 Subject: [PATCH 180/447] Make `Space#flyweights` synchronized once again (#3499) Using a `HashMap` (or `WeakHashMap`) concurrently can cause infinite loops. At the same time also move the string allocation to the value provider for the `computeIfAbsent()` call to save some object allocations. --- .../src/main/java/org/openrewrite/hcl/tree/Space.java | 9 +++------ .../src/main/java/org/openrewrite/java/tree/Space.java | 7 ++----- .../src/main/java/org/openrewrite/json/tree/Space.java | 9 +++------ .../main/java/org/openrewrite/protobuf/tree/Space.java | 9 +++------ .../src/main/java/org/openrewrite/toml/tree/Space.java | 7 ++++--- 5 files changed, 15 insertions(+), 26 deletions(-) diff --git a/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Space.java b/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Space.java index bfc823cbcc6..e73b1b48e37 100644 --- a/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Space.java +++ b/rewrite-hcl/src/main/java/org/openrewrite/hcl/tree/Space.java @@ -23,10 +23,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.WeakHashMap; +import java.util.*; import static java.util.Collections.emptyList; @@ -49,7 +46,7 @@ public class Space { * e.g.: a single space between keywords, or the common indentation of every line in a block. * So use flyweights to avoid storing many instances of functionally identical spaces */ - private static final Map<String, Space> flyweights = new WeakHashMap<>(); + private static final Map<String, Space> flyweights = Collections.synchronizedMap(new WeakHashMap<>()); private Space(@Nullable String whitespace, List<Comment> comments) { this.comments = comments; @@ -63,7 +60,7 @@ public static Space build(@Nullable String whitespace, List<Comment> comments) { return Space.EMPTY; } else if (whitespace.length() <= 100) { //noinspection StringOperationCanBeSimplified - return flyweights.computeIfAbsent(new String(whitespace), k -> new Space(whitespace, comments)); + return flyweights.computeIfAbsent(whitespace, k -> new Space(new String(whitespace), comments)); } } return new Space(whitespace, comments); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index fefa198a56b..bc45bce6205 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -20,10 +20,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.WeakHashMap; +import java.util.*; import static java.util.Collections.emptyList; @@ -46,7 +43,7 @@ public class Space { * e.g.: a single space between keywords, or the common indentation of every line in a block. * So use flyweights to avoid storing many instances of functionally identical spaces */ - private static final Map<String, Space> flyweights = new WeakHashMap<>(); + private static final Map<String, Space> flyweights = Collections.synchronizedMap(new WeakHashMap<>()); static { flyweights.put(" ", SINGLE_SPACE); diff --git a/rewrite-json/src/main/java/org/openrewrite/json/tree/Space.java b/rewrite-json/src/main/java/org/openrewrite/json/tree/Space.java index bb1a1347aaa..4fa9fb1aea3 100644 --- a/rewrite-json/src/main/java/org/openrewrite/json/tree/Space.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/tree/Space.java @@ -22,10 +22,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.WeakHashMap; +import java.util.*; import static java.util.Collections.emptyList; @@ -48,7 +45,7 @@ public class Space { * e.g.: a single space between keywords, or the common indentation of every line in a block. * So use flyweights to avoid storing many instances of functionally identical spaces */ - private static final Map<String, Space> flyweights = new WeakHashMap<>(); + private static final Map<String, Space> flyweights = Collections.synchronizedMap(new WeakHashMap<>()); private Space(@Nullable String whitespace, List<Comment> comments) { this.comments = comments; @@ -62,7 +59,7 @@ public static Space build(@Nullable String whitespace, List<Comment> comments) { return Space.EMPTY; } else if (whitespace.length() <= 100) { //noinspection StringOperationCanBeSimplified - return flyweights.computeIfAbsent(new String(whitespace), k -> new Space(whitespace, comments)); + return flyweights.computeIfAbsent(whitespace, k -> new Space(new String(whitespace), comments)); } } return new Space(whitespace, comments); diff --git a/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/tree/Space.java b/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/tree/Space.java index 138722b5e09..feda9317718 100644 --- a/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/tree/Space.java +++ b/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/tree/Space.java @@ -22,10 +22,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.WeakHashMap; +import java.util.*; import static java.util.Collections.emptyList; @@ -48,7 +45,7 @@ public class Space { * e.g.: a single space between keywords, or the common indentation of every line in a block. * So use flyweights to avoid storing many instances of functionally identical spaces */ - private static final Map<String, Space> flyweights = new WeakHashMap<>(); + private static final Map<String, Space> flyweights = Collections.synchronizedMap(new WeakHashMap<>()); private Space(@Nullable String whitespace, List<Comment> comments) { this.comments = comments; @@ -62,7 +59,7 @@ public static Space build(@Nullable String whitespace, List<Comment> comments) { return Space.EMPTY; } else if (whitespace.length() <= 100) { //noinspection StringOperationCanBeSimplified - return flyweights.computeIfAbsent(new String(whitespace), k -> new Space(whitespace, comments)); + return flyweights.computeIfAbsent(whitespace, k -> new Space(new String(whitespace), comments)); } } return new Space(whitespace, comments); diff --git a/tools/language-parser-builder/src/main/java/org/openrewrite/toml/tree/Space.java b/tools/language-parser-builder/src/main/java/org/openrewrite/toml/tree/Space.java index ea577d76ba9..257d52fe51f 100644 --- a/tools/language-parser-builder/src/main/java/org/openrewrite/toml/tree/Space.java +++ b/tools/language-parser-builder/src/main/java/org/openrewrite/toml/tree/Space.java @@ -21,6 +21,7 @@ import lombok.EqualsAndHashCode; import org.openrewrite.internal.lang.Nullable; +import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; @@ -40,7 +41,7 @@ public class Space { * e.g.: a single space between keywords, or the common indentation of every line in a block. * So use flyweights to avoid storing many instances of functionally identical spaces */ - private static final Map<String, Space> flyweights = new WeakHashMap<>(); + private static final Map<String, Space> flyweights = Collections.synchronizedMap(new WeakHashMap<>()); private Space(@Nullable String whitespace) { this.whitespace = whitespace == null || whitespace.isEmpty() ? null : whitespace; @@ -53,10 +54,10 @@ public static Space build(@Nullable String whitespace) { return Space.EMPTY; } else if (whitespace.length() <= 100) { //noinspection StringOperationCanBeSimplified - return flyweights.computeIfAbsent(new String(whitespace), k -> new Space(whitespace, comments)); + return flyweights.computeIfAbsent(whitespace, k -> new Space(new String(whitespace), comments)); } } - return flyweights.computeIfAbsent(whitespace, k -> new Space(whitespace)); + return new Space(whitespace, comments); } public String getIndent() { From 88c15ea96cb744d12149279cee60ff045639d02b Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 25 Aug 2023 13:28:23 +0200 Subject: [PATCH 181/447] Deal with missing timestamps in Maven snapshot metadata It appears that Maven snapshot versions don't always have a timestamp, even when served from a repository. Fixes: openrewrite/rewrite-spring#427 --- .../org/openrewrite/maven/internal/MavenPomDownloader.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index 7c94fac0671..18b4a2c9ef7 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -420,9 +420,10 @@ private List<String> mergeVersions(List<String> versions1, List<String> versions @Nullable private MavenMetadata.Snapshot maxSnapshot(@Nullable MavenMetadata.Snapshot s1, @Nullable MavenMetadata.Snapshot s2) { - if (s1 == null) { + // apparently the snapshot timestamp is not always present in the metadata + if (s1 == null || s1.getTimestamp() == null) { return s2; - } else if (s2 == null) { + } else if (s2 == null || s2.getTimestamp() == null) { return s1; } else { return (s1.getTimestamp().compareTo(s2.getTimestamp())) >= 0 ? s1 : s2; From 183b3ec611eb7482ec50025162bd7faf15aa66b3 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Fri, 25 Aug 2023 15:41:47 -0400 Subject: [PATCH 182/447] No need to time id generation anymore --- rewrite-core/src/main/java/org/openrewrite/Tree.java | 2 +- .../src/main/java/org/openrewrite/internal/MetricsHelper.java | 2 -- .../src/test/java/org/openrewrite/yaml/YamlParserTest.java | 4 +++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/Tree.java b/rewrite-core/src/main/java/org/openrewrite/Tree.java index c2a2e933aa5..2015ae8c4b7 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Tree.java +++ b/rewrite-core/src/main/java/org/openrewrite/Tree.java @@ -38,7 +38,7 @@ default String getJacksonPolymorphicTypeTag() { static UUID randomId() { //noinspection ConstantConditions - return MetricsHelper.UUID_TIMER.record(UUID::randomUUID); + return UUID.randomUUID(); } /** diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/MetricsHelper.java b/rewrite-core/src/main/java/org/openrewrite/internal/MetricsHelper.java index d889caa02be..d7bac346e86 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/MetricsHelper.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/MetricsHelper.java @@ -22,8 +22,6 @@ import java.util.function.Function; public class MetricsHelper { - public static Timer UUID_TIMER = Metrics.timer("rewrite.id.generate"); - public static void record(String timerName, Consumer<Timer.Builder> f) { Timer.Builder timer = Timer.builder(timerName); Timer.Sample sample = Timer.start(); diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java index 00f2fa0995d..d98c66a1906 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.openrewrite.SourceFile; +import org.openrewrite.test.RewriteTest; import org.openrewrite.tree.ParseError; import org.openrewrite.yaml.tree.Yaml; @@ -26,8 +27,9 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.yaml.Assertions.yaml; -class YamlParserTest { +class YamlParserTest implements RewriteTest { @Test void ascii() { From 0e8069aa2e21c51d397c80c1aa49a6b68205758e Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 25 Aug 2023 22:08:01 +0200 Subject: [PATCH 183/447] Add `ShortenFullyQualifiedTypeReferences#modifyOnly(J)` (#3501) This method can be called to construct a visitor which is designed to be registered using `doAfterVisit()` and will modify the given subtree only. --- ...ortenFullyQualifiedTypeReferencesTest.java | 43 +++++++++++++++++++ .../ShortenFullyQualifiedTypeReferences.java | 42 +++++++++++++++--- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java index cf8dd9684a7..1632c32cc3f 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java @@ -421,4 +421,47 @@ Function<Collection<?>, Integer> m() { ) ); } + + @Test + void subtree() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + if (method.getSimpleName().equals("m1")) { + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(method)); + } + return super.visitMethodDeclaration(method, ctx); + } + })), + java( + """ + import java.util.Collection; + import java.util.function.Function; + + class T { + Function<Collection<?>, Integer> m() { + return java.util.Collection::size; + } + Function<Collection<?>, Integer> m1() { + return java.util.Collection::size; + } + } + """, + """ + import java.util.Collection; + import java.util.function.Function; + + class T { + Function<Collection<?>, Integer> m() { + return java.util.Collection::size; + } + Function<Collection<?>, Integer> m1() { + return Collection::size; + } + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java b/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java index d5b49a1f8a5..a3f71ec5e40 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java @@ -15,13 +15,14 @@ */ package org.openrewrite.java; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Recipe; -import org.openrewrite.SourceFile; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; +import org.openrewrite.internal.lang.NonNull; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.DefaultJavaTypeSignatureBuilder; -import org.openrewrite.java.tree.*; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; import java.time.Duration; import java.util.HashMap; @@ -50,10 +51,21 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { + return getVisitor(null); + } + + public static TreeVisitor<?, ExecutionContext> modifyOnly(J subtree) { + return getVisitor(subtree); + } + + @NonNull + private static JavaVisitor<ExecutionContext> getVisitor(@Nullable J scope) { return new JavaVisitor<ExecutionContext>() { final Map<String, JavaType> usedTypes = new HashMap<>(); final JavaTypeSignatureBuilder signatureBuilder = new DefaultJavaTypeSignatureBuilder(); + boolean modify = scope == null; + private void ensureInitialized() { if (!usedTypes.isEmpty()) { return; @@ -94,6 +106,22 @@ public J.Identifier visitIdentifier(J.Identifier identifier, Map<String, JavaTyp } } + @Override + public @Nullable J visit(@Nullable Tree tree, ExecutionContext ctx) { + @SuppressWarnings("DataFlowIssue") + boolean subtreeRoot = !modify && (scope.equals(tree) || scope.isScope(tree)); + if (subtreeRoot) { + modify = true; + } + try { + return super.visit(tree, ctx); + } finally { + if (subtreeRoot) { + modify = false; + } + } + } + @Override public J visitImport(J.Import import_, ExecutionContext ctx) { // stop recursion @@ -108,6 +136,10 @@ public Space visitSpace(Space space, Space.Location loc, ExecutionContext ctx) { @Override public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) { + if (!modify) { + return super.visitFieldAccess(fieldAccess, ctx); + } + JavaType type = fieldAccess.getType(); if (fieldAccess.getName().getFieldType() == null && type instanceof JavaType.Class && ((JavaType.Class) type).getOwningClass() == null) { ensureInitialized(); From 403a1ecadd13059c217a98aa07faa2c0933af737 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Fri, 25 Aug 2023 16:32:10 -0400 Subject: [PATCH 184/447] Remove excessive message when parsing java files --- .../org/openrewrite/java/isolated/ReloadableJava11Parser.java | 1 - .../org/openrewrite/java/isolated/ReloadableJava17Parser.java | 1 - .../main/java/org/openrewrite/java/ReloadableJava8Parser.java | 1 - 3 files changed, 3 deletions(-) diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java index 895c4737043..d6b31c72a4c 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java @@ -152,7 +152,6 @@ public static Builder builder() { public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); LinkedHashMap<Input, JCTree.JCCompilationUnit> cus = parseInputsToCompilerAst(sourceFiles, ctx); - parsingListener.intermediateMessage("Compiling Java source files"); return cus.entrySet().stream().map(cuByPath -> { Input input = cuByPath.getKey(); parsingListener.startedParsing(input); diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java index 33f8166c6bd..aa04c8a9477 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java @@ -148,7 +148,6 @@ public static Builder builder() { @Override public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - parsingListener.intermediateMessage("Compiling Java source files"); LinkedHashMap<Input, JCTree.JCCompilationUnit> cus = parseInputsToCompilerAst(sourceFiles, ctx); return cus.entrySet().stream().map(cuByPath -> { Input input = cuByPath.getKey(); 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 a69d53547a8..b110f87e444 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 @@ -138,7 +138,6 @@ public void close() { @Override public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); - parsingListener.intermediateMessage("Compiling Java source files"); if (classpath != null) { // override classpath if (context.get(JavaFileManager.class) != pfm) { throw new IllegalStateException("JavaFileManager has been forked unexpectedly"); From 63834ac91a8ac484acb8ce7d286a17d46550c15c Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Fri, 25 Aug 2023 23:51:04 -0400 Subject: [PATCH 185/447] Remove excessive message when parsing maven files --- .../src/main/java/org/openrewrite/maven/MavenParser.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java index b96a7b8f123..910c980721d 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java @@ -26,7 +26,6 @@ import org.openrewrite.maven.tree.Pom; import org.openrewrite.maven.tree.ResolvedPom; import org.openrewrite.tree.ParseError; -import org.openrewrite.tree.ParsingExecutionContextView; import org.openrewrite.xml.XmlParser; import org.openrewrite.xml.tree.Xml; @@ -95,7 +94,6 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re } } - ParsingExecutionContextView.view(ctx).getParsingListener().intermediateMessage("Resolving Maven dependencies"); MavenPomDownloader downloader = new MavenPomDownloader(projectPomsByPath, ctx); MavenExecutionContextView mavenCtx = MavenExecutionContextView.view(ctx); From cb8bd9bd5bb85efb7a5d51d2658b70941a67f2c0 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sat, 26 Aug 2023 13:50:34 +0200 Subject: [PATCH 186/447] Add `AbstractRefasterJavaVisitor` (#3503) This is a base class used by Refaster-based recipes as generated by the `rewrite-templating` annotation processor. It contains some simple utility methods to simplify the generation of the recipes. --- .../template/AbstractRefasterJavaVisitor.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java new file mode 100644 index 00000000000..0d1795eafa4 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java @@ -0,0 +1,80 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.internal.template; + +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.ShortenFullyQualifiedTypeReferences; +import org.openrewrite.java.cleanup.SimplifyBooleanExpressionVisitor; +import org.openrewrite.java.cleanup.UnnecessaryParenthesesVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaCoordinates; + +import java.util.EnumSet; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +@SuppressWarnings("unused") +public abstract class AbstractRefasterJavaVisitor extends JavaVisitor<ExecutionContext> { + + protected final <T> Supplier<T> memoize(Supplier<T> delegate) { + AtomicReference<T> value = new AtomicReference<>(); + return () -> { + T val = value.get(); + if (val == null) { + val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); + } + return val; + }; + } + + protected final JavaTemplate.Matcher matcher(Supplier<JavaTemplate> template, Cursor cursor) { + return template.get().matcher(cursor); + } + + protected final J apply(Supplier<JavaTemplate> template, Cursor cursor, JavaCoordinates coordinates, Object... parameters) { + return template.get().apply(cursor, coordinates, parameters); + } + + protected J embed(J j, Cursor cursor, ExecutionContext ctx) { + return embed(j, cursor, ctx, EmbeddingOptions.ALL_OPTIONS); + } + + @SuppressWarnings({"DataFlowIssue", "SameParameterValue"}) + protected J embed(J j, Cursor cursor, ExecutionContext ctx, EnumSet<EmbeddingOptions> options) { + TreeVisitor<?, ExecutionContext> visitor; + if (options.contains(EmbeddingOptions.REMOVE_PARENS) && !getAfterVisit().contains(visitor = new UnnecessaryParenthesesVisitor())) { + doAfterVisit(visitor); + } + if (options.contains(EmbeddingOptions.SHORTEN_NAMES) && !getAfterVisit().contains(visitor = new ShortenFullyQualifiedTypeReferences().getVisitor())) { + doAfterVisit(visitor); + } + if (options.contains(EmbeddingOptions.SIMPLIFY_BOOLEANS)) { + j = new SimplifyBooleanExpressionVisitor().visitNonNull(j, ctx, cursor.getParent()); + } + return j; + } + + protected enum EmbeddingOptions { + SHORTEN_NAMES, SIMPLIFY_BOOLEANS, REMOVE_PARENS; + + public static final EnumSet<EmbeddingOptions> ALL_OPTIONS = EnumSet.allOf(EmbeddingOptions.class); + } +} From b8605ce2e36da0a8021c0cafafd5c85d548a531d Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sun, 27 Aug 2023 11:48:22 +0200 Subject: [PATCH 187/447] Delay the resolution of classpath in `JavaParser.Builder` (#3505) Instead of directly resolving artifact names to classpath entries inside `JavaParser.Builder#classpath(String...)` this is now delayed until `build()` is called for the first time. This has the advantage that constructing a builder is cheap even when the `classpath(String...)` method gets invoked. As a result a recipe can initialize a `JavaTemplate` (with a customized `JavaBuilder`) in a field initializer of its visitor without incurring a rather big overhead. --- .../org/openrewrite/groovy/GroovyParser.java | 23 +++++++++++++++---- .../org/openrewrite/java/Java11Parser.java | 3 +-- .../java/isolated/ReloadableJava11Parser.java | 2 +- .../org/openrewrite/java/Java17Parser.java | 2 +- .../java/isolated/ReloadableJava17Parser.java | 2 +- .../org/openrewrite/java/Java8Parser.java | 2 +- .../java/org/openrewrite/java/JavaParser.java | 16 +++++++++++-- 7 files changed, 38 insertions(+), 12 deletions(-) diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java index 67240b74719..c8d86e4848e 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java @@ -205,7 +205,9 @@ public static GroovyParser.Builder builder(Builder base) { @SuppressWarnings("unused") public static class Builder extends Parser.Builder { @Nullable - private Collection<Path> classpath = JavaParser.runtimeClasspath(); + private Collection<Path> classpath = Collections.emptyList(); + @Nullable + protected Collection<String> artifactNames = Collections.emptyList(); private JavaTypeCache typeCache = new JavaTypeCache(); private boolean logCompilationWarningsAndErrors = false; @@ -219,6 +221,7 @@ public Builder() { public Builder(Builder base) { super(G.CompilationUnit.class); this.classpath = base.classpath; + this.artifactNames = base.artifactNames; this.typeCache = base.typeCache; this.logCompilationWarningsAndErrors = base.logCompilationWarningsAndErrors; this.styles.addAll(base.styles); @@ -231,16 +234,19 @@ public Builder logCompilationWarningsAndErrors(boolean logCompilationWarningsAnd } public Builder classpath(@Nullable Collection<Path> classpath) { + this.artifactNames = null; this.classpath = classpath; return this; } - public Builder classpath(@Nullable String... classpath) { - this.classpath = JavaParser.dependenciesFromClasspath(classpath); + public Builder classpath(@Nullable String... artifactNames) { + this.artifactNames = Arrays.asList(artifactNames); + this.classpath = null; return this; } public Builder classpathFromResource(ExecutionContext ctx, String... artifactNamesWithVersions) { + this.artifactNames = null; this.classpath = JavaParser.dependenciesFromResources(ctx, artifactNamesWithVersions); return this; } @@ -270,8 +276,17 @@ public GroovyParser.Builder compilerCustomizers(Iterable<Consumer<CompilerConfig return this; } + @Nullable + private Collection<Path> resolvedClasspath() { + if (artifactNames != null && !artifactNames.isEmpty()) { + classpath = JavaParser.dependenciesFromClasspath(artifactNames.toArray(new String[0])); + artifactNames = null; + } + return classpath; + } + public GroovyParser build() { - return new GroovyParser(classpath, styles, logCompilationWarningsAndErrors, typeCache, compilerCustomizers); + return new GroovyParser(resolvedClasspath(), styles, logCompilationWarningsAndErrors, typeCache, compilerCustomizers); } @Override diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/Java11Parser.java b/rewrite-java-11/src/main/java/org/openrewrite/java/Java11Parser.java index 22b4b675d20..9379f79c04c 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/Java11Parser.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/Java11Parser.java @@ -19,7 +19,6 @@ import org.openrewrite.SourceFile; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.JavaTypeCache; -import org.openrewrite.java.tree.J; import java.lang.reflect.Constructor; import java.net.URI; @@ -90,7 +89,7 @@ public Java11Parser build() { parserConstructor.setAccessible(true); JavaParser delegate = (JavaParser) parserConstructor - .newInstance(logCompilationWarningsAndErrors, classpath, classBytesClasspath, dependsOn, charset, styles, javaTypeCache); + .newInstance(logCompilationWarningsAndErrors, resolvedClasspath(), classBytesClasspath, dependsOn, charset, styles, javaTypeCache); return new Java11Parser(delegate); } catch (Exception e) { diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java index d6b31c72a4c..f5de9feb6e6 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11Parser.java @@ -331,7 +331,7 @@ public Env<AttrContext> remove() { public static class Builder extends JavaParser.Builder<ReloadableJava11Parser, Builder> { @Override public ReloadableJava11Parser build() { - return new ReloadableJava11Parser(logCompilationWarningsAndErrors, classpath, classBytesClasspath, dependsOn, charset, styles, javaTypeCache); + return new ReloadableJava11Parser(logCompilationWarningsAndErrors, resolvedClasspath(), classBytesClasspath, dependsOn, charset, styles, javaTypeCache); } } diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/Java17Parser.java b/rewrite-java-17/src/main/java/org/openrewrite/java/Java17Parser.java index 5837e33ad4d..3bbc9047351 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/Java17Parser.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/Java17Parser.java @@ -89,7 +89,7 @@ public Java17Parser build() { parserConstructor.setAccessible(true); JavaParser delegate = (JavaParser) parserConstructor - .newInstance(logCompilationWarningsAndErrors, classpath, classBytesClasspath, dependsOn, charset, styles, javaTypeCache); + .newInstance(logCompilationWarningsAndErrors, resolvedClasspath(), classBytesClasspath, dependsOn, charset, styles, javaTypeCache); return new Java17Parser(delegate); } catch (Exception e) { diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java index aa04c8a9477..d6bb5321ecf 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17Parser.java @@ -301,7 +301,7 @@ public void reset(Collection<URI> uris) { public static class Builder extends JavaParser.Builder<ReloadableJava17Parser, Builder> { @Override public ReloadableJava17Parser build() { - return new ReloadableJava17Parser(logCompilationWarningsAndErrors, classpath, classBytesClasspath, dependsOn, charset, styles, javaTypeCache); + return new ReloadableJava17Parser(logCompilationWarningsAndErrors, resolvedClasspath(), classBytesClasspath, dependsOn, charset, styles, javaTypeCache); } } diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/Java8Parser.java b/rewrite-java-8/src/main/java/org/openrewrite/java/Java8Parser.java index 81c9abb13c8..3abd32b3bd7 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/Java8Parser.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/Java8Parser.java @@ -135,7 +135,7 @@ public Java8Parser build() { delegateParserConstructor.setAccessible(true); JavaParser delegate = (JavaParser) delegateParserConstructor - .newInstance(classpath, classBytesClasspath, dependsOn, charset, logCompilationWarningsAndErrors, styles, javaTypeCache); + .newInstance(resolvedClasspath(), classBytesClasspath, dependsOn, charset, logCompilationWarningsAndErrors, styles, javaTypeCache); return new Java8Parser(delegate); } catch (Exception e) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index b5d40655c9f..158ef9e77a1 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -300,6 +300,7 @@ default boolean accept(Path path) { @SuppressWarnings("unchecked") abstract class Builder<P extends JavaParser, B extends Builder<P, B>> extends Parser.Builder { protected Collection<Path> classpath = Collections.emptyList(); + protected Collection<String> artifactNames = Collections.emptyList(); protected Collection<byte[]> classBytesClasspath = Collections.emptyList(); protected JavaTypeCache javaTypeCache = new JavaTypeCache(); @@ -343,17 +344,20 @@ public B dependsOn(@Language("java") String... inputsAsStrings) { } public B classpath(Collection<Path> classpath) { + this.artifactNames = Collections.emptyList(); this.classpath = classpath; return (B) this; } - public B classpath(String... classpath) { - this.classpath = dependenciesFromClasspath(classpath); + public B classpath(String... artifactNames) { + this.artifactNames = Arrays.asList(artifactNames); + this.classpath = Collections.emptyList(); return (B) this; } @SuppressWarnings("UnusedReturnValue") public B classpathFromResources(ExecutionContext ctx, String... classpath) { + this.artifactNames = Collections.emptyList(); this.classpath = dependenciesFromResources(ctx, classpath); return (B) this; } @@ -370,6 +374,14 @@ public B styles(Iterable<? extends NamedStyles> styles) { return (B) this; } + protected Collection<Path> resolvedClasspath() { + if (!artifactNames.isEmpty()) { + classpath = JavaParser.dependenciesFromClasspath(artifactNames.toArray(new String[0])); + artifactNames = Collections.emptyList(); + } + return classpath; + } + public abstract P build(); @Override From 1f98ef029a5c69893d4c5b7cec21fdc48baa2461 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sun, 27 Aug 2023 12:23:38 +0200 Subject: [PATCH 188/447] Polish `AbstractRefasterJavaVisitor` API --- .../template/AbstractRefasterJavaVisitor.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java index 0d1795eafa4..2f2c2f916ca 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java @@ -26,6 +26,7 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaCoordinates; +import java.util.Arrays; import java.util.EnumSet; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; @@ -53,28 +54,31 @@ protected final J apply(Supplier<JavaTemplate> template, Cursor cursor, JavaCoor return template.get().apply(cursor, coordinates, parameters); } + @Deprecated + // to be removed as soon as annotation processor generates required options protected J embed(J j, Cursor cursor, ExecutionContext ctx) { - return embed(j, cursor, ctx, EmbeddingOptions.ALL_OPTIONS); + return embed(j, cursor, ctx, EmbeddingOption.values()); } @SuppressWarnings({"DataFlowIssue", "SameParameterValue"}) - protected J embed(J j, Cursor cursor, ExecutionContext ctx, EnumSet<EmbeddingOptions> options) { + protected J embed(J j, Cursor cursor, ExecutionContext ctx, EmbeddingOption... options) { + EnumSet<EmbeddingOption> optionsSet = options.length > 0 ? EnumSet.copyOf(Arrays.asList(options)) : + EnumSet.noneOf(EmbeddingOption.class); + TreeVisitor<?, ExecutionContext> visitor; - if (options.contains(EmbeddingOptions.REMOVE_PARENS) && !getAfterVisit().contains(visitor = new UnnecessaryParenthesesVisitor())) { + if (optionsSet.contains(EmbeddingOption.REMOVE_PARENS) && !getAfterVisit().contains(visitor = new UnnecessaryParenthesesVisitor())) { doAfterVisit(visitor); } - if (options.contains(EmbeddingOptions.SHORTEN_NAMES) && !getAfterVisit().contains(visitor = new ShortenFullyQualifiedTypeReferences().getVisitor())) { + if (optionsSet.contains(EmbeddingOption.SHORTEN_NAMES) && !getAfterVisit().contains(visitor = new ShortenFullyQualifiedTypeReferences().getVisitor())) { doAfterVisit(visitor); } - if (options.contains(EmbeddingOptions.SIMPLIFY_BOOLEANS)) { + if (optionsSet.contains(EmbeddingOption.SIMPLIFY_BOOLEANS)) { j = new SimplifyBooleanExpressionVisitor().visitNonNull(j, ctx, cursor.getParent()); } return j; } - protected enum EmbeddingOptions { + protected enum EmbeddingOption { SHORTEN_NAMES, SIMPLIFY_BOOLEANS, REMOVE_PARENS; - - public static final EnumSet<EmbeddingOptions> ALL_OPTIONS = EnumSet.allOf(EmbeddingOptions.class); } } From f9d048b8a800d434fc044442281f6dcf2d99ac5d Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sun, 27 Aug 2023 16:25:47 +0200 Subject: [PATCH 189/447] Fix `ShortenFullyQualifiedTypeReferences#modifyOnly()` API This API is now also used by `AbstractRefasterJavaVisitor`. --- .../openrewrite/java/ShortenFullyQualifiedTypeReferences.java | 2 +- .../java/cleanup/UnnecessaryParenthesesVisitor.java | 2 ++ .../java/internal/template/AbstractRefasterJavaVisitor.java | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java b/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java index a3f71ec5e40..0f66f92fa03 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java @@ -54,7 +54,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { return getVisitor(null); } - public static TreeVisitor<?, ExecutionContext> modifyOnly(J subtree) { + public static <J2 extends J> TreeVisitor<J, ExecutionContext> modifyOnly(J2 subtree) { return getVisitor(subtree); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java index cf6bbc633a3..0b443312b1c 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java.cleanup; +import lombok.EqualsAndHashCode; import org.openrewrite.*; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.UnwrapParentheses; @@ -22,6 +23,7 @@ import org.openrewrite.java.style.UnnecessaryParenthesesStyle; import org.openrewrite.java.tree.*; +@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) public class UnnecessaryParenthesesVisitor extends JavaVisitor<ExecutionContext> { @Override public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionContext) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java index 2f2c2f916ca..d8197183168 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java @@ -69,8 +69,8 @@ protected J embed(J j, Cursor cursor, ExecutionContext ctx, EmbeddingOption... o if (optionsSet.contains(EmbeddingOption.REMOVE_PARENS) && !getAfterVisit().contains(visitor = new UnnecessaryParenthesesVisitor())) { doAfterVisit(visitor); } - if (optionsSet.contains(EmbeddingOption.SHORTEN_NAMES) && !getAfterVisit().contains(visitor = new ShortenFullyQualifiedTypeReferences().getVisitor())) { - doAfterVisit(visitor); + if (optionsSet.contains(EmbeddingOption.SHORTEN_NAMES)) { + doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(j)); } if (optionsSet.contains(EmbeddingOption.SIMPLIFY_BOOLEANS)) { j = new SimplifyBooleanExpressionVisitor().visitNonNull(j, ctx, cursor.getParent()); From 33f9d53ac3bd74f68547f9a78f70ec232d1d714b Mon Sep 17 00:00:00 2001 From: Kun Li <122563761+kunli2@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:31:05 -0700 Subject: [PATCH 190/447] yaml parser support unicode (#3507) * Support multi-byte unicode in Yaml parser --- .../yaml/FormatPreservingReader.java | 46 ++++++++++++- .../java/org/openrewrite/yaml/YamlParser.java | 3 +- .../yaml/FormatPreservingReaderTest.java | 6 +- .../org/openrewrite/yaml/YamlParserTest.java | 66 +++++++++++++++++-- 4 files changed, 108 insertions(+), 13 deletions(-) diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java index e25872e7bb9..14c6dfa7621 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.Reader; +import java.io.StringReader; import java.util.ArrayList; /** @@ -28,19 +29,49 @@ * YAML AST elements. */ class FormatPreservingReader extends Reader { - private final Reader delegate; + // whether the source has multi bytes (> 2 bytes) unicode characters + private final boolean hasMultiBytesUnicode; + // Characters index to source index mapping, valid only when `hasMultiBytesUnicode` is true. + // Snake yaml parser is based on characters index and reader is based on source index. If there are any >2 bytes + // unicode characters in source code, it will make the index mismatch. + private final int[] indexes; + private ArrayList<Character> buffer = new ArrayList<>(); @Getter private int bufferIndex = 0; - FormatPreservingReader(Reader delegate) { - this.delegate = delegate; + FormatPreservingReader(String source) { + this.delegate = new StringReader(source); + + boolean hasUnicodes = false; + int[] pos = new int[source.length() + 1]; + + int cursor = 0; + int i = 1; + pos[0] = 0; + + while (cursor < source.length()) { + int newCursor = source.offsetByCodePoints(cursor, 1); + if (newCursor > cursor + 1) { + hasUnicodes = true; + } + pos[i++] = newCursor; + cursor = newCursor; + } + + hasMultiBytesUnicode = hasUnicodes; + indexes = hasMultiBytesUnicode ? pos : new int[]{}; } String prefix(int lastEnd, int startIndex) { + if (hasMultiBytesUnicode) { + lastEnd = indexes[lastEnd]; + startIndex = indexes[startIndex]; + } + assert lastEnd <= startIndex; int prefixLen = startIndex - lastEnd; @@ -65,6 +96,15 @@ public String prefix(int lastEnd, Event event) { } public String readStringFromBuffer(int start, int end) { + if (end < start) { + return ""; + } + + if (hasMultiBytesUnicode) { + start = indexes[start]; + end = indexes[end + 1] - 1; + } + int length = end - start + 1; char[] readBuff = new char[length]; for (int i = 0; i < length; i++) { diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index d12dc207537..75a3ec1eec2 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -107,8 +107,7 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr yamlSourceWithVariablePlaceholders.append(yamlSource, pos, yamlSource.length()); } - try (StringReader stringReader = new StringReader(yamlSourceWithVariablePlaceholders.toString()); - FormatPreservingReader reader = new FormatPreservingReader(stringReader)) { + try (FormatPreservingReader reader = new FormatPreservingReader(yamlSourceWithVariablePlaceholders.toString())) { StreamReader streamReader = new StreamReader(reader); Scanner scanner = new ScannerImpl(streamReader, new LoaderOptions()); Parser parser = new ParserImpl(scanner); diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/FormatPreservingReaderTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/FormatPreservingReaderTest.java index 94e36565126..539dc7acb05 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/FormatPreservingReaderTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/FormatPreservingReaderTest.java @@ -28,7 +28,7 @@ class FormatPreservingReaderTest { @Test void allInCurrentBuffer() throws IOException { var text = "0123456789"; - var formatPreservingReader = new FormatPreservingReader(new StringReader(text)); + var formatPreservingReader = new FormatPreservingReader(text); char[] charArray = new char[10]; formatPreservingReader.read(charArray, 0, 10); @@ -38,7 +38,7 @@ void allInCurrentBuffer() throws IOException { @Test void allInPreviousBuffer() throws IOException { var text = "0123456789"; - var formatPreservingReader = new FormatPreservingReader(new StringReader(text)); + var formatPreservingReader = new FormatPreservingReader(text); char[] charArray = new char[10]; @@ -51,7 +51,7 @@ void allInPreviousBuffer() throws IOException { @Test void splitBetweenPrevAndCurrentBuffer() throws IOException { var text = "0123456789"; - var formatPreservingReader = new FormatPreservingReader(new StringReader(text)); + var formatPreservingReader = new FormatPreservingReader(text); char[] charArray = new char[10]; diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java index d98c66a1906..5a42250bd0b 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java @@ -15,9 +15,11 @@ */ package org.openrewrite.yaml; +import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.Issue; import org.openrewrite.SourceFile; import org.openrewrite.test.RewriteTest; import org.openrewrite.tree.ParseError; @@ -49,15 +51,69 @@ void ascii() { assertThat(title.getValue()).isEqualTo("b"); } + @Issue("https://github.com/openrewrite/rewrite/issues/2062") + @Test + void fourBytesUnicode() { + rewriteRun( + yaml( + """ + root: + - value1: 🛠 + value2: check + """ + ) + ); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") @ParameterizedTest @ValueSource(strings = { - "🛠", - "🛠🛠", - "🛠 🛠" + "b", + " 🛠", + " 🛠🛠", + "🛠 🛠", + "hello🛠world", + "你好世界", + "你好🛠世界" }) - void unicodeParseError(String input ) { + void parseYamlWithUnicode(String input) { Stream<SourceFile> yamlSources = YamlParser.builder().build().parse("a: %s\n".formatted(input)); - assertThat(yamlSources).singleElement().isInstanceOf(ParseError.class); + SourceFile sourceFile = yamlSources.findFirst().get(); + assertThat(sourceFile).isNotInstanceOf(ParseError.class); + + Yaml.Documents documents = (Yaml.Documents) sourceFile; + Yaml.Document document = documents.getDocuments().get(0); + + // Assert that end is parsed correctly + Yaml.Mapping mapping = (Yaml.Mapping) document.getBlock(); + Yaml.Mapping.Entry entry = mapping.getEntries().get(0); + Yaml.Scalar title = (Yaml.Scalar) entry.getValue(); + assertThat(title.getValue()).isEqualTo(input.trim()); + } + + @ParameterizedTest + @ValueSource(strings = { + "🛠 : 🛠", + "你a好b🛠世c界d : 你a🛠好b🛠世c🛠界d" + }) + void unicodeCharacterSpanningMultipleBytes(@Language("yml") String input) { + rewriteRun( + yaml(input) + ); } + @Test + void newlinesCombinedWithUnniCode() { + rewriteRun( + yaml( + """ + { + "data": { + "pro🛠metheus.y🛠ml": "global:\\n scrape_🛠interval: 10s🛠\\n sc🛠rape_timeout: 9s" + } + } + """ + ) + ); + } } From 6ea0e2efaae4692976825b012944eaf2146a9673 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 28 Aug 2023 19:16:12 -0400 Subject: [PATCH 191/447] Add plain text mask support to PlainTextParser --- .../org/openrewrite/text/PlainTextParser.java | 32 ++++++++++++++- .../openrewrite/text/PlainTextParserTest.java | 39 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 rewrite-core/src/test/java/org/openrewrite/text/PlainTextParserTest.java diff --git a/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java b/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java index f032278fbcd..f447102c483 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/PlainTextParser.java @@ -16,16 +16,19 @@ package org.openrewrite.text; import org.openrewrite.ExecutionContext; -import org.openrewrite.tree.ParseError; import org.openrewrite.Parser; import org.openrewrite.SourceFile; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; +import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.util.Collection; +import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -103,12 +106,39 @@ public static Builder builder() { } public static class Builder extends Parser.Builder { + @Nullable + private Collection<PathMatcher> plainTextMasks; + public Builder() { super(PlainText.class); } + public Builder plainTextMasks(Collection<PathMatcher> plainTextMasks) { + this.plainTextMasks = plainTextMasks; + return this; + } + + public Builder plainTextMasks(Path basePath, Iterable<String> plainTextMaskGlobs) { + return plainTextMasks(StreamSupport.stream(plainTextMaskGlobs.spliterator(), false) + .map((o) -> basePath.getFileSystem().getPathMatcher("glob:" + o)) + .collect(Collectors.toList())); + } + @Override public PlainTextParser build() { + if (plainTextMasks != null) { + return new PlainTextParser() { + @Override + public boolean accept(Path path) { + for (PathMatcher matcher : plainTextMasks) { + if (matcher.matches(path)) { + return true; + } + } + return false; + } + }; + } return new PlainTextParser(); } diff --git a/rewrite-core/src/test/java/org/openrewrite/text/PlainTextParserTest.java b/rewrite-core/src/test/java/org/openrewrite/text/PlainTextParserTest.java new file mode 100644 index 00000000000..af972596bc4 --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/text/PlainTextParserTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.text; + +import org.junit.jupiter.api.Test; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Parser; + +import java.nio.file.Paths; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PlainTextParserTest { + + @Test + void plainTextMask() { + PlainTextParser parser = PlainTextParser.builder() + .plainTextMasks(Paths.get("."), List.of("**/*.png")) + .build(); + assertThat(parser + .parseInputs(List.of(Parser.Input.fromString(Paths.get("test.png"), "test")), null, new InMemoryExecutionContext()) + .findFirst() + ).containsInstanceOf(PlainText.class); + } +} From 0f8f5cb6ceae2c4ea4c71b2811e7d1af910eb8f7 Mon Sep 17 00:00:00 2001 From: AlexanderSkrock <alexanderskrock@gmx.de> Date: Tue, 29 Aug 2023 09:46:59 +0200 Subject: [PATCH 192/447] #3289 padding on implements (#3511) * #3290 Added test cases around extending and implementing classes * #3290 add space when changing implements part * #3290 reformat code * #3290 add license header * #3290 cleaned identifier declaration and padding definition Identifiers usually do not need padding, so I decided to create the constants without padding and added it inside the visit method. * #3289 use java iso visitor * #3289 Remove unnecessary empty check Co-authored-by: Knut Wannheden <knut.wannheden@gmail.com> * #3289 fix extends formatting --------- Co-authored-by: Knut Wannheden <knut.wannheden@gmail.com> --- .../java/ChangeClassInheritanceTest.java | 196 ++++++++++++++++++ .../java/org/openrewrite/java/tree/J.java | 12 +- 2 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 rewrite-java-test/src/test/java/org/openrewrite/java/ChangeClassInheritanceTest.java diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeClassInheritanceTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeClassInheritanceTest.java new file mode 100644 index 00000000000..e7721cfa74e --- /dev/null +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeClassInheritanceTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https:www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Tree; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; +import org.openrewrite.marker.Markers; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import static org.openrewrite.java.Assertions.java; + +class ChangeClassInheritanceTest implements RewriteTest { + + @Nested + class WithExtendsTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(RewriteTest.toRecipe(() -> new JavaIsoVisitor<>() { + private final J.Identifier arrayList = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), ArrayList.class.getName(), JavaType.buildType(ArrayList.class.getName()), null); + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration c, ExecutionContext executionContext) { + J.ClassDeclaration classDecl = super.visitClassDeclaration(c, executionContext); + if (classDecl.getExtends() != null && Objects.equals(arrayList.getType(), classDecl.getExtends().getType())) { + return classDecl; + } + return classDecl.withExtends(arrayList.withPrefix(Space.SINGLE_SPACE)); + } + })); + } + + @Test + void addExtends() { + rewriteRun( + java( + """ + package de.example; + + class CustomList { + } + """, + """ + package de.example; + + class CustomList extends java.util.ArrayList { + } + """ + ) + ); + } + + @Test + void replaceExtends() { + rewriteRun( + java( + """ + package de.example; + + class CustomList extends java.util.HashMap { + } + """, + """ + package de.example; + + class CustomList extends java.util.ArrayList { + } + """ + ) + ); + } + + @Test + void addExtendsOnExistingImplements() { + rewriteRun( + java( + """ + package de.example; + + class CustomList implements java.lang.Cloneable { + } + """, + """ + package de.example; + + class CustomList extends java.util.ArrayList implements java.lang.Cloneable { + } + """ + ) + ); + } + } + + @Nested + class WithImplementsTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(RewriteTest.toRecipe(() -> new JavaIsoVisitor<>() { + private final J.Identifier serializable = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), Serializable.class.getName(), JavaType.buildType(Serializable.class.getName()), null); + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration c, ExecutionContext executionContext) { + J.ClassDeclaration classDecl = super.visitClassDeclaration(c, executionContext); + if (classDecl.getImplements() != null && classDecl.getImplements().stream().anyMatch(e -> Objects.equals(e.getType(), serializable.getType()))) { + return classDecl; + } + return classDecl.withImplements(List.of(serializable.withPrefix(Space.SINGLE_SPACE))); + } + })); + } + + @Test + void addImplements() { + rewriteRun( + java( + """ + package de.example; + + class CustomList { + } + """, + """ + package de.example; + + class CustomList implements java.io.Serializable { + } + """ + ) + ); + } + + @Test + void replaceImplements() { + rewriteRun( + java( + """ + package de.example; + + class CustomList implements java.lang.Cloneable { + } + """, + """ + package de.example; + + class CustomList implements java.io.Serializable { + } + """ + ) + ); + } + + @Test + void addImplementsOnExistingExtends() { + rewriteRun( + java( + """ + package de.example; + + class CustomList extends java.util.HashMap { + } + """, + """ + package de.example; + + class CustomList extends java.util.HashMap implements java.io.Serializable { + } + """ + ) + ); + } + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index 540ffe2b6b3..d08ced94385 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -1163,7 +1163,11 @@ public TypeTree getExtends() { } public ClassDeclaration withExtends(@Nullable TypeTree extendings) { - return getPadding().withExtends(JLeftPadded.withElement(this.extendings, extendings)); + JLeftPadded newExtendings = JLeftPadded.withElement(this.extendings, extendings); + if (newExtendings != null) { + newExtendings = newExtendings.withBefore(Space.SINGLE_SPACE); + } + return getPadding().withExtends(newExtendings); } @Nullable @@ -1175,7 +1179,11 @@ public List<TypeTree> getImplements() { } public ClassDeclaration withImplements(@Nullable List<TypeTree> implementings) { - return getPadding().withImplements(JContainer.withElementsNullable(this.implementings, implementings)); + JContainer<TypeTree> newImplementings = JContainer.withElementsNullable(this.implementings, implementings); + if (newImplementings != null) { + newImplementings = newImplementings.withBefore(Space.SINGLE_SPACE); + } + return getPadding().withImplements(newImplementings); } @Nullable From ff117670266ac9c2451ec6386b74bef9224d0cbd Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 29 Aug 2023 00:47:19 -0700 Subject: [PATCH 193/447] Move FindCallGraph to rewrite-all under the fqn org.openrewrite.FindCallGraph --- .../java/search/FindCallGraphTest.java | 65 ----------- .../java/search/FindCallGraph.java | 109 ------------------ .../java/table/MethodCallGraph.java | 41 ------- 3 files changed, 215 deletions(-) delete mode 100644 rewrite-java-test/src/test/java/org/openrewrite/java/search/FindCallGraphTest.java delete mode 100644 rewrite-java/src/main/java/org/openrewrite/java/search/FindCallGraph.java delete mode 100644 rewrite-java/src/main/java/org/openrewrite/java/table/MethodCallGraph.java diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindCallGraphTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindCallGraphTest.java deleted file mode 100644 index 3393d3672d6..00000000000 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindCallGraphTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.search; - -import org.junit.jupiter.api.Test; -import org.openrewrite.java.table.MethodCallGraph; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.openrewrite.java.Assertions.java; - -public class FindCallGraphTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new FindCallGraph()); - } - - @Test - void findUniqueCallsPerDeclaration() { - rewriteRun( - spec -> spec.dataTable(MethodCallGraph.Row.class, row -> - assertThat(row).containsExactly( - new MethodCallGraph.Row( - "Test test()", - "java.io.PrintStream println(java.lang.String)" - ), - new MethodCallGraph.Row( - "Test test2()", - "java.io.PrintStream println(java.lang.String)" - ) - ) - ), - java( - """ - class Test { - void test() { - System.out.println("Hello"); - System.out.println("Hello"); - } - - void test2() { - System.out.println("Hello"); - System.out.println("Hello"); - } - } - """ - ) - ); - } -} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindCallGraph.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindCallGraph.java deleted file mode 100644 index 2bd52c332de..00000000000 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindCallGraph.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.search; - -import org.openrewrite.*; -import org.openrewrite.internal.TreeVisitorAdapter; -import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaTypeSignatureBuilder; -import org.openrewrite.java.internal.DefaultJavaTypeSignatureBuilder; -import org.openrewrite.java.table.MethodCallGraph; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.marker.SearchResult; - -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Set; - -public class FindCallGraph extends Recipe { - transient final MethodCallGraph methodCallGraph = new MethodCallGraph(this); - - @Override - public String getDisplayName() { - return "Build call graph"; - } - - @Override - public String getDescription() { - return "Produce the call graph describing the relationships between methods."; - } - - @Override - public TreeVisitor<?, ExecutionContext> getVisitor() { - return new JavaIsoVisitor<ExecutionContext>() { - final JavaTypeSignatureBuilder signatureBuilder = new CallGraphSignatureBuilder(); - final Set<JavaType.Method> methodsCalledInDeclaration = Collections.newSetFromMap(new IdentityHashMap<>()); - - @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) { - J.MethodDeclaration m = super.visitMethodDeclaration(method, executionContext); - methodsCalledInDeclaration.clear(); - return m; - } - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - recordCall(method.getMethodType(), ctx); - return super.visitMethodInvocation(method, ctx); - } - - @Override - public J.MemberReference visitMemberReference(J.MemberReference memberRef, ExecutionContext ctx) { - recordCall(memberRef.getMethodType(), ctx); - return super.visitMemberReference(memberRef, ctx); - } - - private void recordCall(@Nullable JavaType.Method method, ExecutionContext ctx) { - if (method == null) { - return; - } - J.MethodDeclaration declaration = getCursor().firstEnclosing(J.MethodDeclaration.class); - if (declaration != null && declaration.getMethodType() != null && methodsCalledInDeclaration.add(method)) { - methodsCalledInDeclaration.add(method); - methodCallGraph.insertRow(ctx, new MethodCallGraph.Row( - signatureBuilder.signature(declaration.getMethodType()), - signatureBuilder.signature(method) - )); - } - } - }; - } - - private static class CallGraphSignatureBuilder extends DefaultJavaTypeSignatureBuilder { - @Override - public String genericSignature(Object type) { - return super.genericSignature(type) - .replace("Generic{", "<") - .replace("}", ">"); - } - - public String methodSignature(JavaType.Method method) { - StringBuilder s = new StringBuilder(signature(method.getDeclaringType())); - s.append(" ").append(method.getName()).append("("); - List<JavaType> parameterTypes = method.getParameterTypes(); - for (int i = 0; i < parameterTypes.size(); i++) { - JavaType parameterType = parameterTypes.get(i); - s.append(i == 0 ? "" : ", "); - s.append(signature(parameterType)); - } - s.append(")"); - return s.toString(); - } - } -} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/table/MethodCallGraph.java b/rewrite-java/src/main/java/org/openrewrite/java/table/MethodCallGraph.java deleted file mode 100644 index f63c9f73ca6..00000000000 --- a/rewrite-java/src/main/java/org/openrewrite/java/table/MethodCallGraph.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.table; - -import lombok.Value; -import org.openrewrite.Column; -import org.openrewrite.DataTable; -import org.openrewrite.Recipe; - -public class MethodCallGraph extends DataTable<MethodCallGraph.Row> { - - public MethodCallGraph(Recipe recipe) { - super(recipe, - "Method call graph", - "The call relationships between methods."); - } - - @Value - public static class Row { - @Column(displayName = "From", - description = "The containing method that is making the call.") - String from; - - @Column(displayName = "To", - description = "The method that is being called.") - String to; - } -} From 4f5713841529f567b9acdb1a71789dc39ad13db6 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 29 Aug 2023 10:19:01 +0200 Subject: [PATCH 194/447] Improve `overrideManagedDependency()` test case Typically, the `overrideManagedVersion` would be used to override the version of some parent POM, which isn't part of the project. Update test to reflect this by applying `SourceSpec#skip()` to the parent POM. --- .../maven/UpgradeDependencyVersionTest.java | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeDependencyVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeDependencyVersionTest.java index d186ed5ba0b..d34ef379f31 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeDependencyVersionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeDependencyVersionTest.java @@ -23,6 +23,7 @@ import org.openrewrite.DocumentExample; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpec; import java.util.Collections; import java.util.regex.Matcher; @@ -274,28 +275,13 @@ void overrideManagedDependency() { <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> - <version>13.0.1</version> + <version>13.0</version> </dependency> </dependencies> </dependencyManagement> </project> """, - """ - <project> - <groupId>com.mycompany</groupId> - <artifactId>my-parent</artifactId> - <version>1</version> - <dependencyManagement> - <dependencies> - <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - <version>14.0</version> - </dependency> - </dependencies> - </dependencyManagement> - </project> - """ + SourceSpec::skip ), mavenProject("my-child", pomXml( From 873126f3987835cfcdcfebf07e5b28e53ea9a0d8 Mon Sep 17 00:00:00 2001 From: Mike Solomon <mike@moderne.io> Date: Tue, 29 Aug 2023 02:04:56 -0700 Subject: [PATCH 195/447] Standardize line break recipe name (#3506) Right now we have a recipe with the display name of `Normalize the line breaks` and a recipe with the display name of `Normalize line breaks` (one for Java and one for XML). Let's make them both say the same thing since they do the same thing in their respective language. --- .../java/org/openrewrite/java/format/NormalizeLineBreaks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaks.java b/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaks.java index 2a475c9078f..182fbb0e916 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaks.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaks.java @@ -29,7 +29,7 @@ public class NormalizeLineBreaks extends Recipe { @Override public String getDisplayName() { - return "Normalize the line breaks"; + return "Normalize line breaks"; } @Override From eda18b5582148608b5dd0d91933ff1519747f260 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 29 Aug 2023 14:32:01 +0200 Subject: [PATCH 196/447] Revert "#3289 padding on implements (#3511)" This reverts commit 0f8f5cb6ceae2c4ea4c71b2811e7d1af910eb8f7. --- .../java/ChangeClassInheritanceTest.java | 196 ------------------ .../java/org/openrewrite/java/tree/J.java | 12 +- 2 files changed, 2 insertions(+), 206 deletions(-) delete mode 100644 rewrite-java-test/src/test/java/org/openrewrite/java/ChangeClassInheritanceTest.java diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeClassInheritanceTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeClassInheritanceTest.java deleted file mode 100644 index e7721cfa74e..00000000000 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeClassInheritanceTest.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https:www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Tree; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.Space; -import org.openrewrite.marker.Markers; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -import static org.openrewrite.java.Assertions.java; - -class ChangeClassInheritanceTest implements RewriteTest { - - @Nested - class WithExtendsTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(RewriteTest.toRecipe(() -> new JavaIsoVisitor<>() { - private final J.Identifier arrayList = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), ArrayList.class.getName(), JavaType.buildType(ArrayList.class.getName()), null); - - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration c, ExecutionContext executionContext) { - J.ClassDeclaration classDecl = super.visitClassDeclaration(c, executionContext); - if (classDecl.getExtends() != null && Objects.equals(arrayList.getType(), classDecl.getExtends().getType())) { - return classDecl; - } - return classDecl.withExtends(arrayList.withPrefix(Space.SINGLE_SPACE)); - } - })); - } - - @Test - void addExtends() { - rewriteRun( - java( - """ - package de.example; - - class CustomList { - } - """, - """ - package de.example; - - class CustomList extends java.util.ArrayList { - } - """ - ) - ); - } - - @Test - void replaceExtends() { - rewriteRun( - java( - """ - package de.example; - - class CustomList extends java.util.HashMap { - } - """, - """ - package de.example; - - class CustomList extends java.util.ArrayList { - } - """ - ) - ); - } - - @Test - void addExtendsOnExistingImplements() { - rewriteRun( - java( - """ - package de.example; - - class CustomList implements java.lang.Cloneable { - } - """, - """ - package de.example; - - class CustomList extends java.util.ArrayList implements java.lang.Cloneable { - } - """ - ) - ); - } - } - - @Nested - class WithImplementsTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(RewriteTest.toRecipe(() -> new JavaIsoVisitor<>() { - private final J.Identifier serializable = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), Serializable.class.getName(), JavaType.buildType(Serializable.class.getName()), null); - - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration c, ExecutionContext executionContext) { - J.ClassDeclaration classDecl = super.visitClassDeclaration(c, executionContext); - if (classDecl.getImplements() != null && classDecl.getImplements().stream().anyMatch(e -> Objects.equals(e.getType(), serializable.getType()))) { - return classDecl; - } - return classDecl.withImplements(List.of(serializable.withPrefix(Space.SINGLE_SPACE))); - } - })); - } - - @Test - void addImplements() { - rewriteRun( - java( - """ - package de.example; - - class CustomList { - } - """, - """ - package de.example; - - class CustomList implements java.io.Serializable { - } - """ - ) - ); - } - - @Test - void replaceImplements() { - rewriteRun( - java( - """ - package de.example; - - class CustomList implements java.lang.Cloneable { - } - """, - """ - package de.example; - - class CustomList implements java.io.Serializable { - } - """ - ) - ); - } - - @Test - void addImplementsOnExistingExtends() { - rewriteRun( - java( - """ - package de.example; - - class CustomList extends java.util.HashMap { - } - """, - """ - package de.example; - - class CustomList extends java.util.HashMap implements java.io.Serializable { - } - """ - ) - ); - } - } -} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index d08ced94385..540ffe2b6b3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -1163,11 +1163,7 @@ public TypeTree getExtends() { } public ClassDeclaration withExtends(@Nullable TypeTree extendings) { - JLeftPadded newExtendings = JLeftPadded.withElement(this.extendings, extendings); - if (newExtendings != null) { - newExtendings = newExtendings.withBefore(Space.SINGLE_SPACE); - } - return getPadding().withExtends(newExtendings); + return getPadding().withExtends(JLeftPadded.withElement(this.extendings, extendings)); } @Nullable @@ -1179,11 +1175,7 @@ public List<TypeTree> getImplements() { } public ClassDeclaration withImplements(@Nullable List<TypeTree> implementings) { - JContainer<TypeTree> newImplementings = JContainer.withElementsNullable(this.implementings, implementings); - if (newImplementings != null) { - newImplementings = newImplementings.withBefore(Space.SINGLE_SPACE); - } - return getPadding().withImplements(newImplementings); + return getPadding().withImplements(JContainer.withElementsNullable(this.implementings, implementings)); } @Nullable From 18e50197c1d767e4d587ab74435d3e1272faa164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Carpenter=E2=84=A2=EF=B8=8F?= <kevin@moderne.io> Date: Tue, 29 Aug 2023 10:03:24 -0300 Subject: [PATCH 197/447] Add missing space in description of parentRecipe from SourcesFilesResults (#3513) --- .../src/main/java/org/openrewrite/table/SourcesFileResults.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/table/SourcesFileResults.java b/rewrite-core/src/main/java/org/openrewrite/table/SourcesFileResults.java index e14fb75a3fd..8c65ad93f74 100644 --- a/rewrite-core/src/main/java/org/openrewrite/table/SourcesFileResults.java +++ b/rewrite-core/src/main/java/org/openrewrite/table/SourcesFileResults.java @@ -39,7 +39,7 @@ public static class Row { String afterSourcePath; @Column(displayName = "Parent of the recipe that made changes", - description = "In a hierarchical recipe, the parent of the recipe that made a change. Empty if" + + description = "In a hierarchical recipe, the parent of the recipe that made a change. Empty if " + "this is the root of a hierarchy or if the recipe is not hierarchical at all.") String parentRecipe; From 62e960eaea9895af1e0f6cb6493fca25f2e613b2 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 29 Aug 2023 09:46:57 -0700 Subject: [PATCH 198/447] Maintain cursor state when visiting javadoc comments --- .../src/main/java/org/openrewrite/java/JavaVisitor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index 41cd05b02d4..016f2aa941e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -162,13 +162,14 @@ public J visitStatement(Statement statement, P p) { @SuppressWarnings("unused") public Space visitSpace(Space space, Space.Location loc, P p) { + //noinspection ConstantValue return space == Space.EMPTY || space == Space.SINGLE_SPACE || space == null ? space : space.withComments(ListUtils.map(space.getComments(), comment -> { if (comment instanceof Javadoc) { if (javadocVisitor == null) { javadocVisitor = getJavadocVisitor(); } - return (Comment) javadocVisitor.visit((Javadoc) comment, p); + return (Comment) javadocVisitor.visit((Javadoc) comment, p, getCursor()); } return comment; })); From d2ea6c9d297756fc2abf5f6c5118e8eb091ba79f Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 29 Aug 2023 10:58:18 -0700 Subject: [PATCH 199/447] Maintain cursor state when visiting javadoc comments --- .../openrewrite/java/tree/JavadocTest.java | 3 +- .../org/openrewrite/java/JavadocVisitor.java | 14 +++++----- .../org/openrewrite/java/RenameVariable.java | 7 ++--- .../openrewrite/java/UnwrapParentheses.java | 3 +- .../java/format/WrappingAndBracesVisitor.java | 2 +- .../java/search/FindMissingTypes.java | 28 ++++++++++++++----- 6 files changed, 34 insertions(+), 23 deletions(-) diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java index cf4c34aa815..d9272f2902c 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java @@ -21,7 +21,7 @@ import static org.openrewrite.java.Assertions.java; -@SuppressWarnings({"JavadocDeclaration", "TrailingWhitespacesInTextBlock", "TextBlockMigration"}) +@SuppressWarnings({"JavadocDeclaration", "TrailingWhitespacesInTextBlock", "TextBlockMigration", "RedundantThrows", "ConcatenationWithEmptyString"}) class JavadocTest implements RewriteTest { @SuppressWarnings("JavadocReference") @@ -1472,7 +1472,6 @@ interface Test { ); } - @SuppressWarnings("JavadocBlankLines") @Issue("https://github.com/openrewrite/rewrite/issues/2046") @Test void trailingWhitespaceAndMultilineMargin() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavadocVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavadocVisitor.java index 96c48e8fa7a..1e74cbf478b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavadocVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavadocVisitor.java @@ -104,7 +104,7 @@ public Javadoc visitInheritDoc(Javadoc.InheritDoc inheritDoc, P p) { public Javadoc visitInlinedValue(Javadoc.InlinedValue inlinedValue, P p) { Javadoc.InlinedValue i = inlinedValue; i = i.withSpaceBeforeTree(ListUtils.map(i.getSpaceBeforeTree(), s -> visit(s, p))); - i = i.withTree(javaVisitor.visit(i.getTree(), p)); + i = i.withTree(javaVisitor.visit(i.getTree(), p, getCursor())); i = i.withEndBrace(ListUtils.map(i.getEndBrace(), b -> visit(b, p))); return i; } @@ -144,7 +144,7 @@ public Javadoc visitParameter(Javadoc.Parameter parameter, P p) { public Javadoc visitProvides(Javadoc.Provides provides, P p) { Javadoc.Provides pr = provides; pr = pr.withSpaceBeforeServiceType(ListUtils.map(pr.getSpaceBeforeServiceType(), s -> visit(s, p))); - pr = pr.withServiceType(javaVisitor.visit(pr.getServiceType(), p)); + pr = pr.withServiceType(javaVisitor.visit(pr.getServiceType(), p, getCursor())); pr = pr.withDescription(ListUtils.map(pr.getDescription(), d -> visit(d, p))); return pr; } @@ -182,8 +182,8 @@ public Javadoc visitSerialData(Javadoc.SerialData serialData, P p) { public Javadoc visitSerialField(Javadoc.SerialField serialField, P p) { Javadoc.SerialField s = serialField; s = s.withMarkers(visitMarkers(s.getMarkers(), p)); - s = s.withName((J.Identifier) javaVisitor.visit(s.getName(), p)); - s = s.withType(javaVisitor.visit(s.getType(), p)); + s = s.withName((J.Identifier) javaVisitor.visit(s.getName(), p, getCursor())); + s = s.withType(javaVisitor.visit(s.getType(), p, getCursor())); s = s.withDescription(ListUtils.map(s.getDescription(), desc -> visit(desc, p))); return s; } @@ -218,7 +218,7 @@ public Javadoc visitText(Javadoc.Text text, P p) { public Javadoc visitThrows(Javadoc.Throws aThrows, P p) { Javadoc.Throws e = aThrows; e = e.withMarkers(visitMarkers(e.getMarkers(), p)); - e = e.withExceptionName(javaVisitor.visit(e.getExceptionName(), p)); + e = e.withExceptionName(javaVisitor.visit(e.getExceptionName(), p, getCursor())); e = e.withSpaceBeforeExceptionName(ListUtils.map(e.getSpaceBeforeExceptionName(), s -> visit(s, p))); e = e.withDescription(ListUtils.map(e.getDescription(), desc -> visit(desc, p))); return e; @@ -240,7 +240,7 @@ public Javadoc visitUnknownInline(Javadoc.UnknownInline unknownInline, P p) { public Javadoc visitUses(Javadoc.Uses uses, P p) { Javadoc.Uses u = uses; u = u.withBeforeServiceType(ListUtils.map(u.getBeforeServiceType(), b -> visit(b, p))); - u = u.withServiceType(javaVisitor.visit(u.getServiceType(), p)); + u = u.withServiceType(javaVisitor.visit(u.getServiceType(), p, getCursor())); u = u.withDescription(ListUtils.map(u.getDescription(), d -> visit(d, p))); return u; } @@ -254,7 +254,7 @@ public Javadoc visitVersion(Javadoc.Version version, P p) { public Javadoc visitReference(Javadoc.Reference reference, P p) { Javadoc.Reference r = reference; - r = r.withTree(javaVisitor.visit(r.getTree(), p)); + r = r.withTree(javaVisitor.visit(r.getTree(), p, getCursor())); r = r.withLineBreaks(ListUtils.map(r.getLineBreaks(), l -> visit(l, p))); return r; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RenameVariable.java b/rewrite-java/src/main/java/org/openrewrite/java/RenameVariable.java index 442ff99417b..3f23193eada 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RenameVariable.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RenameVariable.java @@ -87,7 +87,7 @@ public J.Identifier visitIdentifier(J.Identifier ident, P p) { } } else if (currentNameScope.size() == 1 && isVariableName(parent.getValue(), ident)) { if (parent.getValue() instanceof J.VariableDeclarations.NamedVariable) { - J variableDeclaration = parent.getParentTreeCursor().getValue(); + Tree variableDeclaration = parent.getParentTreeCursor().getValue(); J maybeParameter = getCursor().dropParentUntil(is -> is instanceof JavaSourceFile || is instanceof J.ClassDeclaration || is instanceof J.MethodDeclaration).getValue(); if (maybeParameter instanceof J.MethodDeclaration) { J.MethodDeclaration methodDeclaration = (J.MethodDeclaration) maybeParameter; @@ -119,10 +119,7 @@ private boolean isVariableName(Object value, J.Identifier ident) { } else if(value instanceof J.VariableDeclarations) { J.VariableDeclarations v = (J.VariableDeclarations) value; return ident != v.getTypeExpression(); - } else if(value instanceof J.ParameterizedType) { - return false; - } - return true; + } else return !(value instanceof J.ParameterizedType); } /** diff --git a/rewrite-java/src/main/java/org/openrewrite/java/UnwrapParentheses.java b/rewrite-java/src/main/java/org/openrewrite/java/UnwrapParentheses.java index 1204e5cd076..4ca6c1c7f66 100755 --- a/rewrite-java/src/main/java/org/openrewrite/java/UnwrapParentheses.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/UnwrapParentheses.java @@ -16,6 +16,7 @@ package org.openrewrite.java; import org.openrewrite.Cursor; +import org.openrewrite.Tree; import org.openrewrite.java.tree.J; public class UnwrapParentheses<P> extends JavaVisitor<P> { @@ -41,7 +42,7 @@ public static boolean isUnwrappable(Cursor parensScope) { if (!(parensScope.getValue() instanceof J.Parentheses)) { return false; } - J parent = parensScope.getParentTreeCursor().getValue(); + Tree parent = parensScope.getParentTreeCursor().getValue(); if (parent instanceof J.If || parent instanceof J.Switch || parent instanceof J.Synchronized || diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/WrappingAndBracesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/WrappingAndBracesVisitor.java index ea22d72aba2..a3ee7d7434b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/WrappingAndBracesVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/WrappingAndBracesVisitor.java @@ -46,7 +46,7 @@ public WrappingAndBracesVisitor(WrappingAndBracesStyle style, @Nullable Tree sto @Override public Statement visitStatement(Statement statement, P p) { Statement j = super.visitStatement(statement, p); - J parentTree = getCursor().getParentTreeCursor().getValue(); + Tree parentTree = getCursor().getParentTreeCursor().getValue(); if (parentTree instanceof J.Block && !(j instanceof J.EnumValueSet)) { // for `J.EnumValueSet` the prefix is on the enum constants if (!j.getPrefix().getWhitespace().contains("\n")) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java index d3eec009b5b..b1dc47a9192 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java @@ -18,10 +18,14 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.openrewrite.*; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.JavadocVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Javadoc; import org.openrewrite.marker.Marker; import org.openrewrite.marker.SearchResult; @@ -88,6 +92,16 @@ static class FindMissingTypesVisitor extends JavaIsoVisitor<ExecutionContext> { private final Set<JavaType> seenTypes = new HashSet<>(); + @Override + protected JavadocVisitor<ExecutionContext> getJavadocVisitor() { + return new JavadocVisitor<ExecutionContext>(new JavaVisitor<>()) { + @Override + public @Nullable Javadoc visit(@Nullable Tree tree, ExecutionContext executionContext) { + return (Javadoc) tree; + } + }; + } + @Override public J.Identifier visitIdentifier(J.Identifier identifier, ExecutionContext ctx) { // The non-nullability of J.Identifier.getType() in our AST is a white lie @@ -103,7 +117,7 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations J.VariableDeclarations.NamedVariable v = super.visitVariable(variable, ctx); if (v == variable) { JavaType.Variable variableType = v.getVariableType(); - if (!isWellFormedType(variableType, seenTypes) && !isAllowedToHaveUnknownType(variable)) { + if (!isWellFormedType(variableType, seenTypes) && !isAllowedToHaveUnknownType()) { v = SearchResult.found(v, "Variable type is missing or malformed"); } else if (variableType != null && !variableType.getName().equals(v.getSimpleName())) { v = SearchResult.found(v, "type information has a different variable name '" + variableType.getName() + "'"); @@ -112,7 +126,7 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations return v; } - private boolean isAllowedToHaveUnknownType(J.VariableDeclarations.NamedVariable variable) { + private boolean isAllowedToHaveUnknownType() { Cursor parent = getCursor().getParent(); while (parent != null && parent.getParent() != null && !(parent.getParentTreeCursor().getValue() instanceof J.ClassDeclaration)) { parent = parent.getParentTreeCursor(); @@ -222,24 +236,24 @@ private boolean isMethodInvocationName() { } private boolean isFieldAccess(J.Identifier ident) { - J value = getCursor().getParentTreeCursor().getValue(); + Tree value = getCursor().getParentTreeCursor().getValue(); return value instanceof J.FieldAccess && (ident == ((J.FieldAccess) value).getName() || ident == ((J.FieldAccess) value).getTarget() && !((J.FieldAccess) value).getSimpleName().equals("class")); } private boolean isBeingDeclared(J.Identifier ident) { - J value = getCursor().getParentTreeCursor().getValue(); + Tree value = getCursor().getParentTreeCursor().getValue(); return value instanceof J.VariableDeclarations.NamedVariable && ident == ((J.VariableDeclarations.NamedVariable) value).getName(); } private boolean isParameterizedType(J.Identifier ident) { - J value = getCursor().getParentTreeCursor().getValue(); + Tree value = getCursor().getParentTreeCursor().getValue(); return value instanceof J.ParameterizedType && ident == ((J.ParameterizedType) value).getClazz(); } private boolean isNewClass(J.Identifier ident) { - J value = getCursor().getParentTreeCursor().getValue(); + Tree value = getCursor().getParentTreeCursor().getValue(); return value instanceof J.NewClass && ident == ((J.NewClass) value).getClazz(); } @@ -249,7 +263,7 @@ private boolean isTypeParameter() { } private boolean isMemberReference(J.Identifier ident) { - J value = getCursor().getParentTreeCursor().getValue(); + Tree value = getCursor().getParentTreeCursor().getValue(); return value instanceof J.MemberReference && ident == ((J.MemberReference) value).getReference(); } From 1834d4e3fd3253a6f604f7b34cfc14d02ee66662 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh <jonathan.leitschuh@gmail.com> Date: Tue, 29 Aug 2023 15:15:36 -0400 Subject: [PATCH 200/447] Fix the Javadoc Cursor path not being restored (#3514) Signed-off-by: Jonathan Leitschuh <Jonathan.Leitschuh@gmail.com> --- .../org/openrewrite/java/ChangeTypeTest.java | 4 +++ .../java/VariableNameUtilsTest.java | 4 +++ .../org/openrewrite/java/JavaVisitor.java | 5 +++- .../org/openrewrite/java/JavadocPrinter.java | 14 ++++----- .../org/openrewrite/java/JavadocVisitor.java | 30 ++++++++++++++----- .../java/search/FindMissingTypes.java | 24 +++++++-------- 6 files changed, 53 insertions(+), 28 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java index f10b2320093..a4b993d2382 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java @@ -613,6 +613,8 @@ void parameterizedType() { """ import a.A1; + import java.util.Map; + public class B { Map<A1, A1> m; } @@ -620,6 +622,8 @@ public class B { """ import a.A2; + import java.util.Map; + public class B { Map<A2, A2> m; } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/VariableNameUtilsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/VariableNameUtilsTest.java index f0f005fa104..407716661d9 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/VariableNameUtilsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/VariableNameUtilsTest.java @@ -486,6 +486,8 @@ public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext ctx) { })), java( """ + import java.util.function.Consumer; + @SuppressWarnings("all") class Test { void m() { @@ -499,6 +501,8 @@ void m() { } """, """ + import java.util.function.Consumer; + @SuppressWarnings("all") class Test { void m() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index 016f2aa941e..e8667897c50 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -169,7 +169,10 @@ public Space visitSpace(Space space, Space.Location loc, P p) { if (javadocVisitor == null) { javadocVisitor = getJavadocVisitor(); } - return (Comment) javadocVisitor.visit((Javadoc) comment, p, getCursor()); + Cursor previous = javadocVisitor.getCursor(); + Comment c = (Comment) javadocVisitor.visit((Javadoc) comment, p, getCursor()); + javadocVisitor.setCursor(previous); + return c; } return comment; })); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java index ca92c1aab03..88b5f9dff78 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java @@ -145,7 +145,7 @@ public Javadoc visitInlinedValue(Javadoc.InlinedValue value, PrintOutputCapture< beforeSyntax(value, p); p.append("{@value"); visit(value.getSpaceBeforeTree(), p); - javaVisitor.visit(value.getTree(), p); + javaVisitorVisit(value.getTree(), p); visit(value.getEndBrace(), p); afterSyntax(value, p); return value; @@ -197,7 +197,7 @@ public Javadoc visitProvides(Javadoc.Provides provides, PrintOutputCapture<P> p) beforeSyntax(provides, p); p.append("@provides"); visit(provides.getSpaceBeforeServiceType(), p); - javaVisitor.visit(provides.getServiceType(), p); + javaVisitorVisit(provides.getServiceType(), p); visit(provides.getDescription(), p); afterSyntax(provides, p); return provides; @@ -245,8 +245,8 @@ public Javadoc visitSerialData(Javadoc.SerialData serialData, PrintOutputCapture public Javadoc visitSerialField(Javadoc.SerialField serialField, PrintOutputCapture<P> p) { beforeSyntax(serialField, p); p.append("@serialField"); - javaVisitor.visit(serialField.getName(), p); - javaVisitor.visit(serialField.getType(), p); + javaVisitorVisit(serialField.getName(), p); + javaVisitorVisit(serialField.getType(), p); visit(serialField.getDescription(), p); afterSyntax(serialField, p); return serialField; @@ -298,7 +298,7 @@ public Javadoc visitThrows(Javadoc.Throws aThrows, PrintOutputCapture<P> p) { beforeSyntax(aThrows, p); p.append(aThrows.isThrowsKeyword() ? "@throws" : "@exception"); visit(aThrows.getSpaceBeforeExceptionName(), p); - javaVisitor.visit(aThrows.getExceptionName(), p); + javaVisitorVisit(aThrows.getExceptionName(), p); visit(aThrows.getDescription(), p); afterSyntax(aThrows, p); return aThrows; @@ -328,7 +328,7 @@ public Javadoc visitUses(Javadoc.Uses uses, PrintOutputCapture<P> p) { beforeSyntax(uses, p); p.append("@uses"); visit(uses.getBeforeServiceType(), p); - javaVisitor.visit(uses.getServiceType(), p); + javaVisitorVisit(uses.getServiceType(), p); visit(uses.getDescription(), p); afterSyntax(uses, p); return uses; @@ -355,7 +355,7 @@ public void visit(@Nullable List<? extends Javadoc> nodes, PrintOutputCapture<P> public Javadoc visitReference(Javadoc.Reference reference, PrintOutputCapture<P> p) { getCursor().putMessageOnFirstEnclosing(Javadoc.DocComment.class, "JAVADOC_LINE_BREAKS", reference.getLineBreaks()); getCursor().putMessageOnFirstEnclosing(Javadoc.DocComment.class, "JAVADOC_LINE_BREAK_INDEX", 0); - javaVisitor.visit(reference.getTree(), p, getCursor()); + javaVisitorVisit(reference.getTree(), p); afterSyntax(reference, p); return reference; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavadocVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavadocVisitor.java index 1e74cbf478b..9fe2a0fa794 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavadocVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavadocVisitor.java @@ -15,18 +15,32 @@ */ package org.openrewrite.java; +import org.openrewrite.Cursor; +import org.openrewrite.Tree; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.Javadoc; public class JavadocVisitor<P> extends TreeVisitor<Javadoc, P> { - protected JavaVisitor<P> javaVisitor; + /** + * Don't use directly. Use {@link #javaVisitorVisit(Tree, Object)} instead. + */ + private final JavaVisitor<P> javaVisitor; public JavadocVisitor(JavaVisitor<P> javaVisitor) { this.javaVisitor = javaVisitor; } + @Nullable + protected J javaVisitorVisit(@Nullable Tree tree, P p) { + Cursor previous = javaVisitor.getCursor(); + J j = javaVisitor.visit(tree, p, getCursor()); + javaVisitor.setCursor(previous); + return j; + } + public Javadoc visitAttribute(Javadoc.Attribute attribute, P p) { Javadoc.Attribute a = attribute; a = a.withSpaceBeforeEqual(ListUtils.map(a.getSpaceBeforeEqual(), v -> visit(v, p))); @@ -104,7 +118,7 @@ public Javadoc visitInheritDoc(Javadoc.InheritDoc inheritDoc, P p) { public Javadoc visitInlinedValue(Javadoc.InlinedValue inlinedValue, P p) { Javadoc.InlinedValue i = inlinedValue; i = i.withSpaceBeforeTree(ListUtils.map(i.getSpaceBeforeTree(), s -> visit(s, p))); - i = i.withTree(javaVisitor.visit(i.getTree(), p, getCursor())); + i = i.withTree(javaVisitorVisit(i.getTree(), p)); i = i.withEndBrace(ListUtils.map(i.getEndBrace(), b -> visit(b, p))); return i; } @@ -144,7 +158,7 @@ public Javadoc visitParameter(Javadoc.Parameter parameter, P p) { public Javadoc visitProvides(Javadoc.Provides provides, P p) { Javadoc.Provides pr = provides; pr = pr.withSpaceBeforeServiceType(ListUtils.map(pr.getSpaceBeforeServiceType(), s -> visit(s, p))); - pr = pr.withServiceType(javaVisitor.visit(pr.getServiceType(), p, getCursor())); + pr = pr.withServiceType(javaVisitorVisit(pr.getServiceType(), p)); pr = pr.withDescription(ListUtils.map(pr.getDescription(), d -> visit(d, p))); return pr; } @@ -182,8 +196,8 @@ public Javadoc visitSerialData(Javadoc.SerialData serialData, P p) { public Javadoc visitSerialField(Javadoc.SerialField serialField, P p) { Javadoc.SerialField s = serialField; s = s.withMarkers(visitMarkers(s.getMarkers(), p)); - s = s.withName((J.Identifier) javaVisitor.visit(s.getName(), p, getCursor())); - s = s.withType(javaVisitor.visit(s.getType(), p, getCursor())); + s = s.withName((J.Identifier) javaVisitorVisit(s.getName(), p)); + s = s.withType(javaVisitorVisit(s.getType(), p)); s = s.withDescription(ListUtils.map(s.getDescription(), desc -> visit(desc, p))); return s; } @@ -218,7 +232,7 @@ public Javadoc visitText(Javadoc.Text text, P p) { public Javadoc visitThrows(Javadoc.Throws aThrows, P p) { Javadoc.Throws e = aThrows; e = e.withMarkers(visitMarkers(e.getMarkers(), p)); - e = e.withExceptionName(javaVisitor.visit(e.getExceptionName(), p, getCursor())); + e = e.withExceptionName(javaVisitorVisit(e.getExceptionName(), p)); e = e.withSpaceBeforeExceptionName(ListUtils.map(e.getSpaceBeforeExceptionName(), s -> visit(s, p))); e = e.withDescription(ListUtils.map(e.getDescription(), desc -> visit(desc, p))); return e; @@ -240,7 +254,7 @@ public Javadoc visitUnknownInline(Javadoc.UnknownInline unknownInline, P p) { public Javadoc visitUses(Javadoc.Uses uses, P p) { Javadoc.Uses u = uses; u = u.withBeforeServiceType(ListUtils.map(u.getBeforeServiceType(), b -> visit(b, p))); - u = u.withServiceType(javaVisitor.visit(u.getServiceType(), p, getCursor())); + u = u.withServiceType(javaVisitorVisit(u.getServiceType(), p)); u = u.withDescription(ListUtils.map(u.getDescription(), d -> visit(d, p))); return u; } @@ -254,7 +268,7 @@ public Javadoc visitVersion(Javadoc.Version version, P p) { public Javadoc visitReference(Javadoc.Reference reference, P p) { Javadoc.Reference r = reference; - r = r.withTree(javaVisitor.visit(r.getTree(), p, getCursor())); + r = r.withTree(javaVisitorVisit(r.getTree(), p)); r = r.withLineBreaks(ListUtils.map(r.getLineBreaks(), l -> visit(l, p))); return r; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java index b1dc47a9192..0d74f97a9a5 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java @@ -62,7 +62,10 @@ public static List<MissingTypeResult> findMissingTypes(J j) { public <M extends Marker> M visitMarker(Marker marker, List<MissingTypeResult> missingTypeResults) { if (marker instanceof SearchResult) { String message = ((SearchResult) marker).getDescription(); - String path = getCursor().getPathAsStream().filter(J.class::isInstance).map(t -> t.getClass().getSimpleName()).collect(Collectors.joining("->")); + String path = getCursor() + .getPathAsStream(j -> j instanceof J || j instanceof Javadoc) + .map(t -> t.getClass().getSimpleName()) + .collect(Collectors.joining("->")); J j = getCursor().firstEnclosing(J.class); String printedTree; if (getCursor().firstEnclosing(JavaSourceFile.class) != null) { @@ -92,16 +95,6 @@ static class FindMissingTypesVisitor extends JavaIsoVisitor<ExecutionContext> { private final Set<JavaType> seenTypes = new HashSet<>(); - @Override - protected JavadocVisitor<ExecutionContext> getJavadocVisitor() { - return new JavadocVisitor<ExecutionContext>(new JavaVisitor<>()) { - @Override - public @Nullable Javadoc visit(@Nullable Tree tree, ExecutionContext executionContext) { - return (Javadoc) tree; - } - }; - } - @Override public J.Identifier visitIdentifier(J.Identifier identifier, ExecutionContext ctx) { // The non-nullability of J.Identifier.getType() in our AST is a white lie @@ -209,7 +202,8 @@ public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext executionC private boolean isAllowedToHaveNullType(J.Identifier ident) { return inPackageDeclaration() || inImport() || isClassName() || isMethodName() || isMethodInvocationName() || isFieldAccess(ident) || isBeingDeclared(ident) || isParameterizedType(ident) - || isNewClass(ident) || isTypeParameter() || isMemberReference(ident) || isCaseLabel() || isLabel() || isAnnotationField(ident); + || isNewClass(ident) || isTypeParameter() || isMemberReference(ident) || isCaseLabel() || isLabel() || isAnnotationField(ident) + || isInJavaDoc(ident); } private boolean inPackageDeclaration() { @@ -268,6 +262,12 @@ private boolean isMemberReference(J.Identifier ident) { ident == ((J.MemberReference) value).getReference(); } + private boolean isInJavaDoc(J.Identifier ident) { + Tree value = getCursor().getParentTreeCursor().getValue(); + return value instanceof Javadoc.Reference && + ident == ((Javadoc.Reference) value).getTree(); + } + private boolean isCaseLabel() { return getCursor().getParentTreeCursor().getValue() instanceof J.Case; } From 5f89574673272db01eb089533144a97ddf3cb7bf Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 30 Aug 2023 10:52:14 -0700 Subject: [PATCH 201/447] Add ChangeExtraProperty recipe --- .../gradle/ChangeExtraProperty.java | 100 ++++++++++++++++++ .../gradle/ChangeExtraPropertyTest.java | 73 +++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeExtraProperty.java create mode 100644 rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeExtraPropertyTest.java diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeExtraProperty.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeExtraProperty.java new file mode 100644 index 00000000000..1227c26d555 --- /dev/null +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeExtraProperty.java @@ -0,0 +1,100 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.gradle; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.gradle.search.FindGradleProject; +import org.openrewrite.groovy.GroovyIsoVisitor; +import org.openrewrite.java.tree.J; + +import java.util.Objects; + + +@Value +@EqualsAndHashCode(callSuper = true) +public class ChangeExtraProperty extends Recipe { + + @Override + public String getDisplayName() { + return "Change Extra Property"; + } + + @Override + public String getDescription() { + return "Gradle's [ExtraPropertiesExtension](https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html) " + + "is a commonly used mechanism for setting arbitrary key/value pairs on a project. " + + "This recipe will change the value of a property with the given key name if that key can be found. " + + "It assumes that the value being set is a String literal. " + + "Does not add the value if it does not already exist."; + } + + @Option(displayName = "Key", + description = "The key of the property to change.", + example = "foo") + String key; + + @Option(displayName = "Value", + description = "The new value to set. The value will be treated the contents of a string literal.", + example = "bar") + String value; + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return Preconditions.check(new FindGradleProject(FindGradleProject.SearchCriteria.File).getVisitor(), new GroovyIsoVisitor<ExecutionContext>() { + @Override + public J.Assignment visitAssignment(J.Assignment as, ExecutionContext executionContext) { + if(!(as.getAssignment() instanceof J.Literal)) { + return as; + } + if(as.getVariable() instanceof J.Identifier) { + if(!Objects.equals(key, ((J.Identifier) as.getVariable()).getSimpleName())) { + return as; + } + J.MethodInvocation m = getCursor().firstEnclosing(J.MethodInvocation.class); + if(m == null || !m.getSimpleName().equals("ext")) { + return as; + } + as = updateAssignment(as); + } else if(as.getVariable() instanceof J.FieldAccess) { + J.FieldAccess var = (J.FieldAccess) as.getVariable(); + if(!Objects.equals(key, var.getSimpleName())) { + return as; + } + if((var.getTarget() instanceof J.Identifier && ((J.Identifier) var.getTarget()).getSimpleName().equals("ext")) + || (var.getTarget() instanceof J.FieldAccess && ((J.FieldAccess) var.getTarget()).getSimpleName().equals("ext")) ) { + as = updateAssignment(as); + } + } + + return as; + } + }); + } + + private J.Assignment updateAssignment(J.Assignment as) { + if(!(as.getAssignment() instanceof J.Literal)) { + return as; + } + J.Literal asVal = (J.Literal) as.getAssignment(); + if(Objects.equals(value, asVal.getValue())) { + return as; + } + return as.withAssignment(asVal.withValue(value) + .withValueSource("\"" + value + "\"")); + } +} diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeExtraPropertyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeExtraPropertyTest.java new file mode 100644 index 00000000000..e193c9bc112 --- /dev/null +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeExtraPropertyTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.gradle; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.gradle.Assertions.buildGradle; + +@SuppressWarnings("GroovyAssignabilityCheck") +public class ChangeExtraPropertyTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ChangeExtraProperty("foo", "baz")); + } + + @Test + void closureStyle() { + rewriteRun( + buildGradle( + """ + buildscript { + ext { + foo = "bar" + } + } + ext { + foo = "bar" + } + """, + """ + buildscript { + ext { + foo = "baz" + } + } + ext { + foo = "baz" + } + """) + ); + } + + @Test + void propertyAssignment() { + rewriteRun( + buildGradle( + """ + project.ext.foo = "bar" + ext.foo = "bar" + """, + """ + project.ext.foo = "baz" + ext.foo = "baz" + """) + ); + } +} From 1a5832f7cd61c5b3a5c10fe3e4f69abc66fdc9c0 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 31 Aug 2023 07:47:51 +0200 Subject: [PATCH 202/447] JSON: Support upper case `E` in exponential numbers Fixes: #3518 --- .../org/openrewrite/json/internal/JsonParserVisitor.java | 2 +- .../test/java/org/openrewrite/json/JsonParserTest.java | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java b/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java index a2bb719c24d..fd25faba297 100644 --- a/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java @@ -211,7 +211,7 @@ public Json.Literal visitNumber(JSON5Parser.NumberContext ctx) { source.append(text); if (text.startsWith("0x")) { value = Long.decode(text) * sign.get(); - } else if (text.contains(".") || text.contains("e")) { + } else if (text.contains(".") || text.contains("e") || text.contains("E")) { value = Double.parseDouble(text) * sign.get(); } else { try { diff --git a/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java b/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java index 5064c73fec4..eacb3f78090 100644 --- a/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java +++ b/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java @@ -69,7 +69,14 @@ void booleanLiteral() { @Test void doubleLiteralExpSigned() { rewriteRun( - json("-1.e3") + json("-1e3") + ); + } + + @Test + void doubleLiteralExpSignedUpperCase() { + rewriteRun( + json("1E-3") ); } From 990c4479f3890799cf9e3cbe61e5c163dcfaf1b6 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 31 Aug 2023 07:59:47 +0200 Subject: [PATCH 203/447] JSON: Support upper case `E` in exponential numbers Fixes: #3519 --- .../org/openrewrite/json/internal/JsonParserVisitor.java | 7 ++++++- .../src/test/java/org/openrewrite/json/JsonParserTest.java | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java b/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java index fd25faba297..37f6c8717cf 100644 --- a/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java @@ -26,6 +26,7 @@ import org.openrewrite.json.tree.*; import org.openrewrite.marker.Markers; +import java.math.BigInteger; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.ArrayList; @@ -217,7 +218,11 @@ public Json.Literal visitNumber(JSON5Parser.NumberContext ctx) { try { value = Integer.parseInt(text) * sign.get(); } catch (NumberFormatException e) { - value = Long.parseLong(text) * sign.get(); + try { + value = Long.parseLong(text) * sign.get(); + } catch (NumberFormatException e1) { + value = sign.get() == 1 ? new BigInteger(text, 10) : new BigInteger("-" + text, 10); + } } } } diff --git a/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java b/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java index eacb3f78090..854a74974e7 100644 --- a/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java +++ b/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java @@ -80,6 +80,13 @@ void doubleLiteralExpSignedUpperCase() { ); } + @Test + void bigInteger() { + rewriteRun( + json("-10000000000000000999") + ); + } + @Test void array() { rewriteRun( From d5071a5f0fda751f6a5f4f075c6dedfd81776410 Mon Sep 17 00:00:00 2001 From: Nick McKinney <mckinneynicholas@gmail.com> Date: Thu, 31 Aug 2023 14:46:17 -0400 Subject: [PATCH 204/447] RewriteTest / YamlResourceLoader Enhancements (#3517) * YamlResourceLoader now uses jackson to try to instantiate imperative recipes with zero args if basic reflection fails. And, reverted change to RecipeSpec which made it scanRuntimeClasspath unnecessarily for yaml recipes. Fixes #3414 * adding RewriteTest/RecipeSpec recipeFromResources option to scan all rewrite yamls on classpath (ignoring classes) fixes #3096 * license fixes * NullUtils::findNonNullFields now checks for required Options * unrolling stream apis / polish * explicit nullable annotations now consistently exclude a field for NullUtils::findNonNullFields * fixing nullable option not annotated as non-required * fixing nullable option not annotated as non-required --- .../config/ClasspathScanningLoader.java | 10 + .../openrewrite/config/DeclarativeRecipe.java | 10 +- .../org/openrewrite/config/Environment.java | 4 + .../config/YamlResourceLoader.java | 28 ++- .../openrewrite/internal/lang/NullUtils.java | 47 +++-- .../org/openrewrite/text/FindAndReplace.java | 1 + .../org/openrewrite/RecipeLifecycleTest.java | 190 ++++++++++++++++++ .../META-INF/rewrite/test-sample-a.yml | 22 ++ .../META-INF/rewrite/test-sample-b.yml | 23 +++ .../plugins/AddSettingsPluginRepository.java | 1 + .../java/org/openrewrite/test/RecipeSpec.java | 10 +- 11 files changed, 319 insertions(+), 27 deletions(-) create mode 100644 rewrite-core/src/test/resources/META-INF/rewrite/test-sample-a.yml create mode 100644 rewrite-core/src/test/resources/META-INF/rewrite/test-sample-b.yml diff --git a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java index 887f01e72a0..1884cd3d377 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java @@ -99,6 +99,16 @@ public ClasspathScanningLoader(Path jar, Properties properties, Collection<? ext .acceptPaths("META-INF/rewrite"), properties, dependencyResourceLoaders, classLoader); } + public static ClasspathScanningLoader onlyYaml(Properties properties) { + ClasspathScanningLoader classpathScanningLoader = new ClasspathScanningLoader(); + classpathScanningLoader.scanYaml(new ClassGraph().acceptPaths("META-INF/rewrite"), + properties, emptyList(), null); + return classpathScanningLoader; + } + + private ClasspathScanningLoader() { + } + /** * This must be called _after_ scanClasses or the descriptors of declarative recipes will be missing any * non-declarative recipes they depend on that would be discovered by scanClasses diff --git a/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java b/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java index c4c01c3d81f..24c38790a2c 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java @@ -120,14 +120,8 @@ public void addUninitialized(Recipe recipe) { uninitializedRecipes.add(recipe); } - public void addUninitialized(String recipeName, @Nullable ClassLoader classLoader) { - try { - uninitializedRecipes.add((Recipe) Class.forName(recipeName, true, classLoader != null ? classLoader : this.getClass().getClassLoader()) - .getDeclaredConstructor() - .newInstance()); - } catch (Exception e) { - uninitializedRecipes.add(new DeclarativeRecipe.LazyLoadedRecipe(recipeName)); - } + public void addUninitialized(String recipeName) { + uninitializedRecipes.add(new DeclarativeRecipe.LazyLoadedRecipe(recipeName)); } public void addValidation(Validated<Object> validated) { diff --git a/rewrite-core/src/main/java/org/openrewrite/config/Environment.java b/rewrite-core/src/main/java/org/openrewrite/config/Environment.java index 3765fd51139..5e593c2e0b6 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/Environment.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/Environment.java @@ -229,6 +229,10 @@ public Builder scanClassLoader(ClassLoader classLoader) { return load(new ClasspathScanningLoader(properties, classLoader)); } + public Builder scanYamlResources() { + return load(ClasspathScanningLoader.onlyYaml(properties)); + } + /** * @param jar A path to a jar file to scan. * @param classLoader A classloader that is populated with the transitive dependencies of the jar. diff --git a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java index 36af0b68ccb..4899de59ced 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java @@ -260,15 +260,29 @@ private void loadRecipe(@Language("markdown") String name, int i, Object recipeData) { if (recipeData instanceof String) { - recipe.addUninitialized((String) recipeData, classLoader); + String recipeName = (String) recipeData; + try { + // first try an explicitly-declared zero-arg constructor + recipe.addUninitialized((Recipe) Class.forName(recipeName, true, + classLoader == null ? this.getClass().getClassLoader() : classLoader) + .getDeclaredConstructor() + .newInstance()); + } catch (ReflectiveOperationException reflectiveOperationException) { + try { + // then try jackson + recipe.addUninitialized(instantiateRecipe(recipeName, new HashMap<>())); + } catch (IllegalArgumentException illegalArgumentException) { + // else, it's probably declarative + recipe.addUninitialized(recipeName); + } + } } else if (recipeData instanceof Map) { Map.Entry<String, Object> nameAndConfig = ((Map<String, Object>) recipeData).entrySet().iterator().next(); try { if (nameAndConfig.getValue() instanceof Map) { - Map<Object, Object> withJsonType = new HashMap<>((Map<String, Object>) nameAndConfig.getValue()); - withJsonType.put("@c", nameAndConfig.getKey()); try { - recipe.addUninitialized(mapper.convertValue(withJsonType, Recipe.class)); + recipe.addUninitialized(instantiateRecipe(nameAndConfig.getKey(), + (Map<String, Object>) nameAndConfig.getValue())); } catch (IllegalArgumentException e) { if (e.getCause() instanceof InvalidTypeIdException) { recipe.addValidation(Validated.invalid(nameAndConfig.getKey(), @@ -299,6 +313,12 @@ private void loadRecipe(@Language("markdown") String name, } } + private Recipe instantiateRecipe(String recipeName, Map<String, Object> args) throws IllegalArgumentException { + Map<Object, Object> withJsonType = new HashMap<>(args); + withJsonType.put("@c", recipeName); + return mapper.convertValue(withJsonType, Recipe.class); + } + @Override public Collection<RecipeDescriptor> listRecipeDescriptors() { return listRecipeDescriptors(emptyList(), listContributors(), listRecipeExamples()); diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/lang/NullUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/lang/NullUtils.java index 12f5f0149d5..fc249fbf538 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/lang/NullUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/lang/NullUtils.java @@ -15,6 +15,9 @@ */ package org.openrewrite.internal.lang; +import org.openrewrite.Option; + +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.*; @@ -48,7 +51,7 @@ public class NullUtils { * <li>org.checkerframework.checker.nullness.qual.NonNull</li> * <li>javax.validation.constraints.NotNull</li> */ - private static List<String> FIELD_LEVEL_NON_NULL_ANNOTATIONS = Arrays.asList( + private static final List<String> FIELD_LEVEL_NON_NULL_ANNOTATIONS = Arrays.asList( "NonNull", "Nonnull", "NotNull" @@ -67,14 +70,17 @@ public class NullUtils { * <li>org.checkerframework.checker.nullness.qual.Nullable</li> * <li>javax.validation.constraints.NotNull</li> */ - private static List<String> FIELD_LEVEL_NULLABLE_ANNOTATIONS = Collections.singletonList( + private static final List<String> FIELD_LEVEL_NULLABLE_ANNOTATIONS = Collections.singletonList( "Nullable" ); /** * The method uses reflection to find all declared fields of a class that have been marked (via commonly used - * annotations) as being Non-Null. This method will also look at the class's package level to see if the API - * for that package is defaulted as Non-Null. ANY annotation that has runtime retention and matches on of the simple + * annotations) as being Non-Null, or a required {@link Option}. + * This method will also look at the class's package level to see if the API + * for that package is defaulted as Non-Null. + * Fields with explicit Nullable annotations will be excluded. + * Any other annotation that has runtime retention and matches one of the simple * annotation names (minus any package) will be considered a match. * * @param _class The class to reflect over @@ -94,8 +100,10 @@ public static List<Field> findNonNullFields(@NonNull Class<?> _class) { List<Field> nonNullFields = new ArrayList<>(fields.length); for (Field field : fields) { field.setAccessible(true); - if(fieldHasNonNullableAnnotation(field) || - (defaultNonNull && !fieldHasNullableAnnotation(field))) { + if (fieldHasNullableAnnotation(field)) { + continue; + } + if (defaultNonNull || fieldHasNonNullableAnnotation(field) || fieldIsRequiredOption(field)) { nonNullFields.add(field); } } @@ -103,14 +111,29 @@ public static List<Field> findNonNullFields(@NonNull Class<?> _class) { return nonNullFields; } + private static boolean fieldIsRequiredOption(Field field) { + Option annotation = field.getAnnotation(Option.class); + if (annotation != null) + return annotation.required(); + return false; + } + private static boolean fieldHasNonNullableAnnotation(Field field) { - return Arrays.stream(field.getDeclaredAnnotations()) - .map(a -> a.annotationType().getSimpleName()) - .anyMatch(FIELD_LEVEL_NON_NULL_ANNOTATIONS::contains); + for (Annotation a : field.getDeclaredAnnotations()) { + String simpleName = a.annotationType().getSimpleName(); + if (FIELD_LEVEL_NON_NULL_ANNOTATIONS.contains(simpleName)) { + return true; + } + } + return false; } private static boolean fieldHasNullableAnnotation(Field field) { - return Arrays.stream(field.getDeclaredAnnotations()) - .map(a -> a.annotationType().getSimpleName()) - .anyMatch(FIELD_LEVEL_NULLABLE_ANNOTATIONS::contains); + for (Annotation a : field.getDeclaredAnnotations()) { + String simpleName = a.annotationType().getSimpleName(); + if (FIELD_LEVEL_NULLABLE_ANNOTATIONS.contains(simpleName)) { + return true; + } + } + return false; } } diff --git a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java index 4e128afe330..3ab12e37588 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java @@ -79,6 +79,7 @@ public class FindAndReplace extends Recipe { "Multiple patterns may be specified, separated by a semicolon `;`. " + "If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " + "When not set, all source files are searched. ", + required = false, example = "**/*.java") @Nullable String filePattern; diff --git a/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java b/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java index 77e2dd41e14..3e6cea62f00 100644 --- a/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java @@ -15,19 +15,25 @@ */ package org.openrewrite; +import lombok.NoArgsConstructor; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; +import org.openrewrite.config.Environment; import org.openrewrite.config.RecipeDescriptor; +import org.openrewrite.config.YamlResourceLoader; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; import org.openrewrite.test.RewriteTest; import org.openrewrite.text.PlainText; import org.openrewrite.text.PlainTextVisitor; +import java.io.ByteArrayInputStream; +import java.net.URI; import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Properties; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -249,4 +255,188 @@ public PlainText visitText(PlainText text, ExecutionContext executionContext) { } }).withName(name); } + + @Test + void canCallImperativeRecipeWithoutArgsFromDeclarative() { + rewriteRun(spec -> spec.recipeFromYaml(""" + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe + displayName: Test Recipe + recipeList: + - org.openrewrite.NoArgRecipe + """, + "test.recipe" + ), + text("Hi", "NoArgRecipeHi")); + } + + @Test + void canCallImperativeRecipeWithUnnecessaryArgsFromDeclarative() { + rewriteRun(spec -> spec.recipeFromYaml(""" + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe + displayName: Test Recipe + recipeList: + - org.openrewrite.NoArgRecipe: + foo: bar + """, + "test.recipe" + ), + text("Hi", "NoArgRecipeHi")); + } + + @Test + void canCallRecipeWithNoExplicitConstructor() { + rewriteRun(spec -> spec.recipeFromYaml(""" + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe + displayName: Test Recipe + recipeList: + - org.openrewrite.DefaultConstructorRecipe + """, + "test.recipe" + ), + text("Hi", "DefaultConstructorRecipeHi")); + } + + @Test + void declarativeRecipeChain() { + rewriteRun(spec -> spec.recipeFromYaml(""" + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.a + displayName: Test Recipe + recipeList: + - test.recipe.b + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.b + displayName: Test Recipe + recipeList: + - test.recipe.c + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.c + displayName: Test Recipe + recipeList: + - org.openrewrite.NoArgRecipe + """, + "test.recipe.a" + ), + text("Hi", "NoArgRecipeHi")); + } + + @Test + void declarativeRecipeChainAcrossFiles() { + rewriteRun(spec -> spec.recipe(Environment.builder() + .load(new YamlResourceLoader(new ByteArrayInputStream(""" + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.c + displayName: Test Recipe + recipeList: + - org.openrewrite.NoArgRecipe + """.getBytes()), + URI.create("rewrite.yml"), new Properties())) + .load(new YamlResourceLoader(new ByteArrayInputStream(""" + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.b + displayName: Test Recipe + recipeList: + - test.recipe.c + """.getBytes()), + URI.create("rewrite.yml"), new Properties())) + .load(new YamlResourceLoader(new ByteArrayInputStream(""" + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.a + displayName: Test Recipe + recipeList: + - test.recipe.b + """.getBytes()), + URI.create("rewrite.yml"), new Properties())) + .build() + .activateRecipes("test.recipe.a")), + text("Hi", "NoArgRecipeHi")); + } + + @Test + void declarativeRecipeChainFromResources() { + rewriteRun(spec -> spec.recipeFromResources("test.declarative.sample.a"), + text("Hi", "after")); + } + + @Test + void declarativeRecipeChainFromResourcesIncludesImperativeRecipesInDescriptors() { + rewriteRun(spec -> spec.recipeFromResources("test.declarative.sample.a") + .afterRecipe(recipeRun -> assertThat(recipeRun.getChangeset().getAllResults().get(0) + .getRecipeDescriptorsThatMadeChanges().get(0).getRecipeList().get(0).getRecipeList().get(0) + .getDisplayName()).isEqualTo("Change text")), + text("Hi", "after")); + } + + @Test + void declarativeRecipeChainFromResourcesLongList() { + rewriteRun(spec -> spec.recipe(Environment.builder() + .load(new YamlResourceLoader(RecipeLifecycleTest.class.getResourceAsStream("/META-INF/rewrite/test-sample-a.yml"), URI.create("rewrite.yml"), new Properties())) + .load(new YamlResourceLoader(RecipeLifecycleTest.class.getResourceAsStream("/META-INF/rewrite/test-sample-b.yml"), URI.create("rewrite.yml"), new Properties())) + .build() + .activateRecipes("test.declarative.sample.a")), + text("Hi", "after")); + } } + +class DefaultConstructorRecipe extends Recipe { + @Override + public String getDisplayName() { + return "DefaultConstructorRecipe"; + } + + @Override + public String getDescription() { + return "DefaultConstructorRecipe."; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new PlainTextVisitor<>() { + @Override + public PlainText visitText(PlainText text, ExecutionContext executionContext) { + if (!text.getText().contains(getDisplayName())) { + return text.withText(getDisplayName() + text.getText()); + } + return super.visitText(text, executionContext); + } + }; + } +} + +@NoArgsConstructor +class NoArgRecipe extends Recipe { + @Override + public String getDisplayName() { + return "NoArgRecipe"; + } + + @Override + public String getDescription() { + return "NoArgRecipe."; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new PlainTextVisitor<>() { + @Override + public PlainText visitText(PlainText text, ExecutionContext executionContext) { + if (!text.getText().contains(getDisplayName())) { + return text.withText(getDisplayName() + text.getText()); + } + return super.visitText(text, executionContext); + } + }; + } +} \ No newline at end of file diff --git a/rewrite-core/src/test/resources/META-INF/rewrite/test-sample-a.yml b/rewrite-core/src/test/resources/META-INF/rewrite/test-sample-a.yml new file mode 100644 index 00000000000..2a66687a08a --- /dev/null +++ b/rewrite-core/src/test/resources/META-INF/rewrite/test-sample-a.yml @@ -0,0 +1,22 @@ +# +# Copyright 2023 the original author or authors. +# <p> +# 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 +# <p> +# https://www.apache.org/licenses/LICENSE-2.0 +# <p> +# 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. +# + +--- +type: specs.openrewrite.org/v1beta/recipe +name: test.declarative.sample.a +displayName: Test Recipe +recipeList: + - test.declarative.sample.b \ No newline at end of file diff --git a/rewrite-core/src/test/resources/META-INF/rewrite/test-sample-b.yml b/rewrite-core/src/test/resources/META-INF/rewrite/test-sample-b.yml new file mode 100644 index 00000000000..1ee001ecd96 --- /dev/null +++ b/rewrite-core/src/test/resources/META-INF/rewrite/test-sample-b.yml @@ -0,0 +1,23 @@ +# +# Copyright 2023 the original author or authors. +# <p> +# 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 +# <p> +# https://www.apache.org/licenses/LICENSE-2.0 +# <p> +# 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. +# + +--- +type: specs.openrewrite.org/v1beta/recipe +name: test.declarative.sample.b +displayName: Test Recipe +recipeList: + - org.openrewrite.text.ChangeText: + toText: after \ No newline at end of file diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPluginRepository.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPluginRepository.java index f0c45f7dcc2..2a87640983d 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPluginRepository.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPluginRepository.java @@ -47,6 +47,7 @@ public class AddSettingsPluginRepository extends Recipe { @Option(displayName = "URL", description = "The url of the artifact repository", + required = false, example = "https://repo.spring.io") String url; diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java index 1fc836945c9..6f24dfc6679 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java @@ -19,13 +19,11 @@ import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvSchema; import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.intellij.lang.annotations.Language; import org.openrewrite.*; import org.openrewrite.config.CompositeRecipe; import org.openrewrite.config.Environment; import org.openrewrite.config.YamlResourceLoader; -import org.openrewrite.internal.InMemoryLargeSourceSet; import org.openrewrite.internal.lang.Nullable; import java.io.ByteArrayInputStream; @@ -116,7 +114,6 @@ public RecipeSpec recipes(Recipe... recipes) { public RecipeSpec recipe(InputStream yaml, String... activeRecipes) { return recipe(Environment.builder() - .scanRuntimeClasspath() // Slow but required to find recipe classes the yaml recipe may depend on .load(new YamlResourceLoader(yaml, URI.create("rewrite.yml"), new Properties())) .build() .activateRecipes(activeRecipes)); @@ -130,6 +127,13 @@ public RecipeSpec recipeFromResource(String yamlResource, String... activeRecipe return recipe(Objects.requireNonNull(RecipeSpec.class.getResourceAsStream(yamlResource)), activeRecipes); } + public RecipeSpec recipeFromResources(String... activeRecipes) { + return recipe(Environment.builder() + .scanYamlResources() + .build() + .activateRecipes(activeRecipes)); + } + /** * @param parser The parser supplier to use when a matching source file is found. * @return The current recipe spec. From 0bc61a5b1ea326f3ef84b2a8366cc77d261ea7ae Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 31 Aug 2023 12:08:16 -0700 Subject: [PATCH 205/447] Add FindMethodDeclaration recipe --- .../search/FindMethodDeclarationTest.java | 35 ++++++++++++ .../java/search/FindMethodDeclaration.java | 54 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodDeclarationTest.java create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/search/FindMethodDeclaration.java diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodDeclarationTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodDeclarationTest.java new file mode 100644 index 00000000000..81a22e77cc3 --- /dev/null +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodDeclarationTest.java @@ -0,0 +1,35 @@ +package org.openrewrite.java.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +@SuppressWarnings("InfiniteRecursion") +public class FindMethodDeclarationTest implements RewriteTest { + @Test + void find() { + rewriteRun( + spec -> spec.recipe(new FindMethodDeclaration("A a(int)", false)), + java( + """ + class A { + void a(int n) { + a(1); + } + void a(String s) { + } + } + """, + """ + class A { + /*~~>*/void a(int n) { + a(1); + } + void a(String s) { + } + } + """) + ); + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethodDeclaration.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethodDeclaration.java new file mode 100644 index 00000000000..b6a1e9b690a --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethodDeclaration.java @@ -0,0 +1,54 @@ +package org.openrewrite.java.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.J; +import org.openrewrite.marker.SearchResult; + +@Value +@EqualsAndHashCode(callSuper = true) +public class FindMethodDeclaration extends Recipe { + @Override + public String getDisplayName() { + return "Find method declaration"; + } + + @Override + public String getDescription() { + return "Locates the declaration of a method."; + } + + @Option(displayName = "Method pattern", + description = "A method pattern that is used to find matching method invocations.", + example = "java.util.List add(..)") + String methodPattern; + + @Option(displayName = "Match on overrides", + description = "When enabled, find methods that are overrides of the method pattern.", + required = false) + @Nullable + Boolean matchOverrides; + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return Preconditions.check(new DeclaresMethod<>(methodPattern, matchOverrides), new JavaIsoVisitor<ExecutionContext>() { + final MethodMatcher m = new MethodMatcher(methodPattern, matchOverrides); + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) { + J.MethodDeclaration md = super.visitMethodDeclaration(method, executionContext); + J.ClassDeclaration cd = getCursor().firstEnclosing(J.ClassDeclaration.class); + if(cd == null) { + return md; + } + if(m.matches(md, cd)) { + md = SearchResult.found(md); + } + return md; + } + }); + } +} From b33796756e40aa2cc3564bd160be7b48512d7f42 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 31 Aug 2023 12:13:44 -0700 Subject: [PATCH 206/447] License headers --- .../java/search/FindMethodDeclarationTest.java | 15 +++++++++++++++ .../java/search/FindMethodDeclaration.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodDeclarationTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodDeclarationTest.java index 81a22e77cc3..8cdfd735383 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodDeclarationTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindMethodDeclarationTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite.java.search; import org.junit.jupiter.api.Test; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethodDeclaration.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethodDeclaration.java index b6a1e9b690a..4403d2475fa 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethodDeclaration.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMethodDeclaration.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite.java.search; import lombok.EqualsAndHashCode; From 9e2db96c5d4688713a79a28424005f5235c41b74 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 31 Aug 2023 12:24:11 -0700 Subject: [PATCH 207/447] Update gradle wrapper to 8.3 --- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 12 ++++++++---- gradlew.bat | 1 + 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36524 zcmZ6yQ*<R<6Rw+%ZQHh;j&0kvofX@*ZD+-{?T$OPlTNa~v&T6X|GAx`E}!{U)mv37 zTVUY_V2yOY5W>&aJ*i+pKn$=zKxk7ICNNX(G9gnUwow3iT2Ov?s|4Q$^q<F%qoQ*v zm@>H|&1~>6K_f6Q@z)!W6o~05E1}7HS1}Bv=ef%?3Rc##Sb1)XzucCDxr#(Nfxotv ze%V_W`66|_=BK{+dN$WOZ#V$@kI(=7e7*Y3BMEum`h#%BJi{7P9=hz5ij2k_KbUm( zhz-iBt4RTzAPma)PhcHhjxYjxR6q^N4p+V6h&tZxbs!p4m8noJ?|i)9ATc@)IUzb~ zw2p)KDi7toTFgE%JA2d_9aWv7{xD{EzTGPb{V6+C=+O-u@I~*@9Q;(P9sE>h-v@&g ztSnY;?gI0q;XWPTrOm!4!5|uwJYJVPNluyu5}^SCc1ns-U#GrGqZ1B#qCcJbqoMAc zF$xB#F!(F?RcUqZtueR`*#i7DQ2CF?hhYV&goK!o`U?+H{F-15he}`xQ!)+H>0!QM z`)D&7s@{0}iVkz$(t{mqBKP?~W4b@KcuDglktFy&<2_z)F8Q~73;QcP`+pO=<OS$B zMqL6X57H~`N0W#7!2fpSOs3XRU5ong+e3%SOz`o3Zu^kt5%35;Yp2#AzPP=d7EZ$z zQix&jnNDW*$`;b^xJeDHJ0L3S$djkDzf_=&Bh(lQ5ptC#|1BgGD%EA_oCo!Pk)o$) zWHiwkW*ANvMCZ~OqxW}PY1HY;y*BpjP)q<&Sf?@#8U~3Ukd;yLExFrf{<{!}hghLM zrE`-l+!~F{V(>L}4yjlzNuLzuvnVAO``skBd=rV%VWQTd0x6_%ddY*G(AJt06`GHq zJVxl`G*RiYAeT=`Cf(SUN$kUEju!>SqwEd8RWUIk$|8A<eRN8)l*E=QDfzUehN27T znrLIPA3tPRcCxORz9de&=0t04A$34Rk~Pop7AHBxPp+qgJLflrFSk#5v+cP<!ZMi@ zD-}{0313cUlt;D74yqL(N^AWlaiz!MdL{`<GcXe%*mTADGshMZe#r^3XO4O%Dmr>& zAvW|Uo<=TWC~u}V?SNFv`Fq9OeF_VpfyXHPIIay@Pu5J6$$pg{;xE9D7CROVYV>5c zv^IYXPo_Z4)bg5h?JSUX!K`q_u{>F%FzrG>*!Db_^7*7(F@f%<Xyoek!cME~{+b(U zF@?inQ(h-LTvs7Z0nsZBYxc*xF*CX*JOF>i34Ps`JBAH6{s=ygSr^CVO)voP`v=SO z7v;4cFM_D>iVl{&X*N7pe4_^YKV%`5J774`5!DC}g;D@50h?VA!;fU1?Hf%%`N8R1 zSg@hZ8%Dq^eYV1!g8;`6vCSJoK+V1<Za{*N6?fFB4R;(1f-}G3AQ8tu7{J;c*1t6U zVRfU}YW>Q6N8ImtfE3iXs!<g{^M2wTIRUH7_2MqjA)&QQMIxBSOq%j1c^VBgj&Q<v zgGQB;Ysq^?aJ>s~B>js)sLHB9w$r+6Q>Oh#Ig&awvm%OBLg!7alaf}9Cuf;M4%Ig9 zx4K}IQfPr&u?k8xWp!wI<a>4{CP#GTs#qR0b+G{&+=vL}I{b-Pha43^%8=K3997~* z>A|oxYE%Vo4~DiOih`87u|{8!Ql5|9Y+(ZY2nRP+oLdGErjV&YeVKw>A$JyPPAL+C zA36S<iejUeW3!`{nA$tuzy@hAPmeKO3OtF{DvC-w1QicuZ-QNKxt`Ix&emd>!dNVf z;xJ)YR;^VPE1?`h-5>{~gwY2pY8R<ku;HDw#`3RQy~n5QvJ-NRBT_0-YG?JTE?dh3 ztr^LhhGtlMS6rRu2a37YUoD1>qhrsiIBmJ}n3G@Zs!!fD6y&KWPq&i8HEm*ZAx`G} zjq2CD5U==ID^we8k?=geue4Y>_+%u3$-TzVS6QMlb4NoS%_V>;E2hQ)+1Q@v(reC5 zLeK*f%%{PNO-mtrBVl|-!WaiKAkZv-?wnOwmZ=Tv57k=4PX=C?=I4V*THRFRE8a_{ zb>5YwDf4o>>$o{XYlLN{PZ^Ff?0FJl4>A9C-q9A$$&44l122Qsc|6Fd6aTam{=JO3 zBFfFe9seUPSUeyXQc*RA>2{WoKIYVltA&@5spdIW;rzOOqoQo`CN;~UNgU{{m9^c1 zTrN|8w_7+Nws4}Z-4eS9WMpF3h<@81a)oK9njh;-TB74vR;u{vE?>6FDG7<%GVXFL zUR9l{z*eEND6pp)+hpNT$VVM^Pw*S;#NrbCmH{dhBm?%6D|k)0C@Z9H>T|kby1^)# zOPmJ8Hq`8waoEK(9}IfP_q4yr(s?ME+T%UV-ikxW!XFb^6w02t30j$n_VSwevg;{9 zx0OXK_uGBFej=gbG><WT7G&giXtK@zPEH4_+NqV<Z8%vd7fGvP{v}IsFGQCqvuFDx zbZ1o$js{p7OV3vra>G^pEv^`I8&_a@t9>Nr;#r?XNKquD&Ho|`)qK6C^-7SCdo=S& z)vUi;m5*qIePEIbL=wJ|WCBNY;zCm2F-+@N2i{I^uR9UVZm$o`I|@<&2}w)C`h)vV zW{)yGJ3?GCZNtFe53Kb#uzrC7v-{JygKZ<lO#t^GY1xV!rfAeLwFQBWL>UiXDV5mR z5la_vAFOvoh#yn)B`$^ZN*Dxp5Uo~_k8G9skn2)Tb>Kw#Vgxi`bti)^(z--X9F~oR zZ6=^_x@m<XR-@#YDCW+PvaHaM-p#G6^JqU~F!U+gCv|Cfl#Rve2-P^Xc6HgI*w7k~ zi~#7+V0D~Y-0=$yhDuJw;-LLP&L|Era>DT~=h_@GGVcgBtLzssB1|Xy(xc(lUYJ#_ zgwc&ajE%^cCYW7d;xAxi{#LN*1}s>{K79MZrq!tYMpRA{T!#^tgXP=J5FvkbZ@gx~ ztq-E&c$<P2(ZTW!wt#+AMB$??A%(z%B;XHAs`4LlNEWf$8YeP}TqE}Q!69Y|Y#K0_ z5hnulsn~~d-&m4990ET~iivB?Da9ePQit&}>`|KX8GS2a_voZHf=y8C{6~f~`DpC- zjQfrt2OGi-WGx}Y4>vM`8<4frU*!bq*NJ*Tyn0cqk=zpDdYth-PJIfz5>pLF@qnai zzj2FEhuOa-7$JR=U!L{UWWJBA%~SW-6Nh&3;<}iQO)DvOI&VKi1L8rmICePWqoY^F z-dC8X8~1T}=C9m&yb1kZzbKd2;29_Pm*Cs=y{Z06QZDlT7P<l!ka|EeP8+S;Ymo*8 z?e;wa{R(S&c!S!VuR8m^L9bs6MFIPI<TY!5oZa$Vxz~vUuYs}Bi(v{rvKQ^~#$eNR z;bE9Yi>oci>1@hFa%t0<`1()UTxcQ}e`fAh6K`<5C_SG`dw$IqzwEYNKvIH3VWlhz z_#^(T53W}jeWF#WIhj^U7AdI<mbE}4ac_du+9Cdz`W;6>B~3feC--5iUiiT4Qyu81 z;Xa^8#~M@p%6B`LCKWWTa7I+35BLP=EOa&Gp2<m%4MdfXHZ<`G*b?;J)ANiDq-wGL zVYMK`X`_Oly;=9C*b^uLbh#TDl&XpUpwNjhr3OQ5Dg8Rpp@vT0?=RE86xpQM7WacF z7}6XN3z0GTS8<;e>pbTWw5HOIjrx;2J(KI$$HT|w8}R-8fbp9sot&LiLs7ILlyZc8 zWbss7=*Ah|X$LEt1O|T?ABkIn-0NN`I8+ipfoBZcW>(WiaASG_khBtKM{hfkm5VBS zy0Q`4*G6HRRa#9G)10Ik3$C3|nQbFzmU-dA`LjKQY8icnx?2OE4<k^*%nma+G`H4e zQaf?UlMJ5hqcc}G6;AsCQ6#xw4&;7WQj3*doyeKyyM&N}+>0%z852{OJH=?mbvwr9 zhlx0RDo<cg1ZF<~q_A<_>^D;p*xKx?yT(`s7wj7BHA~rHF2yxnL<1PcU7FM57;?g^ z&CyPh9W4KvZ;T8w;AuNMn|nQ-xJ~CvVT7gAPAGi7w8udw_LOp+p4eZiI`JEC@Mq9F z#dA2AM_<Y}U>};CnL=y0#tZALdB(P~Rz*KqGqjwec%Fy?K(PGoO0tfskWw-aGhd7$ zTi~x1G>4h5q>ek=tIoT(VBQxrq)&#`_0UHC(j*ZO%%}%C)|EzTWEpvYDqCYXLexR9 zlww1ESB+IiO}=oq)8WZj%cY_FTQcEJ`JdABa=_S;O|kLhX*|5|D>0c{12DoC?K95f ztNxm(sTU6cWWd$tv`5X(=x?yAo)IYQ3G*2+o#|EfXko6erF;M4Pc;G0)pUDY)t`H9 z76Z8V9HqbWA@!`BelAT&ErrGTz7}%M*605PEY@3{gv+`yEhr{=EVp_tU%`b54Pn4a zz8nN7`eNx=*`f1t#^7>7G07IEnbnn&`RWZ}4Cp8W_DFDs-5)GU`bw}uBmOQfKmi2@ z(cWWmvHFTUNInRH!0y_ZtuI9Eh@O3+64wy-_2DF~E@KF3abM`0gC%|kHi@&hP_#B$ zLN{Z?$V_;+h?%2zEC{2ITyWOup*w*K?~vpwB(DX1i6oY+F)??;nyHpzaPLIt6G$4; z6>iAsB+&&NN0;ObWVOL+-^ZwD?nHgY>0k>0<sP{^?Mp2+@T46U{FccM>I3iA7o)f# zN&aX$lM@r_Iu|n<SUAV}>SdPjoF{#QD9M6>|JSNPLxX^T2!jCKjS5mwNaO+SmBfOY z;6ZdwfzhO6Vs|9u81f4e%7*mU%8K>A7QWO0;QcX7<jT_Q_h4^Du`TN@xvj?onUvjZ zEiL_7n|a&c|DLa*0&#Yj3r$CU<Cg4_$DLE-N|_7|kkb>W@|NSUVl)_>7VEf#&N6E~ zn9Wv88@Suo9P+M_G2(f+JFf#Q^GV#7QQ`qH#$N1y{A*_t^`5H1=V^u?Ec|EF6W+6B z(@Q8ChIUyq;+I5CmjEa1*v%d5{<?yGM|}B87s<%&dT!n&#wj+}arjX<<RlTrpEQ?# zdHsehntqI5<w-@3txwtgC+XATvpV->WHyhcHSjQuwzQq?;^BmfV#okq3v8bp7dBdk z54B+%D3=JWd-2w$)puXxZyZH>-$O-?tbSIlGc{em9xHN!44iaCr}6uZ^FpN7IvNh8 zbp!%4xR9np`>AOEd1e2_y}xW#v@@h3wYc?WiwL6Q>fxPQA81V^J)XtGs|Z&er6w~M z!1Ph~85TMG>R&ixNUnevc(w>fgb%+X#Wds6Yl+wH29aE%;RuDeZz5dEt%#p&2VK1n zKkqgl&*_YwnO%9`0<6MVP=O3<AJKCZB>{02EcR7PvvZPbL2KMuoRsU|Y%zw38<gt9 zD7Xp4!h8iKF};C<2Bebh-{pJ{+>qeOL#!YFp#_~+rtNJVl>lJSh_*B0A6n3XkE5po z9RpE_h=pnmDJFX*n6wmsWJ9GLu2=L8y!_R;;Aa2Jl|)I}Qff&`Fy@iOhop8>Y2{F} zbVk3rNMi$XX(q1JrgcIhC08@d5Zc>wLUL3wYm}hzS^!5d&Mec$Sp^$DUS1lD1>KAt z|Efof3nJ4^k(WKL_t-u8ud4L(t>q#9ECj?v#W~W#2zTt>|MCh&*H8Wh1_I&^2Li&M zq9j0`(zk~P7}dB`+15b*j%VPGr$;@4MBQ5AT>-y?0Fxfr2nC1kM2D(y7qMN+p-0yo zOlND}ImY;a_K$HZCrD=P{byToyC7*@;Y$v6wL!c*DfeH#$QS6|3)pJe68d>R#{zNn zB0r*Es<6^ZWeH`M)Cdoyz`@Z&Fu_^pu8*089j{gbbd!jV@s7`eI5_X5J3|poVGlq` zDo9}G;CsjW!hgN2O9=1|GpE;RpQvrBc+&dF)L>V&>9kd6^YIL?+*WDmcQlvwnq`Lf z&N$gF>3+E*NcJojXXI^}B(B-;@ebpVY}l#EcDWles7s;Ft+KZ@m+6FWaD^oYPBXVw z3sq|aKIDh1x5Ff=tW$(LO|!e&G?Xvh^H!GfiA(emluL!LmD=EV@|u|8S7w6ibUePJ z>{sOC6L27R+b&}e?VH;KvV3a;O3G=gwG}YzrkSTV6(&=;o)EV~2OD(Eh4mu@K0G)i z3#44IZhqN6+Hb2h#3R8YwJW7LesDA9=n)75u#46_ZmSh@6Q-4oHvGxFPY8x;Q+)d@ z*-SDqhVeyPGkoD)iq;z0r*M)IhY5I>gMA@RS&EIYPq}Z{$Q4Jbfd76EVhSF-sR^TO z!=o?>V(^bx!pG$26J~Z>Tvu&Uu+0;>m+pg(fmbu(97^(OHBH4;J8WIfv-f5}VP#VS z$Y$}SHKdphDUHlbdIVW!k$L6T{LY)|H}MT=l$22kIl>|46FK9dt$?3Fjk2RA-~AX7 z1|Xe`n)%h~e-O_qLpoFXJ$%gmocq`v0%hRw1k_6nh|+3pvJDy}m)V|xjL&!Z6?%pU z+m)r2*pWjEl!etAYxdzWb<DeR-tHX$b+<?(Wn%1Gh(S)MdmKB{*KLQ|o}i^=okrgi z_9tFc+SAl*^)xH(tKi3jdpUyCn<7TfuzsN)Y1<z-uZ+*1n644&T2hhTW#l$JdUVv$ z9F2fCF$SpN?bdF2U!hqU`foD7CNC4D?_2BJVLtYJOiMdLf4g78>0{mGc;#$>rE%)b z@Rnj78P;$lrzY!XCa0&x+8a^YF*G|Q|C}bGeczz(5m_gq08wJHIH`WqHH?A}!~_3{ zQEvMXmL<*nThl^pL58nbHgQ1n9cYmN{C8J^6AKS%?~>1DCt70Q2Vp0;E@`GF%Tzkc zSUt&LJ=wHI6@#8<p`nK`!Ea3fOOSLhy@qP*1BsMS6*j7wD>_%=2s=j^4VBd1-h_)3 zeozYua!|{x(qk#z;tavf28rj_5Oen-cYG%;R6I}Hz$yMXeg^)_$OUUXx1r^qrl!DG zYXkAXKBMrVM-rJwAo<5J{NW1XJhW;Nh*&`n<RXNn6IOA-+&<Z4#Jn~3w@C62#QzG# zo&6oEY>F<mHlXE8t5UZLqPiHGRHomoh-s|VdWDiw{z6h^=(CmJV!wHN#vuwY`m=y# z$b3KecI8NVmbWX9fLRxXl*i|Ky5oAwllK*He?k|CYlD-yF=r&qM8m%_O@_wDtIw{? zsm!8mF2z+!7eXmoti@|4)!q|iq;(;s*+W?H6w13V0El3Hk6%BXBhgeTI5K1PqVf-q z!+aIreh#~w{+%D~P*}=m=S@Xc@3l_@S##Yb(uRNY84PRS#?R3j5sidz{7PVr_7d6+ zw)@PW*{8JaN_`1$A;iCk%{My7B6QL|yjw&-a@mrll4H}OOf2AOc$xJ}uEDm{jyNG6 z8++BP8}RG4A22G}bSAs|=P6!XsB+#y#vUy}^iHqp)BU>V-Z;Vd({KSkMxV#cn|bXJ z50GtvFE##sqGhV#lv2s6?^yeBShlhR%XaPIo)iXOue}jwZ;Zq#dgDn8H?74Y+$Z?C z2Y5mCC66>dp%sVMecUzCir<?IS|{wvN9__C23%k`O7tg=(qtRE5(;!0FJVcjKh07Y zTNRv{El!qCP?t{?82c5+1<}`8=b4=X6@2MHjbOZ^<#e&J=tQV!{?e_ok!N2LCFvBL z`MZd0icO@hLwf|Ve+I=0+l8Yvr<Y7+s^4lTL-U<Dr+7fl8XaIdpv~*BFY?f*`zn0h z>Wq99Ea(TDwClZxtEB~4N-2JmlH#>Z2jOcaNaw4tn?P->BBGNHxUHez7>C@TZNT5Z zHerlG0a4~06L%>tn!~$s^L5`~{ueLZ5?`$46nHvwKxM0V9VQ(k{A40xDVw{+Qt)RV zQ)T2Df)cp0nv!lUFt3D=i~k!V|7dUjpz?K2ZiynO)$d{2*YT$N^CQ{t=luZ>WcE!> zg25p}If9RTho%G@PZp;5zBwv`n+e9iO=6dx1V^|4Ty%`oE=f7O&QC^s!4MJ+lMG>^ za!mgpz*^SHT+M_zm;{H#E~SaU^Kn*y)nTAF*2@t5mF+l)bte+a+goaA*zXJ4P)H|y z{4OwbJnIPtMp4E~=64gM-Y{#o{x)+8YCg$C7Yy=;9hdyBgRFIY2_L9DL3*B@%$5#m z8P}+)glf*}UPD$C;_yntx}9VPmSSnY9`Thd09nfoR;3`kar*FRfS)`+as*t2l*U<a zc9nu(cBKz?7arM)zV;H_Ps3eFwrq3iA)aBYa4pp%PE9r90O+Q<Wlt_YE5AMBb)R~f z_U$RdmLeat%P`|mW{m3)Gtjq%<5SmI0n2A$F}D5;9lBKuuI&j~;#T&_b^ap1RgNwU zpyjOvre7@Dh4h0NO<c20sHfS=+fF>SWgmaZ!qFubr1DegTGZspy<V2nIl0@{`(9e$ zZbhb_XeLc77-5o(b$Hl-1-ZBIU}j%?oNw!?h#Og7$C}S6T-^LuM|G3}+ix`Hxr_0o z&acW)nNFx(89Ed@)*;G@;k^!K{Qi~#6ct)SY7_5rLr*Dsi0>YMgic{inI0dSt+rJR z((jjMrd<p)oCp*`z<7GLWwxEabV9&%vqF|9&I|WlYLCq$26pZ*9NO&d`7*c-dn@-% z^ooh2B_QOrs*P~6G3dv|Ve5x=f;J{sC&>q^?VSZ8FCO;0NW@>O_b67gDHP%W*^O?J z91NQ7ZFODMSvHj3cvT#6RJUF7x=-BJFQ^6<&mOd15Z&M!?b+3Tg!UcgldD9tOAt5K z3X>MlE-a=sj;K&}sSng48jQ7sp|&u3;@e>V4Cuf(!s@9lZ0Cg^DKWmki%>$<85tOG zU;e{%zHU~KREBUg?FbcseK{lmK-`*S1p9j_4hF=F$y)NB;HsHwuf_A0Zhy395e<C8 zA~ru0?GH;EyiSV`l6bsHzG)3nf!Y)uDm!6If-?%v=2I{5&)VdRu~Q+{8cWy<7Y0yr zFenN1YKYr=^-OK)@2=@hmj@XF3n6UE7q|Y+2_s~zF^_f`mVEnK=Sw#OicD>U7o8^A zi2t7Ch|KVp<w$qTr>rUn03N0T2XshT!g$HTErcQBBG=TWaHkYtaI2CJY7ajI%yr&9 zVC^zJ3WW03bjwGNx{l}#+D&Ml_uI4PQhV}qZPXOP7ffSv(<f8@E13K4YNv44sPM9$ z5}jkawJF1jsl0yFSJqAdeIPZ>O;hX{Ff1|HoA~v)V!4y{CdALyi2YPjrRVmRYilRv z5PSkj*Z_8Fa*sCqGN?7YTnkr9=i9X`qcw7nqz#{bj?B7NiV9fWF+%~Rb1X@MuS^<Y zhy-q4^}EzZ4_voD3N3Tn)`-CuQFG!zcw-%FKh$H`;HyF+A&ORjrsSwHx_Ckn@ReGG zj5=ye0y%yy;deA=EHP@H;=mFaVFIQOmI&yGE;Wi{p<Yj%w+UDE2w^VOi%FChlqTvN zYyn&S&}yKkiF}x+iGEBEnyv0tp4J}nZpT6P?`{F|u%@o!l0?JtU{mVLIwcW9Qw>Mw zC)d#K{(-9!?xStM2K5x%x~ogWxgIK>s5r_RT1jU_lxdTtIEFWvi4eJSAiGec&HXQ( z5t7!J1b#SL|8s4)u147PWQUq_e33<Oxbq&!>!5Z#f$Ja&az)(Htl`Z0<naj}?6F$Y z$}4lG0(Y-w)Rs-Zdm7qNOQq3BDy@4Qh+s`$pa=!AfIcX|dbX8Kg*?%N8ZU==cr*hI zTO3p(m<U#|cVVJ~B$&W*53$4|xVn#Qe(q8V6YG1ZCAL(<{ml&SOn@WWwP$#2p)VG0 zZWi3!_`5JRX?;;!NWvp!!`qG9aG#I<BAO%iFc|FT`=mviUxJ4t-qQoJp(_T1bS3-0 zN9pk;=L!I4$_etGN&Ool%e{d3Gt>@Ez)0d74BzNHHfH|<-8q*ZMf?%eJzoGS!0S6Y zS<yq~kB3Tb%sqy8eGF^b<D=L9FeZP3J5X>U7y^1+;V$Je9F027>1eN#_tz+2t}Y^N zYfi9}J!N^SU1CYoNBDbD39@84xLroY@0f%%c^(5CE+}!b5-Mt3oXe2nB<C1ScE5|! zI`R@|JpR6pqCr<tY!>dyicgGIL+rzTTKv`}Pp%fG1f^s?sgNH8=Q}s4Z>0ZCZ8ZYF z4og8nK%OA~zZMJX01uFtrmwhcgg*XbiMP9kfkPYFASbp7*Bk^5ZBzV)dL)JhPwDkM zkgdHeKw)orJcj4^)a^wQC2|->G=OBzuc-SskRrrf+H-E%HQ==Ex}d*504#GbIUXIB zcZs@Oo0i61MG}&0bu%@2N?MMJMRXyTVb8@3wF5eY3G6-1NdT~{{~YFs8f&SNebdaq zKmP>XqCQ@iaamuvY2m%xJ~gdSLSj~DBhB`NCj_c}NbSjB{r(E`_-+6a#vx*|S>-GU zHsw^dxxu`<ZZiBgWm(-)b;EY3EtQaf@}DPW1>e)q1HbH==rLFap?cebKumnTo=iJQ zJD1#=o>0%Y@&jP?^)Q5bTV!pzrf=FoHq2c_59pq@my{D4AW8VU*7LVp;LF-qESV;L zClRfyQ6CcD$sd84K@e@p_ALH%j(Pz@Em@QFyY`AG&(|!(cG8!oV#ejr`y(LolX}Iu zL$)G)8^y4sUAY<?JUnp?V+X<+x497_8RXw4qkU3F93UWMksK4owU+_K_<{V3VkWmI zD75M3nph6sl-syOHNB(%w-56*!?v0+?K_%d3}I{WCUcqMguAOm{GNezK*N1XY58Ds zcxE<o0etgIY8&>CWprzVR?`#OJ%NU)9U^B!OGSj>Ly;<)<(nNh`?z*GvJ|ZBKfZ`0 z=q_yGHWPp~R+J+{{@APVwmp8`=%N!L7AT^l^oaM|JrCFu7J#@frf=z(vGq2>sQ^@u zk=^d#gDf}ME!~9PaLfw44~rsG!)T7h8~dY^VcZQa+u<a0comw1=nN&=)$g(2zsse9 zNosK&#4<FTgb^-(s15odlHtgB_~kcWMDnIhO`~wH@saRRni~OEa5N?8JpVd;EXm+- z)qqqblNN-i(q02>eWPGG$mWXB|H2$$0BT(QAIu|=DJXPQDNes3Q>-|Mh=I<BK=M>h zy{WR)QmhL5rQbBYPBa+e7)8Vo;<S!ABPkUe41j@N>_aKrg`}izmN>#ATuSDu!QUFA zsgM|Kv@W<Nnd-_)&UqvT?%DDV>(S}<tg)B`%JZub4IDFiZN?}0E(h6dt3sI+;gPRP zF3azPXNY>Ag^6e8)9pQc@JLj_2ZIkO=8)#ARm#mU=NncWbmd-SbO;ad=y|k`shy3b z*8o0@EJo3b$#zSgmnlT7KAp)U!qI2<ZO&1fknVbEVAufta&Oj|ev15eyGb%dk3anI zg4jI<VAd7EVcnHNQOm>M`hiC@Gp0)pNGHYMe1$MBNE}Hd{Sv^`wI7>MzNwgVv1ZzL zttmyv!=TKuPH$b>r7$lgP5?vho;#Ks4+zLzaz-1b{p-Fn6dWy1Agg7O2{&VQ5@s3A zAqzC9QokRD59!@ex#k>xy61kq6h~O$lb;lB;Q|chv&wzR+N<xFV<Y81KH2%km!$hH zoFo+vR@C3?To2+Y-myBi&h*P^bmmmc=HbrOs4u%OyLxwxW|{8D@-*rGoO}~@05~u8 zZKzIh5yoi9o;p{~MRA7;a~+AZQqdIinbC2Je=+Bh)JR1#*GZFg!&x*oK;x)(LjxY> zgXdIo%?q1Y$TzsdCo+n$^NODN7yd}cAv+rkG|u-(wTp?zUSUxaA-<EMLrr_!i6MHE z0wZP7ErGEp1(s0$_G*J~vCPWPfW@gkk_W^pfy$hvWRxlX55g#c5l(;A&y4`xsw!#7 zbjj3Nk26d}ceuVA^{pCcbHXaM9%PAaX~SV29RwLNh4~hlNE66<>W3dwqikdrokwz) z68)Gn$Nwc1zB$F9`#(af|C3v;|2$bo7fU8f7h^NK6h&@xi2m`)g4mW$?l@5JEc*VV z6d67@Fl2w6mO;MYUl2U>R996gQUX$d>$D>)TNGq*arz}f21yh^uvIM!3u$H{_CH5! zrjt9L^&J8UqEV_lLn&}nc|Q=MDei6t=vL_>X-i8B%f5FDi)|qQ;2V-T!qOi*uqq{U zElET<vy9xrZ6~nISR*357}=cUZ!(b1<dkD){VtGXEQD9o!#fA?H+{Ods|^zX5wB@z zB=t5saW9~PNiZLkj$Qtc=bV>6#2cb>Z_6p_vw44&mN!;T&~ubi&p`XGepCNAfa0-T zC84V@VN^R6%z({m=$%iXrbiggxvMiBpww~ktD&=9-JPK3kPCOGCJNQj8+l9k#!QeS zv3h$Ej>@j<-zBW0Qr`5tNQVRfYK_$3>nWUzf&c*tCpl@aYwa%b;JNeTX10OevcxY7 zqnLgKU-X9G8~&?Dr)`*7GryqhN#;9v`D_c=_xBcD{j-cLop~pSnM?&<Ph{*jW{3zs zXQD)SW`+zTX5$323+C&4{L$LgzyKOO4ls%ZQetqOx%_}e^jxF|hxG?=ugTSMZR0lO zj?S^@8~(KRX!6eo`<@t9h<g!pSSe!`Q*bHebhi9@HPWFY5K*(+68GGTi4?n{z%V+X z3pOsrLRv>7HggX6gb++ftBq$idM1|>5t+68sWf{ixREbMkZesmpjJsAFPQ#2+8Uek z$BPbu3<xVFpK7dv7KRR3vc|h+Eou!Ce}r1G`v(%`i2Vs+e}pV;{E}3#=nV43Cy~JT zp{ReQ5+LUy15IBpODF-?F?+=8&q1AX%kg1qa;V=5qvaMo0Q3eieFb)7s(gT}^n|6N z<n7r|HK^#T6Xz_Cic<SpE|DzHYE0b!W|wP)Dn4k(k6duc6r<-)86l8!XD(5gXC0=o zKtt6LHkbmHI0-=4c^1$MQs666q<sJXRT-f_%Fb#3VebX;{|Yk|pllkGRz$ruSgWXP zLghC!4N2mEHcfTrO5&M4bxt$zDtVXT-Y_VIi>cQuNDQq+^M}&ZuSHjxUgxOjF<^%4 z*8lc$CgA<$n=DYg_DsrHB7zYM0Ro|gS8ZnUq$u3GQ+{owv9RdB$wG%d-;R+I>?i?b z+r_mu{IL6WTYftdz?0#pbHkmQP31LvXcMK6;mAP+;q^L@q}v~TD}Ni>f7@QYcbM!T zX5kShHv3X1U=>B!2*si9=AEJCBt~GIH7DL4^+gHj+q}tk0F_?Q-=z{JY%77nkw>$F zG}6ROaL_)3t$jX=ZtFG{Q=LZfNjNb2LK=m9<r^Ty{qi6Q_`T^yhCZvQIHK&iB^%e_ ziBxN$_5Xz9Vv;%S8Bb%-@Bn%T5`VDRMXQZF^ibN%5HA5H%ZT>l|7iaB++N|S$vAr1 z_gf3JpIB|?dptfQ{sOZGlhyj~D;T#hjaNh0X5(o&7)87^t@@Hteh{0DOM{tCu$l#& z&NhA&V4VR}nzZP{7i(5bGB17<7bu+RJ1}k}=ffSg%=+213Oy@Aj1vv2U>U>8tRhKM z=*e<21)u<uh~xhWaMrQ3*#wsfNWF0?eaeFy5+Gc}+o;A3QrsU*-XBhVGxL)}N7<EX zVSGiE+>6SSb{CC&We%#6X@duqLWGJ>O)Ls`uM98``34g11<O-!nvGOEuuT0(LVqtO zX2s*MMM2(+2gFI*8De<gPSPFXc2J$ge*lW+%MhNqEp3<?8aH~avgxwV?cQ+LglbNd zTgny`&Mw}<gXQ&GCCAg4nelHjG)ts=LYQm9Wyk%(`iBOLpxM%N5*j+aCvIK_DtP2R z(9oi@i2Ml+wS=_FCY8bdLHu8tu7w}Nhxm_IAUFsJ>;D}*7>c3+^c|Os&;t}`(BWMD zfbyr~$j%{6%DZ`kR-}s~p?0#&-5a}b?6tDqwtqY%ep0ypSRIB54G@|0J5E#LkxQk# z_&xE=d(U}q?*Rh7L7f8A<JhY?St~8>M<fsw8LqS{(O8)T&cz#`t9*_^jXZ*jWVNn+ zmIQjRU-h4bpZw<lsa=D=->5{qdGpC<&t~9YI!%j2G@nUPoLPSiWHjCVP{JAe?cBjQ zTqI=R{nv5c@|R)8Oi3cTL{&6%XdTgDP4CNYT}q2f5|Xf_hID#;83kd+v0RRyNKYn} zyPahwd=4ncDORLvatBc~KzT+jiiD{tzd3d*T(f7ayS;J&I1X!xaL2~POrw2ST=Pr5 zu*c}fb@)0P6jv))kNl38C7gmnWGmlL@{PWOVYt9se*cS0w#@W=N+dY#V08ci=Zmg9 z+${f#Qfs5)hOPxC;q{(J{Kx4HF)2QMzlVtXz0-O&h2$VxtT;ROvZ13nN{IG>Asv{% zHuDqgZ{R2(X*hkO+!HYHHWvRYrvN9fl-1?x6b)oseZY)@dQ6O>9Y#8*23~%bzN~Nf zpHGMdS-G|%F^v3Gnlsc$s4Wl=ZEu+J6y~*Ih2tpmHfO56JXKjldm$BxDvW6ZH>JrU zdRo<INz-S^E3^(+i+V(K#JMeB`Zmj{A(unf-ZV)15)_Wr4EXiGsBEYG1nvHZiY*if zh|vE<rA1s5rVh^#j>}=^466lAq6!qY_@nQ}5ETUEoF;`>7b8W910_Z17!r`D?QNvC z+WF%@IkPi43n4;0Ks`M{x*0-^GK7oCAp?pFK1`~RoMSe@jAlV8vQruCUNyQ_7wk?` zSKe*|!4ar@VSA}!ThlIB*Qa5){pu&HS!a)-{lWL2@o1486ZK_!!}FSZ>vyUPIOX#+ z5d3~J24Op?!f!oNytub~egnkB`}h?eh!QyX6&^LbNuA#9vH#N_7IL|#6kIDhLL=be zE<R8t7<OjI8h7Hy`g96L3Zg7FjWF}3&>g3Cwmw{A(cm{&T<O*na6u2NFOhZWHK|N8 zzF^Udi@lQ}G*3!0m|QnYjkkUa<eRpVhjl|g1Z#*jrZZhN&DA+jO#rB(hp1xg-CwW> zPg>XIWX24$Mj_#^k2I91C@h;b$8WNVr&MLjEwgAUtSeJ2W0)6Fit}PF!K&1j=*+6g zL{XOUrqhNyPLemIF4C&hThR8fie9^fYg$yl$m!1|YgcPlO>TB-(X{lkN~X}R=GA!Q zou<9ZJV6*}SN_4WRsqzRGI&p$;9DxDFTlyPw6Q9rlo@E3tMN&Wo4eFs{1=RCUij$V z`8)kmh0fhTTiEyvRl90B%q2<lV)d9-o)Ht9<Dd~o9~E}Q#6phQLR^v`8)kjHWYFm{ zdE#&UZ+7GaF%E5p%vN$_HrpW<Hhj3Tkc^zKC~)tWizjzmaKI5GVn+m-?(oa;)NghR ziOZuPVAUkW&V6vo3<tCXFMI<A`U<2ei-Y~VhNNyC&4tP?Dp$#im0XH@@Z#D8&!82( z!QQ4+Cnwa<tXboRU6j~tz)^OsB2J-?EwPQoRkv_r4vP~Fmt04>(Moh$jg7{NeQiy> ze!H{zbG7<3BcK}XE&V_1kFfGA7D^ODxn*@nqlp!{LhYb47zIUlV^m+7kZh^a7L1^D zvI?m^9PECMnnN$0hi^Ur0b-~QgEORanrv|`dd;ek$4rAgEEof3HyvuYoZ)H*;+TgO z8CJY~4YDI^7RD7O)m&2h2<Q87rm~8bw|H%P$%uXd8^CHUHs52!jcrIg!ANi7222;r zmZuQ%tiIgYv69fF5fT1=kWwXX<wZuQejO>K`-4e-I$1zcZ*K>Cd7~sSxEXc{d7-;f z5Ykr56Nkie%=z4_LIA}H>c81e$%ey=2hjqzTxoO0MDe!J&PE@EmX49jQJJg?HNw;B zHRHr)3do7CGDa3lPAZ4LAnpT)spnk8(ZiFz$|F$1m*A@!qCPug>Isp|MPI24i>jp~ z((9EQ9W#Rz)0AYT&ZWOWKBNtdNYYm2QytK$o-_|W5j7Abr&73(MG+Ar4K!Ij=nKu# z;SNkveY?Oc!I<e!FB)q^%Sn>|Vta2{rb@c50#p_byn|_tu>Pv}6YDydl|}X#4oZW2 zvq)Y@8iG5@6c3?uu4vdLS<T0bz2Je0!Wb(B=@uU^mNRR`YI&thp(nXw*QA$-KPijm z;LTiqJnO*K9f_I&C6c$ZVjq5_{M<L=0LMz=TFp_bA<lb5i%CFXHgq4jVRzt08RMEf z3@F9PCt(kBjj%#%-^>Bq23P&qUSvtGcu_qgH*?KfaT)@QueLx6apA97FI7sXP=foe zmrEu7;%Z=yTTGUsHsjR(wU54xNPI$hLFZUOwh=uhZ&rLammOQ?w*)}?Ah#%&K~OZc zl#Owj1OCEeXt!ALV7LgJ=MVbCo}<%92WX$wCS~Ins}%5+sb*C{WoOT5*<IA)uz@eb zLP-XZ5-w_QTRhd})@kJPq^5yQMU%(yM2C42W^7h;8W5ewayD_QMDkmJRJe?e57cd# z?^EvB?N3*~s_#gIUTh!s{H~4Lwlcw8BCUhG8S@AIF>2%sgjya;<b-j7+AJl!1_VzD zC*X}=DAXZZH*Y33iou^JGK7nXlgI#0aZoc9aJ&B7Oqn*XDYRQ4ScX$<LuoOjqto>~ z|A#;k?j~J9qB)Tku1BGX=MrZ}<%Z4}i$OvCHv<dr0isg_Mc;A)aZ}o>_3vtH_NZoK zjJljjt(~Yh%aI@gFnM*e*@_*N190p^@w5?SjRMb66N_^3EZ#Yoh<8FM>Yx$+mTbp$ zjQQS7(rs2j^54CJXdkH|$1&$<b^jjZNHa;tGy#1rzLCo4m@7_zJ_d*Uhq}`k@^o?E zWhm=)-i31piD622iVS5V+l~V#6=;GskJ#TYCYJE4bOD@dHOZ17#;_UqD1b_Tkl~@d z=uJ`%IP>wPOGDvm^@1o1pl9~!5&B+I=U-f_M-M&r3zfp2%TH%Ib3lz-^t)+Z9E+<g z6%ri>>W1Bt1`B}rZ$hZ3{<jzL+ML76MX46HOTpFRaR6AuBv`m#NkL(i>0n|nZKM9O z$?_1+y}fB2$zEzE$zC#46=0E_4x7-VXY5}<+d!g2+Kg$gvU-Xm-A9DBZz+bZ*zDTx z$Wfb93))oLQf;wKi5JBJ%$yq}m42lacy`bC9PjFg*}pCnqn@dv{k9WiwCC07;6n#e zJ499v3YGQ^WyYY=x*s`q*;@R_ai1NKNA}<6=F8IvJArr{-YbdY#{l1K{(4l$7^7We zo~>}l=+L8IJ`BhgR&b$J3hW!ljy5F`+4NA06g$&4oC-`oGb@e5aw-1dSDL}GOnUuy z)z1W)8W9t(7w%OCn_~#0;^F)xic6It5)3h);vuLAKFS4b)G;Z$n-R&{b6h@yGxGo> zT-cq0W7~n+qN10;1OS+*c>H$(G<aFTDJgrZDaGTX$mHZXfeKSU5_au$;>oKq4hGG% zL&XJG$PDQ6K^BD#s_MsnlGPE+$W^B`&a+Z+4;`*nyKil99^E(wW?t>#V_xYWHLl2} zIV`uiR-__g+<&m#Z*4E|wjKY1R2mCm%k2ayMSDw`Rz_<XkO4Jp9iw$j)U{W7#+T-9 z#!0H)xg9(UtCcr9i(U^Ono4I%UiIpRi0y6<+!vCGPv9^%kl1I2nubR_dn@JtGE3%K z#cDCp*SImKht;?%sv6gX!vH72S8ovJUM6hjW|y*4u_QYipZ>KA!3P$uIbB`dl`3&A zmT@gMT@ZpAxBys8zRtgoH+ebSaVA)maP?G1=G4x^Nw3mV0?qehWL35vMI~p$y0hGL z6@vHf-50P~uoe6yY&*D)Ekmi06LF!Jqz9#7kMvWexYMbAn{}`{3ZBsd6$5jBCujDp z<0N?b*1%T<-_Nxh`lKtla|FFqs7RZMtjHAwZ0Ck&s{x`#^S?36BNQN1JU^0f&TRoC z$}c)LW7)-n$CmAg&n(96AycC4!4<n#&B)$fduH~(afa&-<1Aiv_BiNqnK}&Z0oQkp zwvrKFy~VfD`8Nl`AUt0=V_p>_*D(~HvXyLW>HORuI0;ny$f9h{!Ud0=X0x%{l6NH$ z?lttWn}DQL521;-r~Kf$N_YPo)7H>3gI@Ivt}GnR=8W~Nn7_PE_3{sRNn`R~bs`g1 zoTh`7o4H*TRp7VBp=%>&t&Cd*Ny~@;{C)P;62d^dipuJYUV3-Dh<#a&AIxtrmX42( zYEH-8F3|^nY-=yw(?^d!hTojNxr~A!n$Ao+2mq*kZ&>Zm+BDC*sul=~!LUtWiokIB zxc(dNwyk&5o;>WRt)Q-Wj;fvuvJO&DLPe%mt@t!Oq^VsoIN0iTh%fh#`-{Ha?a8gf zj^yA3`=_NEONO0Z?}YVP*dL{T<jsgAnob~Nzu;@GNAJQ9Rd@nX+$kaw$MHg^PkBR= zf&se^(HAFh`NNg8o>}v|A&cE7$_0G=g;1s*WDQuRcq>cJ?z=8b5&i<)=3ELSW%Kff zs=my9Q%8?aMxZeDq=RBHg*&HnIeQ_}X@oh=f#?C^HSg?1dwLn#wu(o^uANrRZD;H; zYbOec$#wJB(u?w22{gV+zb~pv|Ag!q$N@^|6n+FV5-X=lR$jajjeRh$1tjht$URz1 zhw)(ksAr2;QBXH9T#A$6V4PsR7K)){JQb?79o6&*IwDPZknNqySIa6pwcs)~xN81I zKc-GmzZ$i(8RaU==$Dx{tD@4nph-V*=W{Ln97*VEN^F+u0!F<%$l=K`ikIp#<^Yt} z{rx1gk>;rVccPIo6hD=xPQ$PxVwl6Cl;YI6iLf3!aevhsyXXZovK#TOv0|*T+^ii5 z+YO`u(SO3@ybv-DG)w)E;@+ULoj_+<;mc#iW8{9<ye(3SZ;)G-=wF~DyKm6vASWTZ zO{{K^a*@#@kx(YwebLyZSuRj8?E_%FG1ez;oAd<-5Z|HKrz?QM@kiyg^yTHzUy{tp zF5XT;g!_*Qazk8=8NE;}L%7S-S;q4!m^lhZ4k`EYesEnQjft(kNupJ_b=pj?v(it| zcc_G<c@j8RZ3Z?kBwajzh1>Y!99vE`HdAK=Utac&Eq1uy!TLgOS-C1E90Am)B{Tiw z$>$Er{s{snLEaO5@u&zqxE@v;p6D&?u@40t{#VNA&7SZael};kGEwnHgD4V5RNM@g z(EL~B=A8&?pPPW-fTja0Oi6SVtI_(3ME!qWLg-uK2afWhBn(C2PAmUyu^2h?Y402i z9P03g5$1#etGdUUo?#skjQ|$*()ybRGMXM`-2?jjThnTcPV==7sg$k{GxYdF+S*zz z%d<d0#}l#z%*B))^3X;zIHWqQ8SVAp<}TGArkj|P$tP4REGB!_Bp_FaJ%zs~iK!`q ze{Xu9=0m>tBo(R9!7SW6Utq|wFpsKMSAH-x{WB|Cz62A8!p8!kHz1tM=9I=M&xqQG zz17xBW7t?Q?C%@4YC`p*za(>hOrK&ELyDQu{5ACOg9noZS1SGh{-FcLy_W;nf$N`N zGYxdIzy7mL3K@Kw65DmvPH0<Zl*R94v5zrUWwl03TdFkdFjL|nibA6)q$y=5&2UJB zTRM;5Tu}8iY79WP-2q{O81+!ho2Txe6{6e`GhHlIj$KCkPf-zmfBv~59rE9ZpxB^q zV<p<&{{dt8@k0dq@d$n)GfrqcvH8{i3;uWVLRTW1$Jr(Wps11*52rX6Ta}fn#sFeW zsN;+2m*q4Sr`KWwOUc^I%9<xg^sE><z<Ju(e@NF`Djg>@&;T{y&jP^AsaYENi}q|A z3}l}5V?z_VvpHf%CkpN@IK`czOuLPY=yBUf8Q3b9$X|kEiYROV$`T8T7ZjFPvKhbK zDYxzz99JRNzsx0f1Y>IrIQq9o+W(TsB(ZtN@4*)DMGr3?4~Jt|37IBI|7oQknQI3X zAWs`45xiCHga9;8+<O=u_fH>W{|!Yy>tic?%SNq=3EX@z2Mk!P0dKG0NCHNz0*F-a z`7K?6d*D4ri*=>wyQyQt{_t=t95*gB1|tdTg45fR{KmKD|3ZuM$QlkX{-tUkq@3Qd z-6X|jEyZa@tuxB}qrdlJdc0{8``%3M$xl8$9pUzkFa$Ww{Jocp9>;5~oNC8o`3GK& zy7_X8YoQDCO1TU_a%#Q+rC?Rr`r)W8CdpEe=>uMYDx6^46V_1DthgX`6CnF*E+%bY z=GYih(DizXEVFDuQ<JCx?PN>RPQY&dc2p;Pwo7L{I2r3;QV8IEPg1McP{PchEUDf} zbtSAoBMPt?&Q@{fG_3a7gzHl58O7e(h_F6^rKgU=a&(^WpgH3U%`tpj3CMVRA-uol z(hA)(VF{4@`k@PREUQJ_8w6CcMW4Pm06{fw^*>aMH%#ik6lD{{j~nT}Vw=wZ(;Ct& zi1nt}RmOGrVHP++5;Z@eE*lkdw~?>AJL_Yg!~p*adS_s1`_o<Um5gSP?T~>T1B26S zt&1-4twO45pMl<5B9T;SLH9Q?E>dBXcy@5k-{YQ5K!A`=YMYMlLOYc(+LdC<@@UIZ zxq%vI<;6P)=W4nRb7nxQ9KGzXsOjWs_3V-2*V+r}?dAZA7{7f*>^PxEw|6+WS0wAs zen2zj2cFKIr`~Ai<sHnmXQ=l#&EhF_$&1z$T*;zE9;?F~V#|Y69&3bZ#mvD7O-oFl z+Iu>`YU|OR4%DQw8uM=|g2B{;1Ho`mx@??e)rX!p$MSlA70pKVcvZ@|fYLpEV~s7G z>#?88yv{ekJpeJL<-?FY7wf10XpS{B4}jy{uc)7esm&J1)ZYt5LI_{)0BkN8Nc}ep zg%SYD0Cub3?KXLY*-dYn<aCFGIZ3oQ_yW5YfHIm$s?p-IJ4C0SY9D*8*Y4b&XU9a< zU1!(PbZz1rCOe*H*Kqi$)vnpn8-DHKLOAyVXV+lvn@0!7w{b)M^=#K~MRJnk1wNVb zsMrFntB3^K(~V&j@ZY92He`yi4I@C-b6EoYo3fc~RKp8eWvHYImrxo#QHMB;oE<{M z%39tlGnBG4CW?())O<2+tn|D*=R4{R$!)mU&Ddp2wd*$F;+$X;9BW26CaW`{bI+5g z1n~WJL=pJ-Rv*Y83>trghE|}%?RY5i3yVcPFlheiJUMLIr=Xp=<jaCVZxQfts*~Ar z7Zz5d(`Rwe73K}cMNg0Jgl34iPq(vF-oW5zT0F7<5SLJyWx+l|n}+Xv_Y!5kVw#kq z=d`&b^&VIkUE(eS4h@^<OEGw+^0d*YhA*n*s)g7LafzU?r^18b&?JfR!)uuL;!l0h z4+o_RVUjb7(QX<Xb94n*^Z=ehg-(PgN^F!gqGGFC&>U-^siywr8MF^JAEw<pKKeu~ z8)q?!JDMUq59ye58$^+fny3CrjH;~E`V>l2uQ$VIfuDFPisd}4W2ZxY$C`2`tBTA~ zG2P62@*~(9gYmO6#Ya<1TG#3rQd0BwVyNP@Ayt7B(h%z<@N>Iz;|2VkT8T3`anW@3 z03^F>TCLS9Y*sY)#=BX5!LYD9Z;z4QSOL2^Zw~0e;OutRfp)Xu83Yz~srLh8rR}fp z=#yHH{&=!mHgDg!b;9K@Ux9<Ms)l6QTJ87$oxA$knnKiY5(eqb)@iCc?X6$@C_7+f zmtQ&Ot^iFkZx+KP%K%)iR{{_Mx>9VmQ*K2Xn%gV6YWHHw(<_uA&($p}$2U2TIs7y+ zM7X5Yk#^wpDE4kQZmN3<X_YMKH6pw6hh0+aBHJXg{JzNS;Qq2Fy^B|@$OpyIviIpy zf4#Rju6G3m9!AEWA|DqF3FYR|>&VC<TG{HJ4FUx!M2Q58+W<nUh{I*84tz?H%3DxQ zs1>{!nno7wD2`bEeAwS;W6>$oUt#~E57Imre?b54{c$`tHdB6GMC`IZWLL(%j20Bh zW@}9_@4EsYT$u1Q3ZPWkvYxUX{6AcsV{;{1w60^@wv!dJW7}rOw!LE8wrwXJr(>&Q z+xFe(e7mP=RLy@dYSfEoS{pC8K<Uu1vz+scTso%QUQrRu;@TSRAVGOF0-?>XH4kGf zd``z`=z(*mSdLiXj&Y{>&akI{IMzo@tD>a^<(r*Ssf6Nz;ZsaLra9mcD`MN8$2`!w zj#+BZCrV}b_c=qEqt7{oF$>wI5*0B0kP{DNQ5_-V9dZ<9u;vm!(L2I_#p*nprX%tU z!{;Gb7IuVBg7pdB2!{X!ZgHqp5+?drImJ(UE6~P2|C?+`E<DC-F0~Vdfh3v4NM%jF za?=DNX3I;7iWJU)=~%<06lh>9th5QSv!}?=L}=tvcFMQuyE`=pek1zbRx<g`Q<pRK zx&9}H$bFQtec60I6|uSVy`cHK!f_KOk9TstY`XN_be~*qS<C``!FB<@h2)VhI&)zx zMx&wQXQngZPQqj2J;cIr>BAFdgqqB#0~EkA_CpTe0`e$i(eyMD!C!D0SjSaixQMIl zQ>-Dj?K($9qMGwhRqIt28n$`*FH_6v*JjZRnI<rr9n;liv$!)vElvyi9=5ABdM}$* zV;4_F>Mxz-qVe_KzSGY5Ph0$(^e$r-hLD4T4m@eV#69bG7_fQ>o`!yu97p=$)>fb; z&!>)wS*Fj!ag#iKWRWiC735;`@XxXFT)nniSe~^1r0v?bQ6_Fokmx~(-O5D{7$d>R z#Us$PxL8^}t1rpnJ@#E}+O?`@a4wB;n{#!lX6WlOwo}C3TgP%?N=BT*FrxR=JR(g$ zJn3EhTI~xj_mVxhFImqt22JE`CI;B~Pb~*cFE>{uL*2mnfeKb_aYO6sDC{Kh<R~k0 z*Uoct8q2)=I_<5>p%ba`v>+M4WqY2KK4@w{=P~Tzx42!1yHniJT#~*CHF5|TVC_n_ z&;r3b9d!f0;<hgU&xKMbnC#a^K(97JB*x7U9TyUu!Fk;5sWsZ}M`W<n!T{#QpJ^c% zOgoCy{d0X>?+iQ8rT1N>MM-D(HQrU-WWU9=w|>nbeG#luD0;ayPj`4=&7Ik$Z{Z3~ z!oob~d$cMHx9;vjAfJ{<vRpDShy<?hDrBBOJ@YV5ZWXaOGtJSz<sGS2-mS=W4_G)T zA1#J{1#aNe(0D1a?q}q2V*txd1Y6&-PU>XC6R<X1rZS3XQ!pRuP76hZ9}}94mDjuC zxY-^T>@pzkLW4q1ak{?IimWUVBKithq`vKQD14&60gGKCCale{X}Ft0By269l*P6r zuTm0E33lN!&zezRh=5l@mQP_RAR5sr^}&4j;(eFAj2@K*7>|(4IdGb4yB%g88|TKZ z^M@nOtS|f?{!z}s#}S=w{R0`LbVP{k5xhlw?;F>N1tIByWsnp`Bg)hb4sZR>Y12=3 z!#Anh?EEZFm==f$1I@Zw1Y6-%6aE;!l&t#!4vB-%4AfB{X;!sT(jBKx*-5qZn|89Z zK%Is6JLf#w>eauBET9VUE&>aD*^+~!ilaiM?p&mM&kqY3D1*5QUGBbUOI)=eY1dMv zJ=ybPA_VaWPE1+MDhiYq4$DfAeVIv!IP-*#v<L<d)(}RtEbU2zUd&$$>53?V-c^a) zG6p$+O#_1{V`nNcS`{^%iBn8Oi4fO$#Q7x-$tp2dRs-etYmui-mt@P{hh?ldJJP!? z`!i88d>h`9rIRd6=^pZVuo5}3zUbAX>~uzA4C%servKlplCW0(Ta+B&Eey1CQ5DDV zf2Mk*YRAVjE>){hi_9poOCsx=BU4gQV)kovP|^v!npW_>^LFUzY<xt1!zGkgHYX4~ zLjVTgLMx3Se}f9XM($nO{gYl$h{|<+$=N!|;XmQAcoFdaUYrF{C^p@tktk-*;hYQi zNcE<Qdb#aO5@5b1ox#Hy>Hx;MKo!BEj7Xy9Xg-A6>kWs*$)aMAWh^<Z3nACxNbQ;& zU7-_dy6Ksm-mlhLv(7Rrx%N8OT}CDWT=4TM)JoE`UAYK0FiV}l@|p=I1!tKv&LiZQ zSVG|KrCA>_0Fnx;eR|2;L0ZjLl*+F1Moh4?D&8h6H6jJQ+OxgwJV51#)zSmqvRnQ5 zz~62JXPCCiwK9W;yo9-%7Xka%OtQeVDK5SGr51}$q@i)OE>BHgfOFiV%SZ5E(VC*q zYujoHFnnF^qs^WhZG}uBRIs<YvwsNlAb+${F@Uz)*vHtaMQDdr2dZK4xo;O4dzM1= z?o9Pz%3<ix1-LO@?ZD&A`>4{4xGP&Tbtr=RJ?=4<NBYXYETOoB%>?;IaVA9Yzp!}H z9QDT#<IFZqPi<yDRi>L{7Y?)r=m^uc<Z};P%sitHIIo$9HxO}=EH*qLk6eT}xFJ^5 zTBydPDR7&9>WOjUuJh*FSmqL?!<1x{iOcP?l7BCorp91#(gUNGIQf@1)d1lXx(RAI zhm*TFNYgXZn_A}FPfh;WMHE%oCs8d+1emobQCt@YTjxcWoK81LeXY~+9)^+UOmeCk z)#LMg9G1`jWr;WZrrR$Gwve9&X+lKpB~*OkxAEnRpO&^BwsOm&TDeQBlvTv^nuju5 zyB8jH2{_Xtz=1n}8hD4nhhZvyxynbGz%2iKM-8|$N`wX8O-Toi=&@x087+joKHd4@ zsx+@?mPB(R?mMWCIeejm^dhs63ARzdm}jsA(O)QqT|m}QRWm-(Hzh#M1)wVV%1iJL zg(a=;b~-ZkGDk#mk1~G*z!7zGrRGL-8}=VILi|%;0knSAjJX1jZXYa@^cU6K|NAIP zkrpm_?r8?!`$D^>c>@hwX{b1l4f&cY;wwU&Q2vPM9oGB`Uj2&haf>bY84LFfn>4P} zUwt~VVTwui2<Q<-BhPj!kx~g+!mBJh`+;dWVRn18{rLuu59TJYW@eU#A|25*w}iL- zFL~9DDvDSxgI8wVAcQB(Ss}vMFm)hLb3<CSiL-8n!T2>oj$uGt#`OH>|MYjm8`R#n z{<Dz-XhA?oL6Qqk$$`^4u)hCeA@@x=J5X_v;*u~&Fw@CC!;l$*DWjpmm=4H+L8<jn zaE<qyQq61{In~13t5+|t)YdI8tdv@sfk})o7PZ#yFUaeDJol|WYwvu&8`!-Io@}ot zu`Im2f3$v1b)NX1@Sbd>C%^u?$@fW&NV}iCuMF`&DU3gT0TNA(vM@&mV$M7yWD^p3 zN996Z8he29k4NFCg+9PbnZ$<&>5-W0fbtK7!ePTkfP37tvtUFQiW$|1%XoEZO`#0Q z2^XjxY40!DruxCn-p%m|j1RfInIaROco}Cf&3zhkkBHj&Rt=WZ_VkNJdliOb-H{>p z4n>c+XW~q#1M6<*boFS%=vdUE3ndU*iM+EFUvAM1=)%}A49e~^iF9Tr^(nqF(J^n~ z49*I<-WXCZ`1EG0hYOd%nsoM{LT8_q$a&QSBz;#S3YCwj?)0mjn_saa@O3c^sMqwF z!ZcWHQHCT~S|SVe5eVTt=z64&T=<uU03rd5so+F-z+?MVAGPh8dn{0XMaz~|(XSr$ zBRisZ%KZ9!NVwlLLL2IRQfB&v=@{F4dji**>nI)wG<+4e2@}Gp9#uWEM+p-{L1PUC zM9N-bN73qWRRpT*YCLuK_D+uRgFcwsV<oL&LlP~tC1b`CF;?F?CPMMyQ>}^odrD$A zI~cJDK#5qb8UPL(A_=P(=)Z0U`Aq`WLGuPhE^-isi?g-0`OZ?4kK^MyAsY+mxqt5G z-B14#h=^(sGv*CF8}cd}Xwl*_z1KEt!uP`_(wPBT8=FmK<+VOOk}fZ4Gj*{W-MSmu zygps+?d@%?tx#Fn|0(KF86C^QEgcz^1&!sUz|u||p8_`<HX*ZNwU)|i7ky0^l7?O@ z{#QpBdbUo{!~x-fTWPz*Q^PAmwHsvIR-dBxzY(alcFEa>(gR(h#GELI8FrjSjfNCc zYJ9BHx955<zBzf0E>5<@$3ttNMYtIMa?NQe?V&_luijx2?!gBJ8tg}l4R@z5x73q4 zfZVtX0lZOzVV%@yTg!w5oMcYuMfGrD!RFwqChHhY`G22|vNLn!6a7VRi4gD!@Ae2K zT6A|%SwkYp{k$!ki4db&5nZ!Hg{8dj)h57Z<$r$9=s?;uzmx54DcKt)m0_ow(XjO@ z{}vbrW9)Fk2;8-9>tkzX!IEOW<s-*k1*RYO5mtqxllVeNq&YvwQedj(Uwq5ydR(tr zJ!U>7lMb$gf~wwZgu2{whBB$YvW7BQSPQZQDy~)5Wh@8*<tTDsMqo8i+x#A9|MtUL zOE-8O<*O>P!VrB-YNi~zFb27ia7U<dH3t2Sxu8Lpvi>toAd`4C|JS~iU%&Qw1UMjN zC(CRqwMFj@{DT5Q%Z!g{R<sk5Indzeq5P~3;qtI8U?OI_@930dZnMT-WW*l=ewTr& z&t|!n3g^^IF2GDuCA8fpfr2y^-i<I|8i>pCq?C<H#2(IS&-$KBvK_FL$v`Ea*g+D= z0IMHRzn*E^UdSDWdSVDiz#5U41l&dvdw0hz%iU9*0-q&i^S6@epXuO1<0|wLB_4G* zV9t@TTC+p6u%~13>pzVQqdKjxHQ1xa=u_EKr1ec5)TH;7hvWIn?hs@&K~48_$RK3+ zdu{2({Eh&7HD%B{)|+9CYaV^V1<$`JDFoj0UB!kwzCp*vlO(9kJe-Iv4aj7J^fJER zTEQS`H@RGhfs9w?M)S`;LliZ`Qvu3g2?r)nr?wT^cRJy(wBCr0MDqtRFHm$E%-!6g zMLRw$2+YPDN~0`{Vm}H&to@Nr&fF{~L0>m}Ghn>Vj81s`EIQnE@l@Jse`#}N0!!DL zkzs?x4I;fLH-LS+=E9Vl88}Td=@l&5&xyb1KaYf^1>c=cC+$#bcr7(`-gQsjD7Tws zxszZy^8Sv(2%nbY|4UVV<}>Y_l1lTjr<eN(R&0M>Ky;Y5${ej*V%OT0+D~Ec3-9;X zs?8%af6+X@s}jQO+NREG?W&1rhl(x1!Yfpt@?JLkH~UV_9l*DG6qvuakx_O+bAq=s z({A;t{jPMtJAA3|O@KE~J3M!)@g5`5KHrMBrNC_Vh4B|&pimlm=+i4!K-R<3m20bD zzS$K<fWtJ4oP(U~1<u9V7kejmlcJ!J9p^nS>i+QfH%hnUo)1S~{GWomug`!{WD(v+ zuvqIy(f7nr<hvoMAK*B_5)E7Be12wS0vsEg>v3AgZ=8rf6?es-84@=OK6qbY0wJ-G zL(2<H9N%cfbFY)1n6ZE8!p7cUWAhKo*}w6K)n2V)^Y`j)J{fkh*fXWVb1Mr>?kPhb zZ{|(D3#69jUn8s@S7FY>F%&HMCc-%c24`6k2Tkw<XB}g(LxAolK#?kmLxq^EQBP(H zlP(HrT%jK!cGk<)S8)NluymBrZ&1QtXY~)+E4kmSjSq;2^LtvIyI0wNMKf9I?%G36 z{B#kR;4ul%n>B}T>7a66k$Rk>2x3dp&D-EP;6vCr%iE>GKFx;(izH3Le$SQsp0A%5 zm-Se9<@jb?{00JSx_;^KuDtmei!?oLZDoJ59(**b_6Y`2ZP$kvK4#2^Lk;B5oCirY zRlPg?{iEPr_J_ES2=O`sJ_qloEFsXBDQ+Z4sZubH45vc)72Y|~@)oVTzXL$U?w#*n zclYx8f%j*|f#eOo&_;}Am3`vA@XpB}-9L>H4kiQkO%r&~{%W@YWSeD_%B5+F67d*j z?Utu*W~cd#8x`Co76I~a0hZ}GzEOX;;hDT#z2m$G4zcHYIefxJIe3HizO!1pDziPE z*|lfM&rHZW`dhSY#7rpieqo!w>m&7!e)<F*&tbU3kACh&oW+kwN|2E(-ay+D-`4?$ zj@?hUY=CBoc)ZbCXQk};9piPJgQdWtyKb^Nv$u+MJ8bl6gLQG^tX2XA(G;ng(JFnS zsiyue25M?R%raxl+a%WL`4R94S2cF$AfJr)NXom`Mz!~JT=i&}@^nSX39cCqv_!F9 z*9hzao083$e<5kl#^MjfN{BsD)#Mh88C8oT6wupT!E(|$_qx#1H2#SoH?ukS?=$~f zS6Bc{uw0o%y4lKhjYR_;@iHj*mxweLme7-SRT8%~b7{c>!(++5So5!vv0pL0Wxlkw z;_!rN(U<NN%T}EO7ZTtCby<}Ss-VpqUSkobS(<6`0>5yR9=>CNO_J%S#)QEl@X^i< z$-v~-byW{BRXav4GT1VHt3jrFK9-@DZunt&iHnR->YIe?0!h%8oHlN&$VawG{+?<< zoY3lysffn`42Anr(od87p_%kBvtEl~1Jq51oU>0Cs?E%&n0t{t#)ExsgW$H{YuO*? z(`4X_deFhMU*%36&*Y&?o78sAOZl$&98gl@b9zEa>Ul`Eht&~4&@b1AzPD7{!Ati$ zwXVr7)>u0Sv&p#{<w^OJuXE-Z2v0}GfMX-?!3^`DhfE@+{3h8!XD_X>4{|Qcx56H> zF?_X1-NV9Zi{jD!EQY!op(nLS=XU(DmJtXhf;wDL&4dvd`O>zAaBzN(?%law3sn1p z_#_Z!M+Gw0@Qk>REY&5+l&ECBG20Y4{6#618u0a_FxP38r-^@-!(PFvJl*UdjdBDn z11S4BYW3AgDE#Gc`TX_x<1XiTCE<BmxCTKiU1eWAQd5ICIF~t*)bBm{&>R)+z?$_X z7n&6Ev$hKOggBsrg&CpBUpqPE1~%I*WKQW)@&B^`ZW5)SBHYAX27S#;6vo)8c5BcH z!iREPvmG%-xk%IahqAZVSke7KH%Rm!>V_tpH`>bSS4Y|tT-m!g!=Ni9VbK>Rx}WE8 z1ss1w(!|#dy?b|&w)Q0+&&lInD4O`WjJ{*tN3GHw8{8SD?rdB!ZRgxa1F<=81)1({ z2JvQ<rlLW*!>>m?i8VI<$}9MmtE)MyKN(H%%Ec)=3jmP)K#QS&7qL0o;%>!jhlVO3 z&jsJtdo5DnGgt&A^6{Y8a8ne9+lmC2B)oq7mWC?KoKbd`r)Uj|vMQx$o%)qPrk?b_ zW1Nh}Mw*Y_&L<adhzt~44<sD8E@N?iiA!40NF;fJIbv(oBqWXBb>N|blw(R<QP%bg zfy+cw3mIoG)4c8D6Uu4@d4$2_wVVvR)1*V*S13R1RJ+$wq^m6oZOP(P0e72QPXY>7 zFqMcuihIjBcSQDyLEoxd@%w52JEp%6<HNtG?r(a&9^5`yvjLKwNFJ)6!iMG%lsh}q zJ*Kp(NEP$(5E6|QULuCK2LAX*^G|~IQPn!lsUYY>+H?S#HPt_I1T@F@jW@935OmoG zE^SH~5V5=!n&E+yvOEFgM<8j%Fift}(j53d3V%<U*1dI_o?@m~Yy#-_9gZ-I4{Iy< z#1`qRkmqCI@_`R1(>1r9NT`}I%2p0$%QVx!#G2{NyO0x+|GF&XFcta601En$nx7I1 zQqAX}hG!*o<m}YsuUa5D(Nt>ND@sdrvXZQ=WU5MOE7QtKbgX45%?B?waqj`sNjDd- zUTH|{!iKvo{j~L-X=^?Us9D+2O!SG>$w%in^7zGGy+BMpnFr)#L4Zc0>7HJeEGS(u z(RiPD!>0L<(^-m_3%r!)MMdobk+T+6rOX^H>@PRjP^E3Fvx;U$0pz%a=(m-W6LZ}U zX2QnW7lPQm!-pgsRh$Rxq+tS|LfE_T9hZ*a3%%5EE8!rlmCi9s<r^HpHRN8CGTqH> zC%T&Q39zQ(krY&I&{y3pYWA%5nHIL{j;9dmcaU{*@}l1i1fbF-HD&(6I+spEHr?l5 z6XUR+=CRY)I%wupKQI4<MBgzZjq-HLr@}EN?ZsO&W?`bZ6q4=X<$@kg2L7nM;lCA| z%aH>-`6@A*Z2p<?v5E`9rH2!?cc(Ngg_Nw=2FfoBEVHl)3Rfe=?1a~zZ7;)G-*(0& zp1V+<UY?t@)cz7aAt<>1C5}Q+EOD4Yb@LB`10Ghl=YqM}RO`lWgijdXcY?-_PlpTe z5*pPp$8~kOI0r-}EJwDCeZBX!`~Vja_Xl`%VEZe$l0N#Q`pQFV5Kk9_nkJD}iNtEl z0C^Kr-ATPgZ(oeg!%ExcVXg|I_d=BoM=ZHAT`5PDZJr04Ur3RdN~zCSJui+P?cOm? zZ_4uvSbO6q9^3ohA?X&NT{--uRs)j1^n_QP0Q$3&rxFIzTz7O`nX?jRXhg1DeB#5) z(GfV1DF?0?JQ|Qk@MriD8NQBaWe&#2Kv2Q%Q{4hBkh-u_vne>zF%J~@`u;J25*=?$ zdhu8F1#*^Vel)g8@`n!4w}b9O5M<kYHz$6-_&#Jr%sUnD-0c8h6f=(k<N=9#g;J!5 z9UP(NseI_R%BfxGc4DSW#9E{ju_TRL=m^?H%YqYg{o$}EDN%TgcDAWQqlk?J5aWG- z8%@oL5#obbi8)5}Z+vkgYJAnJUwy6vV|bo8QZ-R=o550_`{-O?zQU*N;B4pX8D44s zKpV^X*^_-8K`B7etl(q5#?|5|uN$N@TOl-i(;>Z9mGr6l(IoOWq9%{A1u0kLk75}< z&VTouJCQe<1WILdAsGA2MManwFz@+UBd8q0t~Z?>7i9wlMSc4rIngyRBL7^uYc7hA zBHUFVhg$Uoyx@ss=>vt^E5y7o;$7KRvv{t|CpAnB&qk`W5$c_mfC9N(b79uh8{1b@ z`%f{Lmb-*Z{$${zz}Myib@*kI7yMEizc6;Irq>h1)$KEnLBTf!E}{B15VVoV)p+aT z76}rh#zlkeIT-ez_6b@mR`!5_WT}T{kciOQ8yX_<@OT6_Pm<rl$)Vnwn}StcwD@;O z@4expd<CM>xrmJyWnWqxT>-Aho3b*pIl1(z(06k|pbILiK8h1e<%dkjsXB~8Vf{m4 z;ClZn{kzSkl4$w-j^Qx`(3BIce`g>_bgmJy8*cgJ=8Ty6LZs*o(tJ?TUi$1Et5WlE zPm1hE>IZ@-G>o3sf#8sEAr@8W4+aYgQTPkDDhUV$hNQpvpEmwC*qRWQY}4A92_0DZ zmPs>)&dZ8l5)X-zicS159QB<YjKb!vN5as14tK${#7$Z4>4{Zwz=3=NVHv+vF*NB9 z1yz|ms<R!0O|!?@rnj7MiYMU-4<$FjBB<(%!DEMnc7}Y~MxQKRit6s<nzr$yJX_)C zGKj=Uzj3o%)jB5WT773fE9<Lg1C1IP$$`-rq5A%O+|$r*NbfBH%>vE4PVio9vx4?D z{ZQdbB!aR@<gzRfAbplWxT}MqKf$vny7D8I%-Du2D4JZCcGu9#Uv{Z)riH<Wgv}py z>k>T3)149tjYac!k9CIDV$2WZDZLI0o-b<iYva`K=<f|qTR<NfS*e&+*d|&BbMds( zyaVMP-#anSHxnHODJ0L{;A^<7>>X4G9HSuePIX}6fDMrw_{k4w^WTJKctikHje-7u zn7gF^^f9vkrII_IBPZA9zyVn%O~I^a3h^!RY1?E;v_(46kl<lQEMeF|MGC~Jx`)h@ z)-&DQ(}egd+N^aQE>c%M2I=TV%+aGbx1n_|{GwNit$QzspH)ZRKc+9Ky0a-Mj~~W; z9=1QW{@mQWZ0CL4h$4e)g#u@U;Tecj_<Ay+v%}eu<6<;SDF_l#2Z0SC#jK{Lo=!1; z>=E}U`TnGM7>o{0dU4MT*|8>hhQ`?UB!zFB>>~9<{V@O>aC9U~Une<n8Q>3IWIR5R z_5_;sDvxI0ns0l_QeF?}X5QNM`1(*9drDI7dr~8llWtCKyo`HdZv%?+Yo+%2`Fb=5 zKSVr%FvKu>!KA)Y5<ql;DQIFND{2Z~wNaf-rsQzq<Xb0LYD!;`g7*k(29BBx>&sPD zuJbS|=5`k){vruC`iTofuv9tp)kTGFd-$o@dfQ&XgVVImF;1#Xx#`I3vul#F$qWYb z%<w0%QH@LmL+x@i>LOU(SbQDVH4RnT>9}Wa7hO`?yKvd%M<7B)^-9gvI0d9NpIMkS zRT00KAyowFDZ=SlDLo`s`r?978R0T>hJCU9`HXoWFBuyu7Ifhz-OU9hFUQuonGfWr zokmWPK)otgYn@!v?`Dtcubl8K1%*k2<rS~ojmPG6Fqv?xBu`QKat<8>j$mrp>~SkW z=^_So$+T1|P2fC#QyVCNlVUHq?y@pBngYPoosbeTuE5F>N&Y)$kL=WDpkyH~cO!1J zMU8RHS*10ceS^H7l>?Ax-ySAEq;fFak>8M}foyYCs-;Rmzg$T;k1$Bi^ZQD=+=cv~ zbPGjC8@KD2%G>R7`kXxj(wO;v?YYy^+8h$cQIphb3NS<!jfljCT3%6>8{p_AkYO+3 z@r-QEvcg|3shClf+$g=3b_M|nrQ|lu+E$yX&=MQ;_k3cF{6!0wx6Dg;;-oBc9EN>k zD#NH0R)&||qCZOZwIv9erOFWBUabK&8^iW^&#Oat0LxZ=F3cTrBau=&v<f4}D7}AB zCBlCC6eywBj2Uwf6BO>4cK^>5k@gj#zWtyXj%YL_X!h>bYx@JNuVPpBwJE56w;HXl zZ1;k@d>8+<EJPB+7e1cum6M9XS8~APjz3UHRi>2?a%T+rZv`KSlm|ckXJH62?JJAR z7ldHyEgPiZ7!yX$7!&3vTs-Y7hkx;Id(DrB6cEMyABU(*M((X7YWt-L#i`S$!5}fl zC#oXNEBbfMF4HSLYC0$tY1Q-u&Ykz7^Eumbt#?%(T*Y>yC7L`~p}oAkt~tH*7e4Q& z$EWB(at2C8c9em~sOw`1CvA#}IOF9Z2~%FBmb4G8IYeC!Dm&P!zH#Jna-NO;Qd{(7 zATVoYNg}*h`Jn02H$^WRu1L+psWjwYMr~!BZZ{afjMr|Rh^JQYjck*m8ZE0?)~vqw zSAykMDOKwNT}~IGR-3e435!bEmBPlvKn{**+>sru9y;ynv+RdQX`cNo_%uiQyM~gY zkNXTcZ~J38fc(I+Tg@T>ta#K|CyTKv73iu?Y3>J!+07C?lcTyZWvw|?(w33jJN{5- zynWxvFsqw231<32Aj^xVe<U#72G10sf0!xyZDrlme}19;P^N9e7&iRL`U^&y)&Mu; z1o9|B1Quzuvrvf@J5^cG?YFeA=2fFxS1kDma)?=}LvfF8bv{SIk;|qPB^skvG_XIn z+t3`$<F#mz20bh_gVhc(yUppj%<T}oX^+g$p3boF_3&hw5|thbiJDZbRq?5BqbS`> zS{qBm^{P2re~|C%4rPHF|F>PqE#D4Gqy(PQqW(YSb36aV+<T}R6L35Wlm74ft8P7y z$9L&HpNd^+E<%Hg^|Wlpfi#if-$0qkNqS2oNc`^`ix&DQf`7tIOAwe^M{3Dgb{KDQ zw{<y;b{XwTiho>ngr7;Z^rsa`1CFOVGl|5m<s8U7$}AFj7rP_<$=^?bym^Y7gpaMn zm<g7BZANc2Vnb2NYV7QBgR3ad@q;@kNHJt{C`fT+Ys^q*phY$P@RkT@f<UQ7c&nOH z{-gi}SI}#0n<8jPLD<un$Ja0vLw-yS3)FKPPZUh;DIWMv2ZYpZnen13b4En%Dg0s& zrmOd+4qJ$ebPHv+AoG7HJ!gbd#A8Ri8ZjmVFe2>BdB0*q*?%XBXPjPm^A~cwh}`D~ z?6gO&d^<6m>+l5?;>v6BSph|=1uthK(GEITC3RddQQ6I%I8e=$ZwLj#N5a1>8ivCg zc9PxY9k%zK80_2>^XcdCV4!Dqbplas_v<ep_?bj&2hb={RnU-<vhz52#P@c&)1LA? z&B>^F62wKZCbfyb7Wbkyg+t5R?jVp_p=87)rAsVG;p?@}0DhfjF2KY=ur_sDRN5Z@ zBoczZ8+*l`4CNsWF7`5M9V-hSSKJz^0xO62%BvUldB37t{XX4Ba8~4nB7(_iRUV7C zZ;UVO848`?$wGFpL>#F1+QXS!<CQiPEoJ6rnB8}|_b%K;)}%K>7Eecu#h!577tuSg z6^-(>A_N+VK1MVMP=Fhb(cBTDWU#U9m4gz0I*3`Ekeu#d_-kiPg!qv3`67kym=Gc@ z4AmeEJ6{D5GT9l)0Nt?D)UZ!J6$_sfK%VCX&4dy{lH3oNgOFQ2La|}=(_+;?BPZhJ zb<ll&#_NG;wOj!{%5z6}q@<Jwtt%O%UGd>klwJ?_h@!#;1t8lY{2DbWMd63lRBe~A zUI018<L`bjSQgm3@_A00!n2)@H$g2)wYi9C#ULb2he8y}9yGBffV2|7MdP>Hx{L;2 zP!4pmu_b}ynHxga0}8?m18nj=$kLnve9s^Ie^-H@{|7@7h%5N$^Is(t_dm!303><- zFJ^N8IbO0tDI&&}NbSz6da0ByoGx4z$_S2h1eJKQLn#puSq70^es*d-_l4(XJ#*_n zK*J}P(truL6NXuaq7uz`1IeN|p&1V&u2eyhN#=m1r|%dhlWusBQB&9Kj?1K#Hhvs^ z-dw2ubqArME!@rtqD~^LMn}(jgSFkP6{lq?QJpdKZ;mfckF6(uBjSn{+8(#`kG@;n zm3xcjQ0qycjaDG+MetaBT!=+z$|gzdx#dMIAswr_Th_kYiKDKk!&_UmUaRf(O6SR6 zzMcwVclitdu{K&Gt?B%0$DH%Ka)m`JL6Z#Jpcu<41@jFbBz1!FpuJbOJ)Z8kHKT}Q z_!}IRR?c>0&Nt<?lmT_>&Qj;h!jwPEdQD`+lYT-#aWIWB5Cq~_MoaCWl~Jf%0pW3b z-Ku(nGC90fjj`rXh7Cc(Xf)$}yt?d+VM=r=6)FS@`OQ&6LV5%jY**8LDEo=q2-2;W zXLFz5Yj$C0KPF35%Za62bizyq5V&Un=D1ejqYy`jNUkEZx`7gG{jZU)SoHqE-`bUo zsxgy5URx|pOM9qlM|Bp2^+Otw#8?sx1ynFD)OACtwIT+Y1B}#snwfkd`ZNWUuZ1Dg z3J5J&JYAt6fN_#GTqdGv#wb8&nj)t%)0R_2(EHvf6Pta)r*dD@@=u{net~%WnTTt@ zjak199mId#cZ9@4m$bZo{wloNngnd}jm87j!n|hi9Gq)eq)1}J<Cu&o1i^;+eFAgI zh+=GX!OJVp-=Bnu55u&Sc^7ui1sS>2NY6a=#-LWMACKc?Fn0eJgkvFVwzHPJS<V2| z{vUgIS&+K9XZ&^z3^Z(78P=5YauTAD6aqFZAfz;~tA~PV{4aykF(Spnzo?bo*4kFU z%2u0eV{HY6VU=ZThpvrB?V65_?!}6_oqFF-r-M1GG!fXhfYi0$4Wq?ZzRPU4*JrTL z4RsOEXI;Mgy_OYRDK7Q2rxn#<F)UDuP1%6@tuln#hHpgtGaCBy@T8K}BZ*yDFn_9C z%B3p5+$k}??&NG?YOZ5V(WFysCEZ%@_`q3kYZ?=8_Hj|H?s1WyPgcTwa*@C*tNM;l z5YnqD|KtHoaQRlmOP^xe*h*yRltlB*Gjg<i{82^FFDZR|cg$B0@Z~JHdJBXjUY(ed zN&I%z?wC+K=$sj=E)^6#@D>Cda^P{jTCuDdIo7gYl<=sY)}+_Q3T%^*<8y46+?f*t zH^<~z8%7i-y{g&sZx`Wx(?%_9eB=1?F3Q=~ZWpcXS2{)%Z9?Cz?VlQHnd}xq*zI2y zC9dbVFHaskv)NGv?a~q}@_}vlro>|<@v`XmF4Xxq2O;^%wnr{e?a?y4zMGVO?J%x^ zqr6{Bq#9Sdib%!nZ>kG=6?f%d7)P_OZ)Dq)iWU>+(HwnZ2ea?AwD@Sgm6u&|?0uVx zHxW#~O1#4B=U!!E>x~yKjHM?d#H@c!rP-Zxm{VDkNw8W`WrERLYXUVKYIYoFqPj*A zFD}v?HkI1j_Hx{o@ika5m+~!ax#-9xYI>XIWkO7@)a8b3_C=V??O4fZ7soW&yvXmK z-Ps1%D+Tf_>unWrYEhe=B?nJ0+0j#<tkHP=YVQaer8X_ivd&u??8UteI$ZHIBss)l zI3+{wX%+BSj_L-=B7x?bvJ|NZ4qTdH<mPU&(V22}EXH9;wF{9VSIS%gIUds7SZ!&n zx%!`^-hLfaV_4m$=*itw+t#eHHOOOAVQMYwa<<IOv_ui@IEnHSWDGZF!tKkbVJB3) z&TW2(XI}u=#fB-&y^@iZHRa%GcjWN4&<$1CA-FLUb;`EIA>f@%V`N7WrAJ=nVTZJE zu||VpNVe*I9}B7xo>6jqrpD3elbe=GMt4c$PzD=N*o1C^{TEqP{ol-`R~MW*V!kQ% zn+%OSPE%}dn?Wye?nKP0-xm5TJ80J_9&2daEWBpADhIPefDBt{al>tbKt)<2snTIu zZ=8K+!iMD>YoHCf*0G)b%;7n6H#1R~!v@As4^5D1lst)5TM3#`b+<F(9IRy>OnbI8 ze2bnPSnwdjYL}M91Q_*VgiH&E$IwTZ8S_za4*+yAgj5BfnG{is4=6U<z$;eWlRL0s z{|=JyVgDvu?6@)~+g!=D8F;$mPH-C~sST&Qib!Wy0G1yeQ$;WfU?b2^naYxFT79f- z;VB4hD0BKvNzz>mO(6JZKUR5SgyC~B8+P%s38NFVIE@Q6rfXPzmilun?o|)VM7f+` zBdcF#M3FbOR$Q@j4_G#;NQenj3gRkK>d0ZD3{BN3G>@?AF2^t#o1j%e<=&-KcS+6# zm6Eq30rjfpO$--s?Bj7Y=s=H~<AiM@I6zWLtd`wDebR6ob2B5tlBFX-hIb9EmDm*< zxJh~KgC}hD9q?-1S><(V?^04ns*QVD^CIxlO0hb~rThyP*JH%;Os3o-J4%j@DjkQ* zLeNu35%fvejsqOEvSa^M)%+~Sb>V1HspK+y1Fw_zI1{Y*=POV}KhLx<6ibQ~4s47T z9GzXb!%Psmx}s#;glavT22gg7+Otqq7wiTH1hgtBRnI*GQ#>D9U4?Q(U=8Ef&r_)N z0=gyY`$sC*AdM`2lT31sy!%Z?Ys5TOU?=+5bRrov=-JL8B#s+Yvyd!I7ej~T!?yqB z0G*_hL^v2o@bg96In$!D)){V8(7HmoIrS38vkt=Hk`(G)a-;#YyjiDcdB0a)e+l(c zZm;JipJkXo>r!!n|Drb)#WeSzW$q%|2m4c~$7Z)uqb+w8Cuw%9_w^&^?xo*ck_nj3 z@uxkG#F&A0mw=OGT>nKcYT1XP<Gdf@47_MSsD&IWbDOeFEo@b?RL_B_1$<>=j~}ze zn><9CpZC;te(7Psr&pm%h}d%@$tGv<UxX}=*aM4ykuEf58E~nlojqdk*Dn4(Q_m$T zfOdNa?=4v*^%g6iT`2vfFPDD0B>Umk74-*flv?d+qOAVh6;i))(ag1T^!K6{7w~ue z!|EGUtV7CwfxW&=hxs>+K1hz!@B+U!ly3QxjW>KHQcY2c$WirWOqv|mZz>>sCYc8( zb%Zcz*FDj9+sw}1&G{$)chro>?Mq@q&LmDOu;2mtO(FN?UjNt5^ovxp;t5fo@QHzU z;@Re6YR|x?3ORQ%4G;Mm9#`^!7H|`;Xumbak->7ftC1n_fQOOC(Y%4vPXoHvvjLG> zc<yqrk?0I*W~S~UFOshGcSq~=iILXHm#@%?Pp9Gu@twY$1)+Kb`wfNGaPWLQRp89a z-4`D5(i^CMg4I(a2i7VsENRv<|7in!3UCVf%oTsD?qTQ?Jr{YPWV2Cv0$Ao4J+VLF znZ3ATm<@W;mZyh7AH@N>8D~=@;n6U(W)GDu&xX|!V_A-YIzVVtZDOu0=ci9mBwRhz zFqbia8@GeR7L*&w&8f2`d^!*4v5n9uA^pY1j~onD8Uz=Xti(&Y5<IDECOao3)Z$76 z(S_0d_-v?4MilB-BSuG?2W*J$%q-GkZWlr->Vt=jP7-gF6G4=5qf>o$TuBF<{bDQW z0b?DoR%bxUoO?s<1AS5!>{}@}*5I}_zrca*l2lfIwAeWp8$3<JsCh7XESiabK>sC3 ztEe~-=&EHrxI++EdY}cv7fZKqiMa;iYSBl>2Oym1mZ4f5e0y;F2GSZMs^!hUS$x*a z2x9lgyVN0Mf+2;s^Orv`y{3ztYA$?w2dJ!1D4*;^h;JGzMmFu3ry<sWxcMuj$$#CF zDIJ`+eG~pEcrY5zJqSO}Xn~cn*{_tUjv`1Ij~I~4CAhp~N(r{R6QiMdI}wis);|yr zv*_^gu!Th_AEdi}1Iho?+~vFahyK(&{C=&^*fv^C|Mh1AxcJ85Pe@s7f?K+%`x!Rl z5JkST|Hp!Xuf%=fjX0_Mr}E*?t6A<3;5v_Q4c7)5BN*Lz(djznbx&|Vt=-K#{k!^s z_fx;%?lc@^G3KBqT*=>}jIu)6VTR`}{ypX<b+XC8EbEb!7`V{sIS!zAp|Lxa8unPr zPb7!Z;HY>CA07t@KT>O#Gs%@vd7>me@^RA7eN=#Q>CzXb-L%&MZzWdOV}12D8!Qm# z!NxL)Cak9k8f)TR<e_%XeN4PIhl7f&?W=A&fBL=j+Q{f5?=mjQxCW(j)sgB_N6{<s zE?fWaE?a&N3|P+4AAu}X-2^oV+X1DAvu*~CK>!7r3e|{Z$-S|MS9FN8DrR3$qkh}! z<`ucgSNcmAQP!FnVJ+dIMQmR>##46@b&ruT(WY`9yt%YXg3x?K^J#|)6Kj>n_;2)0 zm3y_Qk*;Ud)nT%?iqrJm(>i>`eX-3+%cjK$o3rJfDbTKEad5T1T|O7#9NrqHu~rmt zN#oz<o)2jKu!=J#{PD=+#q9%GaT`b$Wk`E!R$XI?9|MF0%y&XTY8RVPFx>S^(SDrA zsv(RB8@C1~R?f8Zekms{TPVD5IM3Z5td7{^#dnE0>oo=gjzot0pc|W2-CS6Sq_xY2 zKMDYyz&m62bzH&UjDIx#Y3dY%4v<=hB-68UFkV`<fGL*~eSD)bNC;=(kz>UdO2n=$ z#L&BUcq-2)V8}*ybjF?kF<WrQF@k@BMgqso=E?N+RS3fK#OED&OA>jFJ<FHbNgJ<F zENU~*j|A|LQXqF~C1Z3_xxi<tcJHaFMjCnk0-~C2LleP2Tc&RMnZZ##aro#3W_32f z6FhZ^)EVwyf!j>jt1T<@KGe!$-^(q=N1LgKCHaX=4v=|7;o~<0rzSEhRMu+*`oOKW z5<b)S61zte!<ilusc-=n!t&vXmSQ0pQY&<Lo2cp(80G_f2?_<=t5)Dl%M$K5;~aM( zKy7@M;#(vw#OO?Yjd!<(>?SX<;N?sF@l6-Kc}=7kTvS>_d~#<qE;_50%uJE_?W0)x zW`}>^UkwD#!5W!16`<H|l0$zipl7I(70PneM8t^T=E~35hFwv&?=7)0|BgWB!5?CY zKBdneRs9L76BC58sV}OY_eXNb2ZiK%2U?Z(ru_aV`CS%dRD3pXu<$Z}yU^rUlA3ly z?qD?{fY+$$^vd@y@=%-?pO@Z>VLA}O#fomaSk+2EKlne)J(XWzpHxYn7?p-1nR=c# zTBjb)7n*)FYNEN|o3!YkmYQ&hI$^e|!bc*!!0>rekNz!DNYZ#$6A^<LWMt0|OQ5Z) zRX_f1O1k=^iV<?GPr&F7kJOC#oD&O;rtk_Sd!$0Ks%2)9OX@U${zT=SRUbOi^h@)! zx?n$C<9BeX-kPpV_ZgHWC#?6j6qnI5EqkW@XmYy!fIXM{b`<|>S^LvoH_P$Rlp7@a zv#OyyvAiwaMX5Am9pv?V@u_5A0<e58*Mm*I4#v~l@*d2i@S}{}Ar+dhPK1WXY&J2S z-rMq=XhGD#3&s;U?PX4y)Poo@+xzS=VXU=Z_Y|??=1;77Y#BMIN1?}c=*L+~0@j@O zsoD#GonaT3qdL6I4$0HB!t#OAVB6);%82Vr0fq-{R2vF3UAgsKf5JAq0*G?uUeO9p z-vycrzq_C7isqMOK386m!#Lch&wM<x)*5X+Ums+Gzx!|6(T0s+1Gd8r+z}hpTGQ|n z?<v0mkqS_7_K5gS6@v~k?J!T2(S$y`6IRIa4-CZ*x_Q6rqN%Pkt`A<6yhiBA$2C0Z zOXXeQ3xt(x?(9)i$tn0l1=yAR9JH;!#unc~PI9qqSuSzF(e)orU#r+J#G$T|p5y$^ zPpJ5=PB~$x_zZ8kM|KFhqX|2q1^fuc(p&6X#dB^^$tl2``Ifn@V9k#>mA!KU|3&r8 zpROC7?dY#2mr0fJZOR46^c1;}+FVaQ9q~Ysb}-iX@Fj05!hZBw3NZdz=k&|W(w7ht zbW%mADXI^t)}f#^V80V&k3;4+rO}GH9b8#W9#VgsSAjF*maJdH`dPzgJo81_2Xj6B z<izGz2Kq)U#i|d+(&_H-izQx-B-u;w8Mhxn%rHL(W_U$cOHcZ7$R;LQhD&|<djDz9 zG~@$EsfM;mGCnvw9x$5^GgiL8ssi`^aelBYpgbLma5GyxxAyER;WV2ap#ld!9*(MP zDBoqCsi@nE=ZQu>J?M*!<ZO&C5V1J)5hah}Y{|5>zA#+fIE5N^f$!-N9dpW~a%ubr zd_d2GxJYsVk4Ts)vAZiCi+n{SDW=MO5zSQ=ui$AD&S~!p9(aku@VF^KE&Dp%D0<Vf zRgw<f0^Y(Nf0@9mC$LruE%alMflG@JmjS{x6?TWcDE!xzc<^kmM%H4{64D=${$#%R z5eFl{R9js0KMKZ>f|I?$O6l|8FC5g+$-iz8m9mo|L&C8{W5`2ds*u}tmk?Njg-NH$ zuYOT^Z6+X4k3hP4;z6TETdvNR=lR#Nrl9yIl_xy=)8Zrf?T?DGarFi;1Ez}5*}eDF z*k0GJ++IymAM%H#tFlzTmafY98Ox-XcLSY8SwvFPht`ItUu$z4q86N?zTuX>LiAb= zlK=f#yCxc&orpOyjF0y`XPSLU#kcRfrbv8KNQJvbMg)Z051D(nq^I#O+N~k_rE3^b z7d~@V=<*_xEmBf5X;pk)FMi%&)Db#b=!dc5kMQgRc5;-gb;nNfstPyH)^Ix8@L!5{ zlF1VP3$6U7zVU~d<_qiWn#c2qxq?4l>5EY05pwrj9OV5a;9Pd1I5*(JJPX!(wjzNZ ztk+_oHW*koHw&sj%v}q8^&1R8`YYHU@|{TOdBLH70I};=UY@EUkS01XT#dOHO5)we zAg~vu^3FrMVKr&i1H#u2m-wJuqWB1}w_x5H(JExSxDp4Qq{9U}k>OtiWp+5U@H6vL zBilZ%XL1Ifs^Mk%ad$;&xX#5S+!T>@H@Oek$1*TUQ21Cg<@w+eVAbh%`sIUJ;&s28 z&b|j-P)*TP#fmBIGS^y9D=0=;SE@SUw34e=<)|rOh7_<WYtO?~PaS^|C#x6Hg5Bu5 zO&HuXQEm9h)!-8=F@FKm#-+s93rpQ2-hA5^uss8%3#;*+Xb|QFuT=Dx=Wwu3rQ$XU ztrtjVb5Hl)%-WgZ!QHuC4i@`8sJacA$oE#Q(go#-a^ug?*%klm@DZ77mqAc5!6%?? z9MyP2HKfp84ej8y%o#Rn%|A-CH$l_jSE3>X)eQ7I@l7#=2=zL~?Q_zyY-NH*)p__8 zXl=T?l&$Mk;T~zeH{2`IHP5}e<7FBv*>4~b*qc<B{ccaJaRNP*m`r{=xIgy0e#Mfz znai^GAwkiP(X-znT~3)4mUxaN{JQw$ntbbJq55Sp;If=^rEuOcs!*};W*@aBS#rMI zi~ULj$n##^A(ZLO?`R+v>o{T4Fe{QmTwndm8vgt**DfC7CYj^x4(3e#4BnUZyCm>k zsypku(lIZ7|KRtdLkDg0(`D|@fP#}ehZPFpUFrPB%_3QBQU4Pv^DH7{W{U;8ceoPy zV~^<DPbr@|g=u_5tl<me7nh&dW&}&1)x0H_OlKJDn!EEN*vqPwm{%y>F5{ZZp<93x z9h#!%4@<eV3L^!RV!=E?!8jl8(HIaTYgE+*>8_||RJ`FEIb~EFW}a)A)E--&5iii? z%}-rwtJHPYM=>hb??##Q1)hIGlDOZ+-FDeHJ%>og3OCN~H?Z<jok1oQM%V`y$`+NK z0JUwNql3t8n1;A}Etg6=(%_gc$uSr9{245pgwtp3qG8>~H=Cn>dYeGTf&^G!HJ;<K z0XvqxJYsVJ_UkR62AuGPkXp(DXV;$Gm=J-{@oAvrmB8uBt;7^a@Ya;#TVT3ME1<Pn zr|JtsEWL$Gy$vTo?rEAeD@1+F)BNU04VO(2a!WH@7R<7i`K%lx>=j{ObHef}gi_Ld zJJ5hmjNqRtez^0*hgfd>{R0Zxyw&rJ0*4)#u8s9yzg-C?d25;-n4+(`D1;F<D`NEz z?bPE90~O<6ftauoX!IObt%jpcKdwVY$|#yGI+e89=+0s2%$n0EJTO3T;?5SWV9Va3 zhsUD%q~}IpeerR2=l35_L&#?llrU;AKE(cJl9<6TkZjPkqRA6>Q>!(sUC3!(_REC? zbP^_^zyPg9hK;2vAV8PR6|A__<*1qLq6$Eq8l4S6miweXq5?a-nHN^HdIY!f_-o@u zp>Y<5g14Q{Vq)T-cj+<(iSIn49(9+qkL2C3?9iuc1&4aE89IqL*f&6a^^zfQ!1XvI zfXQM>34_t9t82$vL;XRil9PbsK+TGPzDy#&S3cjbOdEm~NI6t9>84uAq4u_*#>l9q z>VI>bQwUr-2dEYXydv#&S)X**ktfYGV57CIm05Omhc}Jl(!cnjYr1cFV7GftkGncB z&Hn2ZS{d3RwD9IFW43<+gepDlSxb;sKMd4%92<=IMHrjqXOhMtmgBT~)AzY1_Q_Nj zw@j(JDHekRvv=jqG7SP@l9|N~)7YfFU*pUw<#ReCAH21<$J61cB~wM-4wnZuf?!x8 z&@&FDqPxuKW1#{Qs|nwITE(P<^g=KYP1JZt=8t1#dyQx~P)ChKLSV$ir527yem+}C z&!-)ct4_`<5j}3Z5e_5){UC0`%OIs5&V!TEOyxa5zGJiDegY_wdbk620d=Q*!#?^i z2(l5VjooD9Z%&w*U%NHIDy}RGVS6`mlYp4y-LVW1;yhH5ADCa|jvjb^77b)wd5-wz zEa)Y94>QRui~kZH!G|4I!~88=%0&5G0eO<-nmHrap#K1XR^grjSe|Z|icAjz75nrP zA<ai%)#<>CVIcUvi7-|NNp!+-;Hwr2EQhS0&}q%-04`%he-ML<FonQ>Z%u)DE3(ue zxb}WfOasY<Uc!fO0xFg%I7hahP!iE!a7L*B$Z==kv^Nq^EK_WbP5}~HWU=EQ2<-%Z z#=oKhQVEb=u~}SxLZf^MlHC0U348Sje+4+PO)-gH0_5EMh8d-BIZ_2k9+88IdX6nu ze*O>Lv|TI5YXcSpqy`fNgeG}+nlPF93JI91>1Bv<g!v=il)a9SY{*H00@G!jV&*{_ zrHq1d1Y^db*$m5Wn61E>Y--xvJTv2LSv#U(gM20pcy6m*!qT-REi98kj;igw`RKd( zC~Lj(W4oNOhm!qSdy9MN+v(nUxk~==dUOJzzjMH4O1xV@F(@m5V@h|b4<bRLNdVr$ zZF`l*XolWe<JK!XVjEN!U_oO}4S?d-t1@FlTSq<!eD~xDhQIE9eR_eb?%)h&d-}57 z^n_YCAnw`m3H0Z;hu<O*?TjBnd%1Imc-+0B0JjMTeLVRF^TS0wnc{Cuwu||E#6~|v zUqPx}W4Pk!wL)n%yt6E*J)Z|qh=69n<{5jUtV9P+VdKzde07G13aoEKaPE8L2=B^} z#6z@@#A8;RudtB${nVnKDk77|rq%_5olgI+t22*>a{J?WriGBkzCCt>v1AD;OO~ud zS+hiL*0B>p#vMeuS<-!EH+B=*GRP8IgoH@h#@K0WF;|rG%kOEr_vJO6f6jBx^PclP zbLRXpXXg8SK7qpH#M2sM(~zwCG;wtNyn?vMWGJEWiqBj0IAtfzk9VBXz_y~AHU6~9 zecjKYtN>+acdRx@uVVO?`NcJ&LhT1VM{@&HtRG3?=|2^Z60B~K*p@boc23}r-TbaD z!>XBP(u5m`S#SH_8J3gct?H5V^cvy_&#begx)Yl6h2xK*oRO@Z_Bk#4%g%EXE^a;b zkdlQ0F~ST`@j9*Ukp#&{yF1LU&!?+q4-voEIiw6U1cY^&#p3_)YP{yLY(Agqbw4*} z8(ZHtUQ70I_%0rD;mz}WmdC+0xKo3QFeYCmLt{d-lfmT;q-hFyBwF=F%k9>_`t<QK z`(rgEvqs_r)DIC&$7k(~daIU==~Ex77AJ#MR+B2P)nwGx>!PruazqK8B3CmUW_dDa zB)FO$wiBn55}<ybJqP;3Y*;pma37z`k3}l<6u28T3AgmIrhZu8dHzG3UOa%SG6ZRj zxNB)GEI)7MR#fU!40{LED}@NVdKX9G>KS<LdxM;EWG8eCBni#2>%KJ)C|<nz_bK+* z_nZ79KZudgc-c+1xuJbYl$%p{wCOl6>1^w#z0|)Q6S9)z{ffONO7hcJN5)R|W9vdu zoyY?Fc{jh}d(4(E0)-LvT6x;Xw+t|wZ!NgmE6k&T#;PUpagBt@kH>C#&)1QC7t?o_ zAGL6{))=~`ebD+i!0lx%G|ZSqFsmA;M>fkEdtL1C89?>1IG+_kb(Cs5{gGC1!-(ON zM}(4=p|PQTfWwU^_usPnyyi7ADZw^bJ=~J+bw8SzTDySd=E@>hxg8&3{L`~}(y3Z% zTbEOv62Z1^`_1$_4C`-6(Z~G7_vh=SAG#x|65B2UCPq!?^i5{&D_Tm_eSWw1uIHig zn@TUk&u!KYG7rm4?ApX8yR0$1&ey!0O9w)5rKNLOWZR)+LC!X^mE!XjZypOQMFo== zmvnO_yf}T-26K4YI!MOfmLivK-8F<CXP0j82d1yNfEk(DgXCi<P6->#=<~6fxyZh< zDenbKj-#aen^9$u0nf~#{nX><U34`Y>NLw5e4-uETs@zK<|UKD6Yl2Ed0Icys!G>* z`dZe_AfCIqLx1P1+N6?X{7YMGtt7VEB{zz~#I=XoGkH}LvBRHap207-`iz$gn{&4{ zh&b+cohV1@otped*^G;Fg|p-3hRt5gX+$C`FV>nOxo6+yY`w>cwW2^NMP27@_Lw}y zeaVVqMbe^?%#osXsOgU-hFW-hvZ9_)GLOA;>wpBC`+#W8jq)h_D@5#SkY(|uF!^Be zvpDxpLH;k;0&3`IV|#nk1OM7EvmXh2`2Dis?iDd54f*u<N4&`{xc9#MU32Th7dgM4 zsg>w}jI5THWNIpIqj#NNJ0^2-^Wl*XFz;=xU8n9fv&FLCRIMSj7Q{ZWQ@hZc50(s; z3m6Qr;uqSO66T^?IXs83+G)5t6Sk}PG{2s=Wk-sPcMR5+`7w%`ajV|Oy3(43TSu+C zM<HwUy2fGGMEUf0>~-Zmxa(}^%;=3m237SDD%R~xy8}xO5~CNQrV)Ltrk<uZI$s1& zPb_3AQ;2g;i0kVZAxVPo=4#uYPc)VvJc$qWYY)h_zujB38QHCd%>&z;N6jZt9)3}| z@p0saOnkL#elg?UO_@Ig`wP$CW^}0K&8wf#eIy++_>C90jd2LruH+s%w`}ihw92os zil}cNBDANCIN?G$uC+&?1()6!CWQzL*<HeJSn>!D=s5W4p6HKG=QYwh{gCf&{3AST zrcNN5Ph~ju9%GXq_H!sthKqWX%||#6QQ)I!eFR95MgKL%q5H-4IkR`d3zHeeKHiFy z(u>-81|;aIADIjbIk)%244uctVlG#1_LwwztihjJ%A5%KqOMyC2rvu|l#eN|91lN5 z=Nt%}c-$Ej=SrDJCxNO7n}28o!M0qw?<gggA(a!o!nICXVeJ%>(~+_vJ6vZYt6Tye z6T%7!VXP5SO7V$#{fL1jMC{}K@z(d_t)^>op*uwbQ*~aco^uJ0YYm$`n&-3CT0M4^ zFXv+7eDBVP03x6O-dE>vRE;nbk$iI7r0?Z}g>Ni#E!lJJj2W&fiz6x=Nh+D04r|@# zfX;@vAkD%`Z1>BilpnVOI0lkfdtaiv2ozv;#fqmZm`>4^9_7-NWrc7gB~{=VO0r|6 zi%rTpc9bR18A3{*7gMjq+3UOVpKWMM)QH+;&%Km}>K;^!mqB|X7T<GJYw*d(pOY0G zF}`y}s`Co6H~`M4s%}?(RmU+-*}zzSMB>OYb9#>(mT>XWq4gBjFX0woPN(1n^o!XP zq~rFHG`l8OKHGr&=M^G~PMXO+(x<C4tjT&}oJPp`Q#Wh^Pgf6#S<ODlmD$n~twSVw z)_UC0sBKioWxhD~%_)+{<mA0K`sO~ym#r*5#i!L3*vrLN;oRboc{ylDBUhOif*@-l z(dEom1hy;YT1kXLAmPjfGe9)uUXl|~WNKzXGX6F+-(B_gfpT4_h@`-%?Z}rq-G@a+ z53`vrOm3}z+7-^P(eMptp-)T*C}a-8JP^4R3w)0g`(KMn%p!%WH)&M}DSDPJOI*kk zxnUMFHk&KG<joT?rfb4;bd8Iby-RSdl)zZtb`J}1Nu3kc3AuT`<yd7fK?RcXpp#xO z;g`L{b^9)xUO0|iodoI9)yJn+2$aMLz4N~W_Y|Y7?J7TF%M5GEJi~ToYxLDzm1J!U zU(a!04NE%ABl@Ol21nZ?)nYT;P0zkN&CsJcIrL3RbvPqFWNV{ll)W8bhU|D&Q@{zs z(BS`A&8F9)+*$x-WDL*NWgI~CDSQ$ieF|p^<aS>sUFhg$FK8?}<)`m7;V2eyLo#pS zkX&aXT3)!$R%e?x&V7=z5>efncx|Ql+l*CJ5z3#j#p$}#Gqc4tP0QJgNX<eYxs`V- zE26WnV)t?TFi?9EAe@=trDMEJ9u1R(?8L8fgAPPAEQtw}WkP_<L}}O_6PU{+1Ci1& zeP+;iQXLk;4Cb@&f^y7!;3U+$&jK|yp-VVuL*k>W1p`S}VFsL_g(d*5k<P-(xTu#O z7XZ+CiVwC1Kus~Q2A~JOF2VW9U%fg|(DAJU`YNgp+2;ILN@7OA!KsVDPh;~UDC0^3 z65&7n-%Y44iHQ^eho+=JmuUfj#!Z?A@)xH@YoC?@s7|ubNE?vzg8>cnN{R|e&8PrW zKTs&SOM>;#Ax#=6M1~6G&d35Z&T2GJkrEZ6pOpa)9IJjGsXzsSkdS{BB;hy<Kl0N! z2rQiCf|aUK`~B45pxF!@_~{dbr#p107+M`QnkWtGR4_`N_2&Qp)jkM%V~t0DU!poq z2vh-^=itDBPs{Jqsnmubc@U%hD=dvc22I#pJqQ#7o%Mc&rLjk#0V(=aaCrU{aA1!B zttky8V+=u}kXeEP-2fQ4U<D((f{L!Ez)Mqb(9i=8`GWr{B{BP;;G!pN7!4xP>eOv! zKFJJDEwaGMyunY48gwI|%#ti{pmX<oq?10NtT#Ux?R^5wS(1hg`BU@u+5<Si%_Sw+ zXyCt1KJYsU4jv7KGarQ3xdBOWKqsS6hM$`iplWqN+h+{gj5_~OP+bxu9!3S%KdJ&$ zB_8NA`xAncX`qHkD&$B;0<?M`2UJN+ITnJ<V6XnBvL}|cVGSU4hRgSbL49b3Y0G@D zr`go5%}hRUdRZD4okwj7uLuD&4@(@33r*1*M}-0*h&GM!fGUZh$5WwaE7C`QWtwfQ zaKie_|9vGrD7vZun{TH!S=yMv&{b*Jz2{)zsv9i$B~*!m$TbC6YcB*Sfyrxd;NaJp qp9zx(r6lIfTX42t9we^90h*)e0Rzohb{K*H=wvE*%#8H&&i?^nK}2Ez delta 35766 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%0DG<S^qW!A-@`soL|Nd&|btslc<LhQ&mX>K?7GHTj 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 VwlMN}`%er%aGR6olj~j${vQ;P=LY}) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f78e0289036..c30b486a892 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip -distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb6c20..79a61d421cc 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac diff --git a/gradlew.bat b/gradlew.bat index f127cfd49d4..93e3f59f135 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% From 965df22efe49033c6dd373539e1927bc57c5cac6 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 1 Sep 2023 12:40:22 +0200 Subject: [PATCH 208/447] Optimize `Space#withWhitespace()` for formatting Return `SINGLE_SPACE` as appropriate. --- rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index bc45bce6205..bace53ca9f0 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -115,6 +115,8 @@ public Space withComments(List<Comment> comments) { public Space withWhitespace(String whitespace) { if (comments.isEmpty() && whitespace.isEmpty()) { return Space.EMPTY; + } else if (comments.isEmpty() && " ".equals(whitespace)) { + return SINGLE_SPACE; } if ((whitespace.isEmpty() && this.whitespace == null) || whitespace.equals(this.whitespace)) { return this; From b18ad1efece1cb10cc4926535124a23f8c3c1162 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sat, 2 Sep 2023 08:13:00 -0400 Subject: [PATCH 209/447] Polish --- .../openrewrite/marker/GitProvenanceTest.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java b/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java index 38054132c8e..d14f7eda6ab 100644 --- a/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java @@ -46,7 +46,7 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.openrewrite.Tree.randomId; -@SuppressWarnings("ConstantConditions") +@SuppressWarnings({"ConstantConditions", "HttpUrlsUsage"}) class GitProvenanceTest { private static Stream<String> remotes() { @@ -61,6 +61,7 @@ private static Stream<String> remotes() { ); } + @SuppressWarnings("deprecation") @ParameterizedTest @MethodSource("remotes") void getOrganizationName(String remote) { @@ -77,7 +78,7 @@ void getRepositoryName(String remote) { @Test void localBranchPresent(@TempDir Path projectDir) throws GitAPIException { - try (Git g = Git.init().setDirectory(projectDir.toFile()).setInitialBranch("main").call()) { + try (Git ignored = Git.init().setDirectory(projectDir.toFile()).setInitialBranch("main").call()) { GitProvenance git = GitProvenance.fromProjectDirectory(projectDir, null); assertThat(git).isNotNull(); assertThat(git.getBranch()).isEqualTo("main"); @@ -85,7 +86,7 @@ void localBranchPresent(@TempDir Path projectDir) throws GitAPIException { } @Test - void nonGitNoStacktrace(@TempDir Path projectDir) throws GitAPIException { + void nonGitNoStacktrace(@TempDir Path projectDir) { PrintStream standardErr = System.err; ByteArrayOutputStream captor = new ByteArrayOutputStream(); try { @@ -270,8 +271,8 @@ void supportsGitHubActions(@TempDir Path projectDir) { envVars.put("GITHUB_HEAD_REF", ""); GitProvenance prov = GitProvenance.fromProjectDirectory(projectDir, - GithubActionsBuildEnvironment.build(var -> envVars.get(var))); - assertThat(prov != null); + GithubActionsBuildEnvironment.build(envVars::get)); + assertThat(prov).isNotNull(); assertThat(prov.getOrigin()).isEqualTo("https://github.com/octocat/Hello-World.git"); assertThat(prov.getBranch()).isEqualTo("main"); assertThat(prov.getChange()).isEqualTo("287364287357"); @@ -285,10 +286,10 @@ void ignoresBuildEnvironmentIfThereIsGitConfig(@TempDir Path projectDir) throws envVars.put("GITHUB_REF", "refs/heads/foo"); envVars.put("GITHUB_SHA", "287364287357"); envVars.put("GITHUB_HEAD_REF", ""); - try (Git g = Git.init().setDirectory(projectDir.toFile()).setInitialBranch("main").call()) { + try (Git ignored = Git.init().setDirectory(projectDir.toFile()).setInitialBranch("main").call()) { GitProvenance prov = GitProvenance.fromProjectDirectory(projectDir, - GithubActionsBuildEnvironment.build(var -> envVars.get(var))); - assertThat(prov != null); + GithubActionsBuildEnvironment.build(envVars::get)); + assertThat(prov).isNotNull(); assertThat(prov.getOrigin()).isNotEqualTo("https://github.com/octocat/Hello-World.git"); assertThat(prov.getBranch()).isEqualTo("main"); assertThat(prov.getChange()).isNotEqualTo("287364287357"); @@ -303,9 +304,9 @@ void supportsCustomBuildEnvironment(@TempDir Path projectDir) { envVars.put("CUSTOM_GIT_SHA", "287364287357"); GitProvenance prov = GitProvenance.fromProjectDirectory(projectDir, - CustomBuildEnvironment.build(var -> envVars.get(var))); + CustomBuildEnvironment.build(envVars::get)); - assertThat(prov != null); + assertThat(prov).isNotNull(); assertThat(prov.getOrigin()).isEqualTo("https://github.com/octocat/Hello-World.git"); assertThat(prov.getBranch()).isEqualTo("main"); assertThat(prov.getChange()).isEqualTo("287364287357"); @@ -319,9 +320,9 @@ void supportsGitLab(@TempDir Path projectDir) { envVars.put("CI_COMMIT_SHA", "287364287357"); GitProvenance prov = GitProvenance.fromProjectDirectory(projectDir, - GitlabBuildEnvironment.build(var -> envVars.get(var))); + GitlabBuildEnvironment.build(envVars::get)); - assertThat(prov != null); + assertThat(prov).isNotNull(); assertThat(prov.getOrigin()).isEqualTo("https://github.com/octocat/Hello-World.git"); assertThat(prov.getBranch()).isEqualTo("main"); assertThat(prov.getChange()).isEqualTo("287364287357"); @@ -336,9 +337,9 @@ void supportsDrone(@TempDir Path projectDir) { envVars.put("DRONE_COMMIT_SHA", "287364287357"); GitProvenance prov = GitProvenance.fromProjectDirectory(projectDir, - DroneBuildEnvironment.build(var -> envVars.get(var))); + DroneBuildEnvironment.build(envVars::get)); - assertThat(prov != null); + assertThat(prov).isNotNull(); assertThat(prov.getOrigin()).isEqualTo("https://github.com/octocat/Hello-World.git"); assertThat(prov.getBranch()).isEqualTo("main"); assertThat(prov.getChange()).isEqualTo("287364287357"); @@ -346,9 +347,8 @@ void supportsDrone(@TempDir Path projectDir) { @Test void supportsTravis(@TempDir Path projectDir) throws Exception { - try (Git g = Git.init().setDirectory(projectDir.toFile()).setInitialBranch("main").call()) { - Map<String, String> envVars = new HashMap<>(); - TravisBuildEnvironment buildEnvironment = TravisBuildEnvironment.build(var -> envVars.get(var)); + try (Git ignored = Git.init().setDirectory(projectDir.toFile()).setInitialBranch("main").call()) { + TravisBuildEnvironment buildEnvironment = TravisBuildEnvironment.build(s -> null); GitProvenance git = GitProvenance.fromProjectDirectory(projectDir, buildEnvironment); assertThat(git).isNotNull(); assertThat(git.getBranch()).isEqualTo("main"); From 5eda3a6535bced2cb4b3917f2163515a85d425e5 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sat, 2 Sep 2023 18:22:25 -0400 Subject: [PATCH 210/447] Added jsch to shaded jgit types --- rewrite-core/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-core/build.gradle.kts b/rewrite-core/build.gradle.kts index 1616491549a..13248eff782 100644 --- a/rewrite-core/build.gradle.kts +++ b/rewrite-core/build.gradle.kts @@ -7,6 +7,7 @@ plugins { dependencies { compileOnly("org.eclipse.jgit:org.eclipse.jgit:5.13.+") + compileOnly("org.eclipse.jgit:org.eclipse.jgit.ssh.jsch:5.13.+") implementation("org.openrewrite.tools:java-object-diff:latest.release") From fb0a7854399052b9758487a510ec73745997e2d4 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sat, 2 Sep 2023 19:41:06 -0400 Subject: [PATCH 211/447] SSHD --- rewrite-core/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-core/build.gradle.kts b/rewrite-core/build.gradle.kts index 13248eff782..f57a14cd05d 100644 --- a/rewrite-core/build.gradle.kts +++ b/rewrite-core/build.gradle.kts @@ -8,6 +8,7 @@ plugins { dependencies { compileOnly("org.eclipse.jgit:org.eclipse.jgit:5.13.+") compileOnly("org.eclipse.jgit:org.eclipse.jgit.ssh.jsch:5.13.+") + compileOnly("org.eclipse.jgit:org.eclipse.jgit.ssh.apache.agent:latest.release") implementation("org.openrewrite.tools:java-object-diff:latest.release") From 37a22968c5ff273883fb023310220beac6f65cf3 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sun, 3 Sep 2023 18:42:47 -0400 Subject: [PATCH 212/447] Remove JGit SSHD and JSC --- rewrite-core/build.gradle.kts | 2 -- 1 file changed, 2 deletions(-) diff --git a/rewrite-core/build.gradle.kts b/rewrite-core/build.gradle.kts index f57a14cd05d..1616491549a 100644 --- a/rewrite-core/build.gradle.kts +++ b/rewrite-core/build.gradle.kts @@ -7,8 +7,6 @@ plugins { dependencies { compileOnly("org.eclipse.jgit:org.eclipse.jgit:5.13.+") - compileOnly("org.eclipse.jgit:org.eclipse.jgit.ssh.jsch:5.13.+") - compileOnly("org.eclipse.jgit:org.eclipse.jgit.ssh.apache.agent:latest.release") implementation("org.openrewrite.tools:java-object-diff:latest.release") From 807dd69000efc8a5d8042a064623244ebe112809 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 6 Sep 2023 21:34:32 +0200 Subject: [PATCH 213/447] Add `Sealed` and `NonSealed` flags Kotlin also has sealed types. --- .../src/main/java/org/openrewrite/java/tree/Flag.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Flag.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Flag.java index 7a65b97dbbe..8a177eccb01 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Flag.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Flag.java @@ -42,7 +42,9 @@ public enum Flag { Union(1L << 39), Default(1L << 43), SignaturePolymorphic(1L << 46), - PotentiallyAmbiguous(1L << 48); + PotentiallyAmbiguous(1L << 48), + Sealed(1L << 62), + NonSealed(1L << 63); private final long bitMask; From fbafdf077ef356b202f508609081f774ad60c2fa Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 6 Sep 2023 20:38:10 -0700 Subject: [PATCH 214/447] Groovy parser support for "var" variable declarations --- .../org/openrewrite/groovy/GroovyParserVisitor.java | 13 ++++++++----- .../groovy/tree/VariableDeclarationsTest.java | 8 ++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index 71ce2bb1879..b3124129f08 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -257,7 +257,7 @@ public void visitClass(ClassNode clazz) { .withAfter(i == interfaces.length - 1 ? EMPTY : sourceBefore(","))); } // Can be empty for an annotation @interface which only implements Annotation - if (implTypes.size() > 0) { + if (!implTypes.isEmpty()) { implementings = JContainer.build(implPrefix, implTypes, Markers.EMPTY); } } @@ -621,7 +621,7 @@ public void visitArgumentlistExpression(ArgumentListExpression expression) { .sorted(Comparator.comparing(ASTNode::getLastLineNumber) .thenComparing(ASTNode::getLastColumnNumber)) .collect(Collectors.toList()); - } else if (unparsedArgs.size() > 0 && unparsedArgs.get(0) instanceof MapExpression) { + } else if (!unparsedArgs.isEmpty() && unparsedArgs.get(0) instanceof MapExpression) { // The map literal may or may not be wrapped in "[]" // If it is wrapped in "[]" then this isn't a named arguments situation and we should not lift the parameters out of the enclosing MapExpression saveCursor = cursor; @@ -1249,7 +1249,7 @@ public void visitExpressionStatement(ExpressionStatement statement) { } Statement condenseLabels(List<J.Label> labels, Statement s) { - if (labels.size() == 0) { + if (labels.isEmpty()) { return s; } return labels.get(0).withStatement(condenseLabels(labels.subList(1, labels.size()), s)); @@ -1859,11 +1859,14 @@ public TypeTree visitVariableExpressionType(VariableExpression expression) { JavaType type = typeMapping.type(staticType(((org.codehaus.groovy.ast.expr.Expression) expression))); if (expression.isDynamicTyped()) { + Space prefix = whitespace(); + String defOrVar = source.substring(cursor, cursor + 3); + cursor += 3; return new J.Identifier(randomId(), - sourceBefore("def"), + prefix, Markers.EMPTY, emptyList(), - "def", + defOrVar, type, null); } Space prefix = sourceBefore(expression.getOriginType().getUnresolvedName()); diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/VariableDeclarationsTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/VariableDeclarationsTest.java index 7f686713639..7a74d433d2c 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/VariableDeclarationsTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/VariableDeclarationsTest.java @@ -35,6 +35,14 @@ @SuppressWarnings("GroovyUnusedAssignment") class VariableDeclarationsTest implements RewriteTest { + @Test + void varKeyword() { + rewriteRun( + groovy("var a = 1") + ); + } + + @Test void singleVariableDeclaration() { rewriteRun( From 66d2f7220b70974d135b1a9f86e12362f0c2c24d Mon Sep 17 00:00:00 2001 From: Nick McKinney <mckinneynicholas@gmail.com> Date: Thu, 7 Sep 2023 13:22:26 -0400 Subject: [PATCH 215/447] Recipe validation / RecipeIntrospectionUtils changes for String fields (#3524) * Revert "String fields are null by default. (#3404)" This reverts commit 78b64cc0d6f1020b07e585ca492e80035e2b1cc8. * default Recipe::validate now checks for `String` fields to be `notBlank`, not just `required` * reverting that change to default Recipe::validate (no longer checking notBlank for strings), and added an `isKotlin` check for String fields in RecipeIntrospectionUtils::construct --- .../internal/RecipeIntrospectionUtils.java | 14 ++++++++++++++ .../java/org/openrewrite/java/ChangePackage.java | 7 +++++++ .../org/openrewrite/properties/AddProperty.java | 7 +++++++ 3 files changed, 28 insertions(+) diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java index 531b86295ea..a3cc7e156d3 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/RecipeIntrospectionUtils.java @@ -112,6 +112,11 @@ private static <V> V construct(Class<?> clazz) { java.lang.reflect.Parameter param = primaryConstructor.getParameters()[i]; if (param.getType().isPrimitive()) { constructorArgs[i] = getPrimitiveDefault(param.getType()); + } else if (param.getType().equals(String.class) && isKotlin(clazz)) { + // Default Recipe::validate is more valuable if we pass null for unconfigured Strings. + // But, that's not safe for Kotlin non-null types, so use an empty String for those + // (though it will sneak through default recipe validation) + constructorArgs[i] = ""; } else if (Enum.class.isAssignableFrom(param.getType())) { try { Object[] values = (Object[]) param.getType().getMethod("values").invoke(null); @@ -135,6 +140,15 @@ private static <V> V construct(Class<?> clazz) { } } + private static boolean isKotlin(Class<?> clazz) { + for (Annotation a : clazz.getAnnotations()) { + if (a.annotationType().getName().equals("kotlin.Metadata")) { + return true; + } + } + return false; + } + @NonNull private static RecipeIntrospectionException getRecipeIntrospectionException(Class<?> recipeClass, ReflectiveOperationException e) { return new RecipeIntrospectionException("Unable to call primary constructor for Recipe " + recipeClass, e); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java index 642c3c3470f..6a8c92640ef 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangePackage.java @@ -69,6 +69,13 @@ public String getDescription() { return "A recipe that will rename a package name in package statements, imports, and fully-qualified types."; } + @Override + public Validated<Object> validate() { + return Validated.none() + .and(Validated.notBlank("oldPackageName", oldPackageName)) + .and(Validated.required("newPackageName", newPackageName)); + } + @Override public TreeVisitor<?, ExecutionContext> getVisitor() { JavaIsoVisitor<ExecutionContext> condition = new JavaIsoVisitor<ExecutionContext>() { diff --git a/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java b/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java index fb1921fecbd..b4bebb377dd 100644 --- a/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java +++ b/rewrite-properties/src/main/java/org/openrewrite/properties/AddProperty.java @@ -58,6 +58,13 @@ public String getDescription() { return "Adds a new property to a property file at the bottom of the file if it's missing. Whitespace before and after the `=` must be included in the property and value."; } + @Override + public Validated<Object> validate() { + return Validated.none() + .and(Validated.required("property", property)) + .and(Validated.required("value", value)); + } + @Override public PropertiesIsoVisitor<ExecutionContext> getVisitor() { return new PropertiesIsoVisitor<ExecutionContext>() { From efcc2a276b798223f079ba1f89f3700f5e570491 Mon Sep 17 00:00:00 2001 From: tclayton-newr <87031785+tclayton-newr@users.noreply.github.com> Date: Thu, 7 Sep 2023 16:31:38 -0400 Subject: [PATCH 216/447] Issue 3275/fix remove used param v2 (#3527) * update list to check for fully qualified paths * add option to unfold wildcard imports --- .../java/RemoveUnusedImportsTest.java | 143 +++++++++++++++++- .../org/openrewrite/java/OrderImports.java | 2 +- .../openrewrite/java/RemoveUnusedImports.java | 37 ++++- .../org/openrewrite/java/tree/TypeUtils.java | 6 +- 4 files changed, 179 insertions(+), 9 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java index 04b0718d527..de8a04b8947 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java @@ -31,7 +31,7 @@ class RemoveUnusedImportsTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new RemoveUnusedImports()); + spec.recipe(new RemoveUnusedImports(true)); } @Test @@ -1138,6 +1138,147 @@ void f(A classA) { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/3275") + @Test + void doesNotRemoveWildCardImport() { + rewriteRun( + java( + """ + package com.Source.mine; + + public class A { + public void f() {} + } + """ + ), + java( + """ + package com.Source.mine; + + public class B { + public void f() {} + } + """ + ), + java( + """ + package com.Source.mine; + + public class C { + public void f() {} + } + """ + ), + java( + """ + package com.Source.mine; + + public class Record { + public A theOne() { return new A(); } + public B theOther1() { return new B(); } + public C theOther2() { return new C(); } + } + """ + ), + java( + """ + package com.test; + + import com.Source.mine.Record; + import com.Source.mine.*; + + class Test { + void f(Record r) { + A a = r.theOne(); + B b = r.theOther1(); + C c = r.theOther2(); + } + } + """, + """ + package com.test; + + import com.Source.mine.Record; + import com.Source.mine.A; + import com.Source.mine.B; + import com.Source.mine.C; + + class Test { + void f(Record r) { + A a = r.theOne(); + B b = r.theOther1(); + C c = r.theOther2(); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/3275") + @Test + void doesNotUnfoldWildCardImport() { + rewriteRun( + spec -> spec.recipe(new RemoveUnusedImports(false)), + java( + """ + package com.Source.mine; + + public class A { + public void f() {} + } + """ + ), + java( + """ + package com.Source.mine; + + public class B { + public void f() {} + } + """ + ), + java( + """ + package com.Source.mine; + + public class C { + public void f() {} + } + """ + ), + java( + """ + package com.Source.mine; + + public class Record { + public A theOne() { return new A(); } + public B theOther1() { return new B(); } + public C theOther2() { return new C(); } + } + """ + ), + java( + """ + package com.test; + + import com.Source.mine.Record; + import com.Source.mine.A; + import com.Source.mine.B; + import com.Source.mine.C; + + class Test { + void f(Record r) { + A a = r.theOne(); + B b = r.theOther1(); + C c = r.theOther2(); + } + } + """ + ) + ); + } + @Test void removeImportUsedAsLambdaParameter() { rewriteRun( diff --git a/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java b/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java index 3937062d307..7b1c5f5de55 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java @@ -96,7 +96,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon } if (Boolean.TRUE.equals(removeUnused)) { - doAfterVisit(new RemoveUnusedImports().getVisitor()); + doAfterVisit(new RemoveUnusedImports(true).getVisitor()); } else if (changed) { doAfterVisit(new FormatFirstClassPrefix<>()); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java index ed3b9ab71af..446652158cb 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java @@ -15,10 +15,14 @@ */ package org.openrewrite.java; +import lombok.EqualsAndHashCode; +import lombok.Value; import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.style.ImportLayoutStyle; import org.openrewrite.java.style.IntelliJ; import org.openrewrite.java.tree.*; @@ -26,17 +30,29 @@ import java.time.Duration; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static java.util.Collections.emptySet; import static org.openrewrite.java.style.ImportLayoutStyle.isPackageAlwaysFolded; import static org.openrewrite.java.tree.TypeUtils.fullyQualifiedNamesAreEqualAsPredicate; +import static org.openrewrite.java.tree.TypeUtils.getFullyQualifiedClassPath; /** * This recipe will remove any imports for types that are not referenced within the compilation unit. This recipe * is aware of the import layout style and will correctly handle unfolding of wildcard imports if the import counts * drop below the configured values. */ +@Value +@EqualsAndHashCode(callSuper = true) public class RemoveUnusedImports extends Recipe { + + @Option(displayName = "Unfold wildcard imports", + description = "Expand wildcard imports into individual imports", + required = false) + @Nullable + Boolean unfoldWildcard; + @Override public String getDisplayName() { return "Remove unused imports"; @@ -61,11 +77,17 @@ public Duration getEstimatedEffortPerOccurrence() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - return Preconditions.check(new NoMissingTypes(), new RemoveUnusedImportsVisitor()); + return Preconditions.check(new NoMissingTypes(), new RemoveUnusedImportsVisitor(Boolean.TRUE.equals(unfoldWildcard))); } private static class RemoveUnusedImportsVisitor extends JavaIsoVisitor<ExecutionContext> { + private boolean unfoldWildcard; + + public RemoveUnusedImportsVisitor(final boolean unfoldWildcard) { + this.unfoldWildcard = unfoldWildcard; + } + @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { ImportLayoutStyle layoutStyle = Optional.ofNullable(cu.getStyle(ImportLayoutStyle.class)) @@ -199,21 +221,24 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon changed = true; } } else { - Set<JavaType.FullyQualified> types = typesByPackage.get(elem.getPackageName()); + Set<JavaType.FullyQualified> types = typesByPackage.getOrDefault(elem.getPackageName(), new HashSet<>()); + Set<JavaType.FullyQualified> typesByFullyQualifiedClassPath = typesByPackage.getOrDefault(getFullyQualifiedClassPath(elem.getPackageName()), new HashSet<>()); + Set<JavaType.FullyQualified> combinedTypes = Stream.concat(types.stream(), typesByFullyQualifiedClassPath.stream()) + .collect(Collectors.toSet()); JavaType.FullyQualified qualidType = TypeUtils.asFullyQualified(elem.getQualid().getType()); - if (types == null || sourcePackage.equals(elem.getPackageName()) && qualidType != null && !qualidType.getFullyQualifiedName().contains("$")) { + if (combinedTypes.isEmpty() || sourcePackage.equals(elem.getPackageName()) && qualidType != null && !qualidType.getFullyQualifiedName().contains("$")) { anImport.used = false; changed = true; } else if ("*".equals(elem.getQualid().getSimpleName())) { if (isPackageAlwaysFolded(layoutStyle.getPackagesToFold(), elem)) { anImport.used = true; usedWildcardImports.add(elem.getPackageName()); - } else if (types.size() < layoutStyle.getClassCountToUseStarImport()) { + } else if (combinedTypes.size() < layoutStyle.getClassCountToUseStarImport()) { // replacing the star with a series of unfolded imports anImport.imports.clear(); // add each unfolded import - types.stream().map(JavaType.FullyQualified::getClassName).sorted().distinct().forEach(type -> + combinedTypes.stream().map(JavaType.FullyQualified::getClassName).sorted().distinct().forEach(type -> anImport.imports.add(new JRightPadded<>(elem .withQualid(qualid.withName(name.withSimpleName(type))) .withPrefix(Space.format("\n")), Space.EMPTY, Markers.EMPTY)) @@ -227,7 +252,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon } else { usedWildcardImports.add(elem.getPackageName()); } - } else if (types.stream().noneMatch(c -> { + } else if (combinedTypes.stream().noneMatch(c -> { if ("*".equals(elem.getQualid().getSimpleName())) { return elem.getPackageName().equals(c.getPackageName()); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 51e733fc122..edf47ee522f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -38,10 +38,14 @@ public static boolean isString(@Nullable JavaType type) { ); } + public static String getFullyQualifiedClassPath(String fqn) { + return fqn.replace('$', '.'); + } + public static boolean fullyQualifiedNamesAreEqual(@Nullable String fqn1, @Nullable String fqn2) { if (fqn1 != null && fqn2 != null) { return fqn1.equals(fqn2) || fqn1.length() == fqn2.length() - && fqn1.replace('$', '.').equals(fqn2.replace('$', '.')); + && getFullyQualifiedClassPath(fqn1).equals(getFullyQualifiedClassPath(fqn2)); } return fqn1 == null && fqn2 == null; } From c0bb28b8a4a843ce4ad8d51c48496f76cfbbb077 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 7 Sep 2023 22:45:31 +0200 Subject: [PATCH 217/447] Polish `TypeUtils` API To reduce the API surface area, the `fullyQualifiedNamesAreEqualAsPredicate()` method was removed again (only one usage). Also, `getFullyQualifiedClassPath()` was renamed to `toFullyQualifiedName()`. Note: According to the JLS this would be called the _canonical name_, whereas the name with dollars in it would be the _binary name_. But for consistency I stayed with the _fully qualified name_ nomenclature. --- .../org/openrewrite/java/RemoveUnusedImports.java | 12 +++++++----- .../java/org/openrewrite/java/tree/JavaType.java | 2 +- .../java/org/openrewrite/java/tree/TypeUtils.java | 8 ++------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java index 446652158cb..bfdb63c3d21 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java @@ -30,13 +30,14 @@ import java.time.Duration; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Collections.emptySet; import static org.openrewrite.java.style.ImportLayoutStyle.isPackageAlwaysFolded; -import static org.openrewrite.java.tree.TypeUtils.fullyQualifiedNamesAreEqualAsPredicate; -import static org.openrewrite.java.tree.TypeUtils.getFullyQualifiedClassPath; +import static org.openrewrite.java.tree.TypeUtils.fullyQualifiedNamesAreEqual; +import static org.openrewrite.java.tree.TypeUtils.toFullyQualifiedName; /** * This recipe will remove any imports for types that are not referenced within the compilation unit. This recipe @@ -160,7 +161,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon // see https://github.com/openrewrite/rewrite/issues/1698 for more detail String target = qualid.getTarget().toString(); String modifiedTarget = methodsAndFieldsByTypeName.keySet().stream() - .filter(fullyQualifiedNamesAreEqualAsPredicate(target)) + .filter((fqn) -> fullyQualifiedNamesAreEqual(target, fqn)) .findFirst() .orElse(target); SortedSet<String> targetMethodsAndFields = methodsAndFieldsByTypeName.get(modifiedTarget); @@ -222,7 +223,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon } } else { Set<JavaType.FullyQualified> types = typesByPackage.getOrDefault(elem.getPackageName(), new HashSet<>()); - Set<JavaType.FullyQualified> typesByFullyQualifiedClassPath = typesByPackage.getOrDefault(getFullyQualifiedClassPath(elem.getPackageName()), new HashSet<>()); + Set<JavaType.FullyQualified> typesByFullyQualifiedClassPath = typesByPackage.getOrDefault(toFullyQualifiedName(elem.getPackageName()), new HashSet<>()); Set<JavaType.FullyQualified> combinedTypes = Stream.concat(types.stream(), typesByFullyQualifiedClassPath.stream()) .collect(Collectors.toSet()); JavaType.FullyQualified qualidType = TypeUtils.asFullyQualified(elem.getQualid().getType()); @@ -256,7 +257,8 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon if ("*".equals(elem.getQualid().getSimpleName())) { return elem.getPackageName().equals(c.getPackageName()); } - return fullyQualifiedNamesAreEqualAsPredicate(c.getFullyQualifiedName()).test(elem.getTypeName()); + @Nullable String fqn1 = c.getFullyQualifiedName(); + return ((Predicate<String>) (fqn2) -> fullyQualifiedNamesAreEqual(fqn1, fqn2)).test(elem.getTypeName()); })) { anImport.used = false; changed = true; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java index 30702218c45..aa29dfd44c0 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java @@ -277,7 +277,7 @@ public E next() { public String getClassName() { String fqn = getFullyQualifiedName(); String className = fqn.substring(fqn.lastIndexOf('.') + 1); - return className.replace('$', '.'); + return TypeUtils.toFullyQualifiedName(className); } public String getPackageName() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index edf47ee522f..70d5f04097c 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -38,22 +38,18 @@ public static boolean isString(@Nullable JavaType type) { ); } - public static String getFullyQualifiedClassPath(String fqn) { + public static String toFullyQualifiedName(String fqn) { return fqn.replace('$', '.'); } public static boolean fullyQualifiedNamesAreEqual(@Nullable String fqn1, @Nullable String fqn2) { if (fqn1 != null && fqn2 != null) { return fqn1.equals(fqn2) || fqn1.length() == fqn2.length() - && getFullyQualifiedClassPath(fqn1).equals(getFullyQualifiedClassPath(fqn2)); + && toFullyQualifiedName(fqn1).equals(toFullyQualifiedName(fqn2)); } return fqn1 == null && fqn2 == null; } - public static Predicate<String> fullyQualifiedNamesAreEqualAsPredicate(@Nullable String fqn1) { - return (fqn2) -> fullyQualifiedNamesAreEqual(fqn1, fqn2); - } - /** * Returns true if the JavaTypes are of the same type. * {@link JavaType.Parameterized} will be checked for both the FQN and each of the parameters. From 9f0a4574102744d910b466efb765ab60588bec22 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 8 Sep 2023 10:38:42 +0200 Subject: [PATCH 218/447] Fix parsing and printing of array type literals in Javadoc Fixes: #3530 --- .../ReloadableJava17JavadocVisitor.java | 34 +++++++++++++++++++ .../openrewrite/java/tree/JavadocTest.java | 13 +++++++ .../org/openrewrite/java/JavadocPrinter.java | 14 ++++++++ 3 files changed, 61 insertions(+) diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java index 8d289b3152c..ad3a86e473d 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java @@ -17,6 +17,7 @@ import com.sun.source.doctree.*; import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.ArrayTypeTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.PrimitiveTypeTree; @@ -1132,6 +1133,39 @@ public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, typeMapping.primitive(primitiveType.typetag), null); } + @Override + public J visitArrayType(ArrayTypeTree node, Space fmt) { + com.sun.source.tree.Tree typeIdent = node.getType(); + int dimCount = 1; + + while (typeIdent instanceof ArrayTypeTree) { + dimCount++; + typeIdent = ((ArrayTypeTree) typeIdent).getType(); + } + + TypeTree elemType = (TypeTree) scan(typeIdent, fmt); + + List<JRightPadded<Space>> dimensions = emptyList(); + if (dimCount > 0) { + dimensions = new ArrayList<>(dimCount); + for (int n = 0; n < dimCount; n++) { + dimensions.add(padRight(Space.build(sourceBeforeAsString("["), emptyList()), Space.build(sourceBeforeAsString("]"), emptyList()))); + } + } + + return new J.ArrayType( + randomId(), + fmt, + Markers.EMPTY, + elemType, + dimensions + ); + } + + private <T> JRightPadded<T> padRight(T tree, Space right) { + return new JRightPadded<>(tree, right, Markers.EMPTY); + } + @Override public J visitParameterizedType(ParameterizedTypeTree node, Space fmt) { NameTree id = (NameTree) javaVisitor.scan(node.getType(), Space.EMPTY); diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java index d9272f2902c..46f3bcc861e 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java @@ -1634,4 +1634,17 @@ void trailingWhitespaceWithLF() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3530") + void arrayTypeLiterals() { + rewriteRun( + java("" + + " /**\n" + + " * Create an instance of {@link byte[]} and {@link byte[][]}\n" + + " */\n" + + "class A {}" + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java index 88b5f9dff78..af78d3099c6 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java @@ -400,6 +400,20 @@ public J visitMemberReference(J.MemberReference memberRef, PrintOutputCapture<P> return memberRef; } + @Override + public J visitArrayType(J.ArrayType arrayType, PrintOutputCapture<P> p) { + beforeSyntax(arrayType, Space.Location.ARRAY_TYPE_PREFIX, p); + visit(arrayType.getElementType(), p); + for (JRightPadded<Space> d : arrayType.getDimensions()) { + visitSpace(d.getElement(), Space.Location.DIMENSION, p); + p.append('['); + visitSpace(d.getAfter(), Space.Location.DIMENSION_SUFFIX, p); + p.append(']'); + } + afterSyntax(arrayType, p); + return arrayType; + } + @Override public J visitParameterizedType(J.ParameterizedType type, PrintOutputCapture<P> p) { beforeSyntax(type, Space.Location.IDENTIFIER_PREFIX, p); From b3b6c120ee8790f49af658c3d823e58db5523d7a Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 8 Sep 2023 10:53:19 +0200 Subject: [PATCH 219/447] Remove `@ExpectedToFail` annotation Actual fix is in https://github.com/openrewrite/rewrite/commit/9f0a4574102744d910b466efb765ab60588bec22. Fixes: #3198 --- .../src/test/java/org/openrewrite/java/AddLicenseHeaderTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/AddLicenseHeaderTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/AddLicenseHeaderTest.java index 5ffacf6fbaf..22a4cc164e2 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/AddLicenseHeaderTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/AddLicenseHeaderTest.java @@ -82,7 +82,6 @@ class Test { } @Test - @ExpectedToFail @Issue("https://github.com/openrewrite/rewrite/issues/3198") void dontChangeJavadoc() { rewriteRun( From 98051aad9d5dd4d962fe0d0d96a71382cc6a423a Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 8 Sep 2023 11:42:11 +0200 Subject: [PATCH 220/447] Remove `RemoveUnusedImports#unfoldWildcard` option again As this option was not used anywhere in the implementation, I am removing it now again. See: #3527 --- .../java/RemoveUnusedImportsTest.java | 4 ++-- .../org/openrewrite/java/OrderImports.java | 2 +- .../openrewrite/java/RemoveUnusedImports.java | 20 ++----------------- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java index de8a04b8947..66b50859746 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java @@ -31,7 +31,7 @@ class RemoveUnusedImportsTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new RemoveUnusedImports(true)); + spec.recipe(new RemoveUnusedImports()); } @Test @@ -1219,7 +1219,7 @@ void f(Record r) { @Test void doesNotUnfoldWildCardImport() { rewriteRun( - spec -> spec.recipe(new RemoveUnusedImports(false)), + spec -> spec.recipe(new RemoveUnusedImports()), java( """ package com.Source.mine; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java b/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java index 7b1c5f5de55..3937062d307 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/OrderImports.java @@ -96,7 +96,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon } if (Boolean.TRUE.equals(removeUnused)) { - doAfterVisit(new RemoveUnusedImports(true).getVisitor()); + doAfterVisit(new RemoveUnusedImports().getVisitor()); } else if (changed) { doAfterVisit(new FormatFirstClassPrefix<>()); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java index bfdb63c3d21..2dc88645105 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java @@ -18,11 +18,9 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.ExecutionContext; -import org.openrewrite.Option; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.style.ImportLayoutStyle; import org.openrewrite.java.style.IntelliJ; import org.openrewrite.java.tree.*; @@ -30,7 +28,6 @@ import java.time.Duration; import java.util.*; -import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -48,12 +45,6 @@ @EqualsAndHashCode(callSuper = true) public class RemoveUnusedImports extends Recipe { - @Option(displayName = "Unfold wildcard imports", - description = "Expand wildcard imports into individual imports", - required = false) - @Nullable - Boolean unfoldWildcard; - @Override public String getDisplayName() { return "Remove unused imports"; @@ -78,17 +69,11 @@ public Duration getEstimatedEffortPerOccurrence() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - return Preconditions.check(new NoMissingTypes(), new RemoveUnusedImportsVisitor(Boolean.TRUE.equals(unfoldWildcard))); + return Preconditions.check(new NoMissingTypes(), new RemoveUnusedImportsVisitor()); } private static class RemoveUnusedImportsVisitor extends JavaIsoVisitor<ExecutionContext> { - private boolean unfoldWildcard; - - public RemoveUnusedImportsVisitor(final boolean unfoldWildcard) { - this.unfoldWildcard = unfoldWildcard; - } - @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { ImportLayoutStyle layoutStyle = Optional.ofNullable(cu.getStyle(ImportLayoutStyle.class)) @@ -257,8 +242,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon if ("*".equals(elem.getQualid().getSimpleName())) { return elem.getPackageName().equals(c.getPackageName()); } - @Nullable String fqn1 = c.getFullyQualifiedName(); - return ((Predicate<String>) (fqn2) -> fullyQualifiedNamesAreEqual(fqn1, fqn2)).test(elem.getTypeName()); + return fullyQualifiedNamesAreEqual(c.getFullyQualifiedName(), elem.getTypeName()); })) { anImport.used = false; changed = true; From 31e20f50d18656b1e90a3bc26a96a61e509fb04f Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 8 Sep 2023 11:59:22 +0200 Subject: [PATCH 221/447] Fix formatting of `RemoveUnusedImportsTest` test --- .../openrewrite/java/RemoveUnusedImportsTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java index 66b50859746..2c570fd759f 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java @@ -831,7 +831,7 @@ void doNotUnfoldSubpackage() { import static java.util.Collections.*; class Test { - ConcurrentHashMap<String, String> m = new ConcurrentHashMap(emptyMap()); + ConcurrentHashMap<String, String> m = new ConcurrentHashMap<>(emptyMap()); } """ ) @@ -973,7 +973,7 @@ public enum Enums { } public static void helloWorld() { - System.out.println("hello world!"); + System.out.println("hello world!"); } } """ @@ -1003,7 +1003,7 @@ class Test { public static void main(String[] args) { var uniqueCount = B1; helloWorld(); - } + } } """ ) @@ -1030,7 +1030,7 @@ public enum Enums { } public static void helloWorld() { - System.out.println("hello world!"); + System.out.println("hello world!"); } } """ @@ -1065,7 +1065,7 @@ public static void main(String[] args) { var uniqueCount = B1; var uniqueCountNested = C1; helloWorld(); - } + } } """ ) @@ -1085,7 +1085,7 @@ public class A { public short getShort1() { return SHORT1; - } + } } """ ), From 2323f550b700858576433527b2e22c57a31d9791 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sun, 10 Sep 2023 22:08:58 +0200 Subject: [PATCH 222/447] Allow `JavaReflectionTypeMapping` to understand primitive array values --- .../internal/JavaReflectionTypeMapping.java | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java index 3e559319a34..c78fb200d13 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java @@ -23,10 +23,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.*; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import static java.util.Collections.emptyList; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.*; @@ -400,11 +398,44 @@ private JavaType.Method method(Method method, JavaType.FullyQualified declaringT } List<String> defaultValues = null; - if(method.getDefaultValue() != null) { - if(method.getDefaultValue().getClass().isArray()) { - defaultValues = Arrays.stream((Object[])method.getDefaultValue()) - .map(Object::toString) - .collect(Collectors.toList()); + if (method.getDefaultValue() != null) { + Class<?> valueClass = method.getDefaultValue().getClass(); + if (valueClass.isArray()) { + defaultValues = new ArrayList<>(); + Class<?> elementType = valueClass.getComponentType(); + if (elementType == int.class) { + for (int v : ((int[]) method.getDefaultValue())) { + defaultValues.add(String.valueOf(v)); + } + } else if (elementType == long.class) { + for (long v : ((long[]) method.getDefaultValue())) { + defaultValues.add(String.valueOf(v)); + } + } else if (elementType == byte.class) { + for (byte v : ((byte[]) method.getDefaultValue())) { + defaultValues.add(String.valueOf(v)); + } + } else if (elementType == boolean.class) { + for (boolean v : ((boolean[]) method.getDefaultValue())) { + defaultValues.add(String.valueOf(v)); + } + } else if (elementType == short.class) { + for (short v : ((short[]) method.getDefaultValue())) { + defaultValues.add(String.valueOf(v)); + } + } else if (elementType == double.class) { + for (double v : ((double[]) method.getDefaultValue())) { + defaultValues.add(String.valueOf(v)); + } + } else if (elementType == float.class) { + for (float v : ((float[]) method.getDefaultValue())) { + defaultValues.add(String.valueOf(v)); + } + } else { + for (Object v : ((Object[]) method.getDefaultValue())) { + defaultValues.add(String.valueOf(v)); + } + } } else { defaultValues = Collections.singletonList(method.getDefaultValue().toString()); } From 7adcf1d789740d0063e9379a4b235c24d9ff89d8 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sun, 10 Sep 2023 22:38:49 +0200 Subject: [PATCH 223/447] Fix spacing bug in `ReloadableJava17JavadocVisitor` --- .../ReloadableJava17JavadocVisitor.java | 2 +- .../openrewrite/java/tree/JavadocTest.java | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java index ad3a86e473d..ef5aea9f48f 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java @@ -1155,7 +1155,7 @@ public J visitArrayType(ArrayTypeTree node, Space fmt) { return new J.ArrayType( randomId(), - fmt, + Space.EMPTY, Markers.EMPTY, elemType, dimensions diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java index 46f3bcc861e..5f7e50be314 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java @@ -1640,9 +1640,22 @@ void trailingWhitespaceWithLF() { void arrayTypeLiterals() { rewriteRun( java("" + - " /**\n" + - " * Create an instance of {@link byte[]} and {@link byte[][]}\n" + - " */\n" + + "/**\n" + + " * Create an instance of {@link byte[]} and {@link byte[][]}\n" + + " */\n" + + "class A {}" + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3530") + void arrayTypeLiterals2() { + rewriteRun( + java("" + + "/**\n" + + " * <p>Values are converted to strings using {@link java.util.Arrays#compare(Comparable[], Comparable[])}}.\n" + + " */\n" + "class A {}" ) ); From d7a1546ba20a2d74fc36a42ab27a67f87d69d36f Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Mon, 11 Sep 2023 09:07:39 +0200 Subject: [PATCH 224/447] Allow `Space#format()` to accept `/` in whitespace When parsers have bugs they may end up using `Space#format()` for text which actually isn't whitespace. It is however still useful if the returned result preserves all input characters. --- .../src/main/java/org/openrewrite/java/tree/Space.java | 7 +++++-- .../test/java/org/openrewrite/java/tree/SpaceTest.java | 10 +++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index bace53ca9f0..6ae31e43796 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -163,15 +163,18 @@ public static Space format(String formatting, int beginIndex, int toIndex) { } else if (last == '/' && !inMultiLineComment) { inSingleLineComment = true; comment.setLength(0); + prefix.setLength(prefix.length() - 1); } else if (last == '*' && inMultiLineComment && comment.length() > 0) { inMultiLineComment = false; comment.setLength(comment.length() - 1); // trim the last '*' - comments.add(new TextComment(true, comment.toString(), prefix.toString(), Markers.EMPTY)); + comments.add(new TextComment(true, comment.toString(), prefix.substring(0, prefix.length() - 1), Markers.EMPTY)); prefix.setLength(0); comment.setLength(0); continue; - } else { + } else if (inMultiLineComment) { comment.append(c); + } else { + prefix.append(c); } break; case '\r': diff --git a/rewrite-java/src/test/java/org/openrewrite/java/tree/SpaceTest.java b/rewrite-java/src/test/java/org/openrewrite/java/tree/SpaceTest.java index 57f4dd802bb..10dcf368a5a 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/tree/SpaceTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/tree/SpaceTest.java @@ -170,7 +170,7 @@ void singleLineCommentSuffix() { } @Test - void MultiCommentsSuffix() { + void multiCommentsSuffix() { String input = """ //c1 @@ -184,4 +184,12 @@ void MultiCommentsSuffix() { assertThat(c2.getText()).isEqualTo("c2"); assertThat(c2.getSuffix()).isEqualTo(""); } + + @Test + void slashInNonComment() { + String input = "foo/bar"; + var space = Space.format(input); + assertThat(space.getComments()).isEmpty(); + assertThat(space.getWhitespace()).isEqualTo(input); + } } From 756a43d2cd04296e4702d6cbb95bc141c64fe042 Mon Sep 17 00:00:00 2001 From: Valentin Delaye <jonesbusy@users.noreply.github.com> Date: Mon, 11 Sep 2023 14:43:58 +0200 Subject: [PATCH 225/447] Add support for relativePath change on UpdateParentPom (#3537) --- .../openrewrite/maven/ChangeParentPom.java | 33 ++- .../maven/UpgradeParentVersion.java | 2 +- .../maven/ChangeParentPomTest.java | 188 +++++++++++++++++- 3 files changed, 213 insertions(+), 10 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java index 65a990a8990..9cd4e38766c 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java @@ -19,6 +19,7 @@ import lombok.Value; import org.openrewrite.*; import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.internal.StringUtils; import org.openrewrite.marker.SearchResult; import org.openrewrite.maven.table.MavenMetadataFailures; import org.openrewrite.maven.tree.MavenMetadata; @@ -27,6 +28,7 @@ import org.openrewrite.maven.utilities.RetainVersions; import org.openrewrite.semver.Semver; import org.openrewrite.semver.VersionComparator; +import org.openrewrite.xml.AddToTagVisitor; import org.openrewrite.xml.ChangeTagValueVisitor; import org.openrewrite.xml.XmlVisitor; import org.openrewrite.xml.tree.Xml; @@ -85,6 +87,20 @@ public String getDescription() { example = "29.X") String newVersion; + @Option(displayName = "Old relative path", + description = "The relativePath of the maven parent pom to be changed away from.", + example = "../../pom.xml", + required = false) + @Nullable + String oldRelativePath; + + @Option(displayName = "New relative path", + description = "New relative path attribute for parent lookup.", + example = "../pom.xml", + required = false) + @Nullable + String newRelativePath; + @Option(displayName = "Version pattern", description = "Allows version selection to be extended beyond the original Node Semver semantics. So for example," + "Setting 'version' to \"25-29\" can be paired with a metadata pattern of \"-jre\" to select Guava 29.0-jre", @@ -161,11 +177,13 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { ResolvedPom resolvedPom = getResolutionResult().getPom(); if (matchesGlob(resolvedPom.getValue(tag.getChildValue("groupId").orElse(null)), oldGroupId) && - matchesGlob(resolvedPom.getValue(tag.getChildValue("artifactId").orElse(null)), oldArtifactId)) { + matchesGlob(resolvedPom.getValue(tag.getChildValue("artifactId").orElse(null)), oldArtifactId) && + (oldRelativePath == null || matchesGlob(resolvedPom.getValue(tag.getChildValue("relativePath").orElse(null)), oldRelativePath))) { String oldVersion = resolvedPom.getValue(tag.getChildValue("version").orElse(null)); assert oldVersion != null; String targetGroupId = newGroupId == null ? tag.getChildValue("groupId").orElse(oldGroupId) : newGroupId; String targetArtifactId = newArtifactId == null ? tag.getChildValue("artifactId").orElse(oldArtifactId) : newArtifactId; + String targetRelativePath = newRelativePath == null ? tag.getChildValue("relativePath").orElse(oldRelativePath) : newRelativePath; try { Optional<String> targetVersion = findNewerDependencyVersion(targetGroupId, targetArtifactId, oldVersion, ctx); if (targetVersion.isPresent()) { @@ -182,6 +200,19 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { changeParentTagVisitors.add(new ChangeTagValueVisitor<>(t.getChild("version").get(), targetVersion.get())); } + // Update or add relativePath + if (oldRelativePath != null && !oldRelativePath.equals(targetRelativePath)) { + changeParentTagVisitors.add(new ChangeTagValueVisitor<>(t.getChild("relativePath").get(), targetRelativePath)); + } + else if (tag.getChildValue("relativePath").orElse(null) == null && targetRelativePath != null) { + if (StringUtils.isBlank(targetRelativePath)) { + changeParentTagVisitors.add(new AddToTagVisitor<>(t, Xml.Tag.build("<relativePath />"))); + } + else { + changeParentTagVisitors.add(new AddToTagVisitor<>(t, Xml.Tag.build("<relativePath>" + targetRelativePath + "</relativePath>"))); + } + } + if (changeParentTagVisitors.size() > 0) { retainVersions(); doAfterVisit(new RemoveRedundantDependencyVersions(null, null, true, retainVersions).getVisitor()); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeParentVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeParentVersion.java index 0c460196786..982c66e7b82 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeParentVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeParentVersion.java @@ -91,7 +91,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { } private ChangeParentPom changeParentPom() { - return new ChangeParentPom(groupId, null, artifactId, null, newVersion, + return new ChangeParentPom(groupId, null, artifactId, null, newVersion, null, null, versionPattern, false, retainVersions); } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java index 85a6fae5a3f..21e8ea19f7f 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java @@ -42,6 +42,110 @@ void changeParent() { "jackson-parent", "2.12", null, + null, + null, + false, + null + )), + pomXml( + """ + <project> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>1.5.12.RELEASE</version> + </parent> + + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </project> + """, + """ + <project> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>com.fasterxml.jackson</groupId> + <artifactId>jackson-parent</artifactId> + <version>2.12</version> + </parent> + + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </project> + """ + ) + ); + } + + @Test + void changeParentWithRelativePath() { + rewriteRun( + spec -> spec.recipe(new ChangeParentPom( + "org.springframework.boot", + "com.fasterxml.jackson", + "spring-boot-starter-parent", + "jackson-parent", + "2.12", + "", + "../../pom.xml", + null, + false, + null + )), + pomXml( + """ + <project> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>1.5.12.RELEASE</version> + <relativePath/> + </parent> + + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </project> + """, + """ + <project> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>com.fasterxml.jackson</groupId> + <artifactId>jackson-parent</artifactId> + <version>2.12</version> + <relativePath>../../pom.xml</relativePath> + </parent> + + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </project> + """ + ) + ); + } + + @Test + void changeParentAddRelativePathEmptyValue() { + rewriteRun( + spec -> spec.recipe(new ChangeParentPom( + "org.springframework.boot", + "com.fasterxml.jackson", + "spring-boot-starter-parent", + "jackson-parent", + "2.12", + null, + "", + null, false, null )), @@ -69,6 +173,58 @@ void changeParent() { <groupId>com.fasterxml.jackson</groupId> <artifactId>jackson-parent</artifactId> <version>2.12</version> + <relativePath /> + </parent> + + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </project> + """ + ) + ); + } + + @Test + void changeParentAddRelativePathNonEmptyValue() { + rewriteRun( + spec -> spec.recipe(new ChangeParentPom( + "org.springframework.boot", + "com.fasterxml.jackson", + "spring-boot-starter-parent", + "jackson-parent", + "2.12", + null, + "../pom.xml", + null, + false, + null + )), + pomXml( + """ + <project> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>1.5.12.RELEASE</version> + </parent> + + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </project> + """, + """ + <project> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>com.fasterxml.jackson</groupId> + <artifactId>jackson-parent</artifactId> + <version>2.12</version> + <relativePath>../pom.xml</relativePath> </parent> <groupId>com.mycompany.app</groupId> @@ -90,6 +246,8 @@ void upgradeVersion() { null, "~1.5", null, + null, + null, false, null )), @@ -140,6 +298,8 @@ void upgradeToExactVersion() { null, "1.5.22.RELEASE", null, + null, + null, false, null )), @@ -190,6 +350,8 @@ void doNotDowngradeToLowerVersionWhenArtifactsAreTheSame() { null, "1.5.12.RELEASE", null, + null, + null, false, null )), @@ -224,6 +386,8 @@ void downgradeToLowerVersionWhenFlagIsSet() { null, "1.5.12.RELEASE", null, + null, + null, true, null )), @@ -274,6 +438,8 @@ void wildcardVersionUpdate() { null, "~1.5", null, + null, + null, false, null )), @@ -324,6 +490,8 @@ void removesRedundantExplicitVersionsMatchingOldParent() { null, "5.9.1", null, + null, + null, false, null )), @@ -389,6 +557,8 @@ void removesRedundantExplicitVersionsMatchingNewParent() { null, "5.9.1", null, + null, + null, false, null )), @@ -454,6 +624,8 @@ void keepsRedundantExplicitVersionsNotMatchingOldOrNewParent() { null, "5.9.1", null, + null, + null, false, null )), @@ -513,7 +685,7 @@ void keepsRedundantExplicitVersionsNotMatchingOldOrNewParent() { @Test void upgradeNonSemverVersion() { rewriteRun( - spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-starter-parent", null, "2021.0.5", null, false, null)), + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-starter-parent", null, "2021.0.5", null, null, null, false, null)), pomXml( """ <?xml version="1.0" encoding="UTF-8"?> @@ -556,7 +728,7 @@ class RetainVersions { @Test void dependencyWithExplicitVersionRemovedFromDepMgmt() { rewriteRun( - spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, null, null, List.of("com.jcraft:jsch"))), pomXml( """ @@ -612,7 +784,7 @@ void dependencyWithExplicitVersionRemovedFromDepMgmt() { @Test void dependencyWithoutExplicitVersionRemovedFromDepMgmt() { rewriteRun( - spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, null, null, Collections.singletonList("com.jcraft:jsch"))), pomXml( """ @@ -667,7 +839,7 @@ void dependencyWithoutExplicitVersionRemovedFromDepMgmt() { @Test void dependencyWithoutExplicitVersionRemovedFromDepMgmtRetainSpecificVersion() { rewriteRun( - spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-config-dependencies", null, "3.1.4", null, null, null, null, Collections.singletonList("com.jcraft:jsch:0.1.50"))), pomXml( """ @@ -722,7 +894,7 @@ void dependencyWithoutExplicitVersionRemovedFromDepMgmtRetainSpecificVersion() { @Test void multipleRetainVersions() { rewriteRun( - spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, true, + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, null, null, true, Lists.newArrayList("com.jcraft:jsch", "org.springframework.cloud:spring-cloud-schema-registry-*:1.1.1"))), pomXml( """ @@ -786,7 +958,7 @@ void multipleRetainVersions() { @Test void globGavWithNoVersion() { rewriteRun( - spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, true, + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, null, null, true, Lists.newArrayList("org.springframework.cloud:spring-cloud-schema-registry-*"))), pomXml( """ @@ -841,7 +1013,7 @@ void globGavWithNoVersion() { @Test void preservesExplicitVersionIfNotRequested() { rewriteRun( - spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, true, + spec -> spec.recipe(new ChangeParentPom("org.springframework.cloud", null, "spring-cloud-dependencies", null, "2021.0.5", null, null, null, true, Lists.newArrayList("org.springframework.cloud:spring-cloud-schema-registry-*"))), pomXml( """ @@ -898,7 +1070,7 @@ void preservesExplicitVersionIfNotRequested() { @RepeatedTest(10) @Issue("https://github.com/openrewrite/rewrite/issues/1753") void multiModule() { - ChangeParentPom recipe = new ChangeParentPom("org.springframework.boot", null, "spring-boot-starter-parent", null, "2.6.7", null, true, null); + ChangeParentPom recipe = new ChangeParentPom("org.springframework.boot", null, "spring-boot-starter-parent", null, "2.6.7", null, null, null, true, null); rewriteRun( spec -> spec.recipe(recipe), mavenProject("parent", From 8d39d6f9a94bfc7b738d78d50a24912d673092c9 Mon Sep 17 00:00:00 2001 From: Thomas Zub <thomas.zub@outlook.de> Date: Mon, 11 Sep 2023 19:58:34 +0200 Subject: [PATCH 226/447] fix: use source file encoding for reading parser input (#3520) (#3521) * fix: use source file encoding for reading parser input (#3520) * Update rewrite-core/src/main/java/org/openrewrite/SourceFile.java Co-authored-by: Tim te Beek <timtebeek@gmail.com> --------- Co-authored-by: Tim te Beek <tim@moderne.io> Co-authored-by: Tim te Beek <timtebeek@gmail.com> --- .../main/java/org/openrewrite/SourceFile.java | 7 +- .../java/org/openrewrite/SourceFileTest.java | 65 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 rewrite-core/src/test/java/org/openrewrite/SourceFileTest.java diff --git a/rewrite-core/src/main/java/org/openrewrite/SourceFile.java b/rewrite-core/src/main/java/org/openrewrite/SourceFile.java index 4b2b3c0661b..cb626690210 100644 --- a/rewrite-core/src/main/java/org/openrewrite/SourceFile.java +++ b/rewrite-core/src/main/java/org/openrewrite/SourceFile.java @@ -36,7 +36,12 @@ public interface SourceFile extends Tree { * @return <code>true</code> if the parse-to-print loop is idempotent, <code>false</code> otherwise. */ default boolean printEqualsInput(Parser.Input input, ExecutionContext ctx) { - return printAll().equals(StringUtils.readFully(input.getSource(ctx))); + String printed = printAll(); + Charset charset = getCharset(); + if (charset != null) { + return printed.equals(StringUtils.readFully(input.getSource(ctx), charset)); + } + return printed.equals(StringUtils.readFully(input.getSource(ctx))); } /** diff --git a/rewrite-core/src/test/java/org/openrewrite/SourceFileTest.java b/rewrite-core/src/test/java/org/openrewrite/SourceFileTest.java new file mode 100644 index 00000000000..8f3624b6dac --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/SourceFileTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2022 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite; + +import org.junit.jupiter.api.Test; +import org.openrewrite.text.PlainText; + +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; + +class SourceFileTest { + + @Test + void isPrintEqualForDefaultCharsets() { + ExecutionContext ctx = new InMemoryExecutionContext(); + Parser.Input input = Parser.Input.fromString("äö"); + SourceFile sourceFile = PlainText.builder() + .text("äö") + .build(); + + assertThat(sourceFile.printEqualsInput(input, ctx)) + .isTrue(); + } + + @Test + void isPrintEqualForExplicitCharsets() { + ExecutionContext ctx = new InMemoryExecutionContext(); + Parser.Input input = Parser.Input.fromString("äö", StandardCharsets.ISO_8859_1); + SourceFile sourceFile = PlainText.builder() + .text("äö") + .charsetName("ISO-8859-1") + .build(); + + assertThat(sourceFile.printEqualsInput(input, ctx)) + .isTrue(); + } + + @Test + void isNotPrintEqualForDifferentCharsets() { + ExecutionContext ctx = new InMemoryExecutionContext(); + Parser.Input input = Parser.Input.fromString("äö"); + SourceFile sourceFile = PlainText.builder() + .text("äö") + .charsetName("ISO-8859-1") + .build(); + + assertThat(sourceFile.printEqualsInput(input, ctx)) + .isFalse(); + } + +} From 8905a8d48778566b6f548578ed2b85de52beb675 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Mon, 11 Sep 2023 20:11:19 +0200 Subject: [PATCH 227/447] Added new recipe to change project version of a maven project. (#3515) * Added new recipe to change maven project version * added license headers * added overrideParentVersion flag --- .../maven/ChangeProjectVersion.java | 120 +++++++ .../maven/ChangeProjectVersionTest.java | 295 ++++++++++++++++++ 2 files changed, 415 insertions(+) create mode 100755 rewrite-maven/src/main/java/org/openrewrite/maven/ChangeProjectVersion.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/ChangeProjectVersionTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeProjectVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeProjectVersion.java new file mode 100755 index 00000000000..3ce3aea8850 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeProjectVersion.java @@ -0,0 +1,120 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.maven.tree.ResolvedPom; +import org.openrewrite.xml.AddToTagVisitor; +import org.openrewrite.xml.ChangeTagValueVisitor; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.tree.Xml; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; + +import static org.openrewrite.internal.StringUtils.matchesGlob; + +@Value +@EqualsAndHashCode(callSuper = true) +public class ChangeProjectVersion extends Recipe { + // there are several implicitly defined version properties that we should never attempt to update + private static final Collection<String> implicitlyDefinedVersionProperties = Arrays.asList( + "${version}", "${project.version}", "${pom.version}", "${project.parent.version}" + ); + + @Override + public String getDisplayName() { + return "Change Maven Project Version"; + } + + @Override + public String getDescription() { + return "Change the project version of a Maven pom.xml. Identifies the project to be changed by its groupId and artifactId. " + + "If the version is defined as a property, this recipe will only change the property value if the property exists within the same pom."; + } + + @Option(displayName = "GroupId", + description = "The groupId of the maven project to change it's version. This can be a glob expression.", + example = "org.openrewrite") + String groupId; + + @Option(displayName = "ArtifactId", + description = "The artifactId of the maven project to change it's version. This can be a glob expression.", + example = "rewrite-maven") + String artifactId; + + @Option(displayName = "New version", + description = "The new version to replace the maven project version.", + example = "8.4.2") + String newVersion; + + @Option(displayName = "Override Parent Version", + description = "This flag can be set to explicitly override the inherited parent version. The default for this flag is `false`.", + required = false + ) + @Nullable + Boolean overrideParentVersion; + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + + return new MavenIsoVisitor<ExecutionContext>() { + private final XPathMatcher PROJECT_MATCHER = new XPathMatcher("/project"); + + @Override + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + + if (PROJECT_MATCHER.matches(getCursor())) { + ResolvedPom resolvedPom = getResolutionResult().getPom(); + + if (matchesGlob(resolvedPom.getValue(t.getChildValue("groupId").orElse(null)), groupId) && + matchesGlob(resolvedPom.getValue(t.getChildValue("artifactId").orElse(null)), artifactId)) { + Optional<Xml.Tag> versionTag = t.getChild("version"); + if (versionTag.isPresent() && versionTag.get().getValue().isPresent()) { + String versionTagValue = versionTag.get().getValue().get(); + String oldVersion = resolvedPom.getValue(versionTagValue); + assert oldVersion != null; + + if (!oldVersion.equals(newVersion)) { + if (versionTagValue.startsWith("${") && !implicitlyDefinedVersionProperties.contains(versionTagValue)) { + doAfterVisit(new ChangePropertyValue(versionTagValue.substring(2, versionTagValue.length() - 1), newVersion, false, false).getVisitor()); + } else { + doAfterVisit(new ChangeTagValueVisitor<>(versionTag.get(), newVersion)); + } + maybeUpdateModel(); + } + } else if (Boolean.TRUE.equals(overrideParentVersion)) { + // if the version is not present and the override parent version is set, + // add a new explicit version tag + Xml.Tag newVersionTag = Xml.Tag.build("<version>" + newVersion + "</version>"); + doAfterVisit(new AddToTagVisitor<>(t, newVersionTag, new MavenTagInsertionComparator(t.getChildren()))); + maybeUpdateModel(); + } + } + } + return t; + } + }; + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeProjectVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeProjectVersionTest.java new file mode 100644 index 00000000000..055637e3ab7 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeProjectVersionTest.java @@ -0,0 +1,295 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.mavenProject; +import static org.openrewrite.maven.Assertions.pomXml; + +public class ChangeProjectVersionTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ChangeProjectVersion("org.openrewrite", "rewrite-maven", "8.4.2", null)); + } + + @Test + void changeProjectVersion() { + rewriteRun( + pomXml( + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>8.4.1</version> + </project> + """, """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>8.4.2</version> + </project> + """ + ) + ); + } + + @Test + void changeProjectVersionProperty() { + rewriteRun( + pomXml( + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>${rewrite.version}</version> + + <properties> + <rewrite.version>8.4.1</rewrite.version> + </properties> + </project> + """, """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>${rewrite.version}</version> + + <properties> + <rewrite.version>8.4.2</rewrite.version> + </properties> + </project> + """ + ) + ); + } + + @Test + void changeProjectVersionResolveProperties() { + rewriteRun( + pomXml( + """ + <project> + <groupId>${rewrite.groupId}</groupId> + <artifactId>${rewrite-maven.artifactId}</artifactId> + <version>8.4.1</version> + + <properties> + <rewrite.groupId>org.openrewrite</rewrite.groupId> + <rewrite-maven.artifactId>rewrite-maven</rewrite-maven.artifactId> + </properties> + </project> + """, """ + <project> + <groupId>${rewrite.groupId}</groupId> + <artifactId>${rewrite-maven.artifactId}</artifactId> + <version>8.4.2</version> + + <properties> + <rewrite.groupId>org.openrewrite</rewrite.groupId> + <rewrite-maven.artifactId>rewrite-maven</rewrite-maven.artifactId> + </properties> + </project> + """ + ) + ); + } + + @Test + void changeProjectVersionResolvePropertiesOnParent() { + rewriteRun( + pomXml( + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-core</artifactId> + <version>8.4.1</version> + + <properties> + <rewrite.groupId>org.openrewrite</rewrite.groupId> + <rewrite-maven.artifactId>rewrite-maven</rewrite-maven.artifactId> + </properties> + + <packaging>pom</packaging> + </project> + """ + ), + mavenProject("rewrite-maven", + pomXml( + """ + <project> + <parent> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-core</artifactId> + <version>8.4.1</version> + </parent> + + <groupId>${rewrite.groupId}</groupId> + <artifactId>${rewrite-maven.artifactId}</artifactId> + <version>8.4.1</version> + </project> + """, """ + <project> + <parent> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-core</artifactId> + <version>8.4.1</version> + </parent> + + <groupId>${rewrite.groupId}</groupId> + <artifactId>${rewrite-maven.artifactId}</artifactId> + <version>8.4.2</version> + </project> + """ + ) + ) + ); + } + + @Test + void doNotChangeOtherProjectVersion() { + rewriteRun( + pomXml( + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-gradle</artifactId> + <version>8.4.1</version> + </project> + """ + ) + ); + } + + @Test + void changesMultipleMatchingProjects() { + rewriteRun( + spec -> spec.recipe(new ChangeProjectVersion("org.openrewrite", "rewrite-*", "8.4.2", null)), + pomXml( + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>8.4.1</version> + </project> + """, """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>8.4.2</version> + </project> + """ + ), + pomXml( + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-gradle</artifactId> + <version>8.4.1</version> + </project> + """, """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-gradle</artifactId> + <version>8.4.2</version> + </project> + """ + ) + ); + } + + @Test + void doNotChangeProjectVersionInheritedFromParent() { + rewriteRun( + pomXml( + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-core</artifactId> + <version>8.4.1</version> + + <packaging>pom</packaging> + </project> + """ + ), + mavenProject("rewrite-maven", + pomXml( + """ + <project> + <parent> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-core</artifactId> + <version>8.4.1</version> + </parent> + + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + </project> + """ + ) + ) + ); + } + + @Test + void changeProjectVersionInheritedFromParentIfOverrideParentVersion() { + rewriteRun( + spec -> spec.recipe(new ChangeProjectVersion("org.openrewrite", "rewrite-maven", "8.4.2", true)), + pomXml( + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-core</artifactId> + <version>8.4.1</version> + + <packaging>pom</packaging> + </project> + """ + ), + mavenProject("rewrite-maven", + pomXml( + """ + <project> + <parent> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-core</artifactId> + <version>8.4.1</version> + </parent> + + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + </project> + """, """ + <project> + <parent> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-core</artifactId> + <version>8.4.1</version> + </parent> + + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>8.4.2</version> + </project> + """ + ) + ) + ); + } + +} From 34cd31ccd4a7c7de34932ae2e4ce2d5e008cab0b Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Fri, 8 Sep 2023 23:01:30 -0700 Subject: [PATCH 228/447] Groovy parser support for java-style lambdas --- .../groovy/GroovyParserVisitor.java | 47 +++++++++------ .../org/openrewrite/groovy/GroovyPrinter.java | 20 ++++++- .../groovy/marker/LambdaStyle.java | 38 +++++++++++++ .../openrewrite/groovy/tree/LambdaTest.java | 57 ++++++++++++++++++- 4 files changed, 139 insertions(+), 23 deletions(-) create mode 100644 rewrite-groovy/src/main/java/org/openrewrite/groovy/marker/LambdaStyle.java diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index b3124129f08..8b85a276346 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -993,9 +993,17 @@ public void visitCastExpression(CastExpression cast) { @Override public void visitClosureExpression(ClosureExpression expression) { - Space prefix = sourceBefore("{"); + Space prefix = whitespace(); + LambdaStyle ls = new LambdaStyle(randomId(), expression instanceof LambdaExpression, true); + boolean parenthesized = false; + if(source.charAt(cursor) == '(') { + parenthesized = true; + cursor += 1; // skip '(' + } else if (source.charAt(cursor) == '{') { + cursor += 1; // skip '{' + } JavaType closureType = typeMapping.type(staticType(expression)); - List<JRightPadded<J>> paramExprs = emptyList(); + List<JRightPadded<J>> paramExprs; if (expression.getParameters() != null && expression.getParameters().length > 0) { paramExprs = new ArrayList<>(expression.getParameters().length); Parameter[] parameters = expression.getParameters(); @@ -1016,36 +1024,39 @@ null, emptyList(), JRightPadded<J> param = JRightPadded.build(expr); if (i != parameters.length - 1) { param = param.withAfter(sourceBefore(",")); + } else { + param = param.withAfter(whitespace()); } paramExprs.add(param); } + } else { + Space argPrefix = EMPTY; + if(parenthesized) { + argPrefix = whitespace(); + } + paramExprs = singletonList(JRightPadded.build(new J.Empty(randomId(), argPrefix, Markers.EMPTY))); } - - J.Lambda.Parameters params = new J.Lambda.Parameters(randomId(), EMPTY, Markers.EMPTY, false, paramExprs); + if(parenthesized) { + cursor += 1; + } + J.Lambda.Parameters params = new J.Lambda.Parameters(randomId(), EMPTY, Markers.EMPTY, parenthesized, paramExprs); int saveCursor = cursor; Space arrowPrefix = whitespace(); if (source.startsWith("->", cursor)) { cursor += "->".length(); - if (params.getParameters().isEmpty()) { - params = params.getPadding().withParams(singletonList(JRightPadded - .build((J) new J.Empty(randomId(), EMPTY, Markers.EMPTY)) - .withAfter(arrowPrefix))); - } else { - params = params.getPadding().withParams( - ListUtils.mapLast(params.getPadding().getParams(), param -> param.withAfter(arrowPrefix)) - ); - } } else { + ls = ls.withArrow(false); cursor = saveCursor; + arrowPrefix = EMPTY; } - J body = visit(expression.getCode()); - queue.add(new J.Lambda(randomId(), prefix, Markers.EMPTY, params, - EMPTY, + queue.add(new J.Lambda(randomId(), prefix, Markers.build(singletonList(ls)), params, + arrowPrefix, body, closureType)); - - cursor += 1; // skip '}' + if(cursor < source.length() && source.charAt(cursor) == '}') { + cursor++; + } } @Override diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyPrinter.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyPrinter.java index 02138868af4..51d3e73aa0f 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyPrinter.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyPrinter.java @@ -261,10 +261,22 @@ public J visitTypeCast(J.TypeCast t, PrintOutputCapture<P> p) { @Override public J visitLambda(J.Lambda lambda, PrintOutputCapture<P> p) { beforeSyntax(lambda, Space.Location.LAMBDA_PREFIX, p); - p.append('{'); + LambdaStyle ls = lambda.getMarkers().findFirst(LambdaStyle.class) + .orElse(new LambdaStyle(null, false, !lambda.getParameters().getParameters().isEmpty())); + boolean parenthesized = lambda.getParameters().isParenthesized(); + if(!ls.isJavaStyle()) { + p.append('{'); + } visitMarkers(lambda.getParameters().getMarkers(), p); + visitSpace(lambda.getParameters().getPrefix(), Space.Location.LAMBDA_PARAMETERS_PREFIX, p); + if(parenthesized) { + p.append('('); + } visitRightPadded(lambda.getParameters().getPadding().getParams(), JRightPadded.Location.LAMBDA_PARAM, ",", p); - if (!lambda.getParameters().getParameters().isEmpty()) { + if(parenthesized) { + p.append(')'); + } + if (ls.isArrow()) { visitSpace(lambda.getArrow(), Space.Location.LAMBDA_ARROW_PREFIX, p); p.append("->"); } @@ -275,7 +287,9 @@ public J visitLambda(J.Lambda lambda, PrintOutputCapture<P> p) { } else { visit(lambda.getBody(), p); } - p.append('}'); + if(!ls.isJavaStyle()) { + p.append('}'); + } afterSyntax(lambda, p); return lambda; } diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/marker/LambdaStyle.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/marker/LambdaStyle.java new file mode 100644 index 00000000000..4bbcb7e53d3 --- /dev/null +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/marker/LambdaStyle.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.groovy.marker; + +import lombok.Value; +import lombok.With; +import org.openrewrite.marker.Marker; + +import java.util.UUID; + +@Value +@With +public class LambdaStyle implements Marker { + UUID id; + /** + * `true` indicates a lambda of the form (parameter) -> body + * `false` indicates a lambda of the form { parameter -> body } + */ + boolean javaStyle; + + /** + * `true` indicates the presence of an arrow + */ + boolean arrow; +} diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LambdaTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LambdaTest.java index 9f987255e16..f0316c4483a 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LambdaTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LambdaTest.java @@ -24,14 +24,67 @@ @SuppressWarnings("GroovyUnusedAssignment") class LambdaTest implements RewriteTest { + @Test + void lambdaExpressionNoParens() { + rewriteRun( + groovy(""" + def lambda = a -> a + """) + ); + } + + @Test + void lambdaExpressionNoArguments() { + rewriteRun( + groovy(""" + ( ) -> arg + """) + ); + } + @Test + void lambdaExpressionWithArgument() { + rewriteRun( + groovy(""" + ( String arg ) -> arg + """) + ); + } + + @Test + void closureReturningLambda() { + rewriteRun( + groovy(""" + def foo(Closure cl) {} + foo { String a -> + ( _ ) -> a + } + """) + ); + } + + @Test + void closureParameterWithType() { + rewriteRun( + groovy(""" + class A {} + def foo(Closure cl) {} + foo { A a -> + a + a + } + """) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/2168") @Test - void lambdaWithNoArguments() { + void closureNoArguments() { rewriteRun( groovy( """ - def f1 = { -> 1 } + //def f1 = { -> 1 } def f2 = { 1 } + //def f3 = { -> } """ ) ); From eb8e004f7c21dbb58c1258dd0af791d11d82e804 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 11 Sep 2023 19:38:27 -0700 Subject: [PATCH 229/447] Add back in testcase I had commented out --- .../src/test/java/org/openrewrite/groovy/tree/LambdaTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LambdaTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LambdaTest.java index f0316c4483a..5ae92dce556 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LambdaTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LambdaTest.java @@ -82,9 +82,9 @@ void closureNoArguments() { rewriteRun( groovy( """ - //def f1 = { -> 1 } + def f1 = { -> 1 } def f2 = { 1 } - //def f3 = { -> } + def f3 = { -> } """ ) ); From b4602a55e56f1e4773423a4d96daa0be9d00d4d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Carpenter=E2=84=A2=EF=B8=8F?= <kevin@moderne.io> Date: Tue, 12 Sep 2023 09:14:00 -0300 Subject: [PATCH 230/447] Add cache name to InMemoryMavenPomCache to prevent gauges from overwriting one another (#3540) --- .../maven/cache/InMemoryMavenPomCache.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/cache/InMemoryMavenPomCache.java b/rewrite-maven/src/main/java/org/openrewrite/maven/cache/InMemoryMavenPomCache.java index 2b596b439a8..d7d7a827d93 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/cache/InMemoryMavenPomCache.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/cache/InMemoryMavenPomCache.java @@ -59,7 +59,8 @@ public InMemoryMavenPomCache() { ); } - public InMemoryMavenPomCache(Cache<ResolvedGroupArtifactVersion, Optional<Pom>> pomCache, + public InMemoryMavenPomCache(String name, + Cache<ResolvedGroupArtifactVersion, Optional<Pom>> pomCache, Cache<MetadataKey, Optional<MavenMetadata>> mavenMetadataCache, Cache<MavenRepository, Optional<MavenRepository>> repositoryCache, Cache<ResolvedGroupArtifactVersion, ResolvedPom> dependencyCache) { @@ -68,10 +69,17 @@ public InMemoryMavenPomCache(Cache<ResolvedGroupArtifactVersion, Optional<Pom>> this.repositoryCache = repositoryCache; this.dependencyCache = dependencyCache; - CaffeineCacheMetrics.monitor(Metrics.globalRegistry, pomCache, "Maven POMs"); - CaffeineCacheMetrics.monitor(Metrics.globalRegistry, mavenMetadataCache, "Maven metadata"); - CaffeineCacheMetrics.monitor(Metrics.globalRegistry, repositoryCache, "Maven repositories"); - CaffeineCacheMetrics.monitor(Metrics.globalRegistry, dependencyCache, "Resolved dependency POMs"); + CaffeineCacheMetrics.monitor(Metrics.globalRegistry, pomCache, "Maven POMs", "name", name); + CaffeineCacheMetrics.monitor(Metrics.globalRegistry, mavenMetadataCache, "Maven metadata", "name", name); + CaffeineCacheMetrics.monitor(Metrics.globalRegistry, repositoryCache, "Maven repositories", "name", name); + CaffeineCacheMetrics.monitor(Metrics.globalRegistry, dependencyCache, "Resolved dependency POMs", "name", name); + } + + public InMemoryMavenPomCache(Cache<ResolvedGroupArtifactVersion, Optional<Pom>> pomCache, + Cache<MetadataKey, Optional<MavenMetadata>> mavenMetadataCache, + Cache<MavenRepository, Optional<MavenRepository>> repositoryCache, + Cache<ResolvedGroupArtifactVersion, ResolvedPom> dependencyCache) { + this("default", pomCache, mavenMetadataCache, repositoryCache, dependencyCache); } @Nullable From f844227c4b1128fe549893b5f52a962693b00512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Carpenter=E2=84=A2=EF=B8=8F?= <kevin@moderne.io> Date: Tue, 12 Sep 2023 10:38:05 -0300 Subject: [PATCH 231/447] Change InMemoryMavenPomCache nickname's name and move it to the cache name --- .../openrewrite/maven/cache/InMemoryMavenPomCache.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/cache/InMemoryMavenPomCache.java b/rewrite-maven/src/main/java/org/openrewrite/maven/cache/InMemoryMavenPomCache.java index d7d7a827d93..7e996173cc3 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/cache/InMemoryMavenPomCache.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/cache/InMemoryMavenPomCache.java @@ -59,7 +59,7 @@ public InMemoryMavenPomCache() { ); } - public InMemoryMavenPomCache(String name, + public InMemoryMavenPomCache(String cacheNickname, Cache<ResolvedGroupArtifactVersion, Optional<Pom>> pomCache, Cache<MetadataKey, Optional<MavenMetadata>> mavenMetadataCache, Cache<MavenRepository, Optional<MavenRepository>> repositoryCache, @@ -69,10 +69,10 @@ public InMemoryMavenPomCache(String name, this.repositoryCache = repositoryCache; this.dependencyCache = dependencyCache; - CaffeineCacheMetrics.monitor(Metrics.globalRegistry, pomCache, "Maven POMs", "name", name); - CaffeineCacheMetrics.monitor(Metrics.globalRegistry, mavenMetadataCache, "Maven metadata", "name", name); - CaffeineCacheMetrics.monitor(Metrics.globalRegistry, repositoryCache, "Maven repositories", "name", name); - CaffeineCacheMetrics.monitor(Metrics.globalRegistry, dependencyCache, "Resolved dependency POMs", "name", name); + CaffeineCacheMetrics.monitor(Metrics.globalRegistry, pomCache, "Maven POMs - " + cacheNickname); + CaffeineCacheMetrics.monitor(Metrics.globalRegistry, mavenMetadataCache, "Maven metadata - " + cacheNickname); + CaffeineCacheMetrics.monitor(Metrics.globalRegistry, repositoryCache, "Maven repositories - " + cacheNickname); + CaffeineCacheMetrics.monitor(Metrics.globalRegistry, dependencyCache, "Resolved dependency POMs - " + cacheNickname); } public InMemoryMavenPomCache(Cache<ResolvedGroupArtifactVersion, Optional<Pom>> pomCache, From 457716941ff67e7ac33e7bee1564e1e55986e021 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 12 Sep 2023 18:48:02 -0700 Subject: [PATCH 232/447] Ensure that an edit which deletes a source file does not result in null being passed into the next recipe --- .../java/org/openrewrite/RecipeScheduler.java | 6 +- .../org/openrewrite/RecipeLifecycleTest.java | 101 ++++++------------ 2 files changed, 35 insertions(+), 72 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java b/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java index e6b1a78c01a..9ebd5808a5b 100644 --- a/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java +++ b/rewrite-core/src/main/java/org/openrewrite/RecipeScheduler.java @@ -141,7 +141,7 @@ static class RecipeRunCycle { public LargeSourceSet scanSources(LargeSourceSet sourceSet, int cycle) { return mapForRecipeRecursively(sourceSet, (recipeStack, sourceFile) -> { Recipe recipe = recipeStack.peek(); - if (recipe.maxCycles() < cycle) { + if (sourceFile == null || recipe.maxCycles() < cycle) { return sourceFile; } @@ -200,7 +200,7 @@ public LargeSourceSet generateSources(LargeSourceSet sourceSet, int cycle) { public LargeSourceSet editSources(LargeSourceSet sourceSet, int cycle) { return mapForRecipeRecursively(sourceSet, (recipeStack, sourceFile) -> { Recipe recipe = recipeStack.peek(); - if (recipe.maxCycles() < cycle) { + if (sourceFile == null || recipe.maxCycles() < cycle) { return sourceFile; } @@ -320,7 +320,7 @@ private SourceFile handleError(Recipe recipe, SourceFile sourceFile, @Nullable S return after; } - private LargeSourceSet mapForRecipeRecursively(LargeSourceSet sourceSet, BiFunction<Stack<Recipe>, SourceFile, SourceFile> mapFn) { + private LargeSourceSet mapForRecipeRecursively(LargeSourceSet sourceSet, BiFunction<Stack<Recipe>, @Nullable SourceFile, @Nullable SourceFile> mapFn) { return sourceSet.edit(sourceFile -> { Stack<Stack<Recipe>> allRecipesStack = initRecipeStack(); diff --git a/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java b/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java index 3e6cea62f00..a1695935274 100644 --- a/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java @@ -15,7 +15,8 @@ */ package org.openrewrite; -import lombok.NoArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Value; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; import org.openrewrite.config.Environment; @@ -24,6 +25,7 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.Markers; import org.openrewrite.test.RewriteTest; +import org.openrewrite.text.FindAndReplace; import org.openrewrite.text.PlainText; import org.openrewrite.text.PlainTextVisitor; @@ -32,10 +34,9 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; -import java.util.Properties; -import java.util.UUID; +import java.util.*; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.openrewrite.Recipe.noop; @@ -78,20 +79,33 @@ void generateFile() { ); } + @Value + @EqualsAndHashCode(callSuper = true) + static class DeleteFirst extends Recipe { + + @Override + public String getDisplayName() { + return "Delete a file"; + } + + @Override + public String getDescription() { + return "Deletes a file early on in the recipe pipeline. " + + "Subsequent recipes should not be passed a null source file."; + } + + @Override + public List<Recipe> getRecipeList() { + return Arrays.asList( + new DeleteSourceFiles("test.txt"), + new FindAndReplace("test", "", null, null, null, null, null)); + } + } + @Test - void deleteFile() { + void deletionWithSubsequentRecipes() { rewriteRun( - spec -> spec - .recipe(toRecipe(() -> new TreeVisitor<>() { - @Override - public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { - return null; - } - }).withName("test.DeletingRecipe") - ) - .afterRecipe(run -> assertThat(run.getChangeset().getAllResults().stream() - .map(r -> r.getRecipeDescriptorsThatMadeChanges().get(0).getName())) - .containsOnly("test.DeletingRecipe")), + spec -> spec.recipe(new DeleteFirst()), text("test", null, spec -> spec.path("test.txt")) ); } @@ -382,61 +396,10 @@ void declarativeRecipeChainFromResourcesIncludesImperativeRecipesInDescriptors() @Test void declarativeRecipeChainFromResourcesLongList() { rewriteRun(spec -> spec.recipe(Environment.builder() - .load(new YamlResourceLoader(RecipeLifecycleTest.class.getResourceAsStream("/META-INF/rewrite/test-sample-a.yml"), URI.create("rewrite.yml"), new Properties())) - .load(new YamlResourceLoader(RecipeLifecycleTest.class.getResourceAsStream("/META-INF/rewrite/test-sample-b.yml"), URI.create("rewrite.yml"), new Properties())) + .load(new YamlResourceLoader(requireNonNull(RecipeLifecycleTest.class.getResourceAsStream("/META-INF/rewrite/test-sample-a.yml")), URI.create("rewrite.yml"), new Properties())) + .load(new YamlResourceLoader(requireNonNull(RecipeLifecycleTest.class.getResourceAsStream("/META-INF/rewrite/test-sample-b.yml")), URI.create("rewrite.yml"), new Properties())) .build() .activateRecipes("test.declarative.sample.a")), text("Hi", "after")); } } - -class DefaultConstructorRecipe extends Recipe { - @Override - public String getDisplayName() { - return "DefaultConstructorRecipe"; - } - - @Override - public String getDescription() { - return "DefaultConstructorRecipe."; - } - - @Override - public TreeVisitor<?, ExecutionContext> getVisitor() { - return new PlainTextVisitor<>() { - @Override - public PlainText visitText(PlainText text, ExecutionContext executionContext) { - if (!text.getText().contains(getDisplayName())) { - return text.withText(getDisplayName() + text.getText()); - } - return super.visitText(text, executionContext); - } - }; - } -} - -@NoArgsConstructor -class NoArgRecipe extends Recipe { - @Override - public String getDisplayName() { - return "NoArgRecipe"; - } - - @Override - public String getDescription() { - return "NoArgRecipe."; - } - - @Override - public TreeVisitor<?, ExecutionContext> getVisitor() { - return new PlainTextVisitor<>() { - @Override - public PlainText visitText(PlainText text, ExecutionContext executionContext) { - if (!text.getText().contains(getDisplayName())) { - return text.withText(getDisplayName() + text.getText()); - } - return super.visitText(text, executionContext); - } - }; - } -} \ No newline at end of file From fab92f59e4b98786fffdcd0814ff39cd47bd5605 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 12 Sep 2023 19:19:45 -0700 Subject: [PATCH 233/447] Bring back "unused" classes which were actually used --- .../org/openrewrite/RecipeLifecycleTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java b/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java index a1695935274..4f239cff2b4 100644 --- a/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java @@ -16,6 +16,7 @@ package org.openrewrite; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import lombok.Value; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; @@ -403,3 +404,56 @@ void declarativeRecipeChainFromResourcesLongList() { text("Hi", "after")); } } + +@SuppressWarnings("unused") // referenced in yaml +class DefaultConstructorRecipe extends Recipe { + @Override + public String getDisplayName() { + return "DefaultConstructorRecipe"; + } + + @Override + public String getDescription() { + return "DefaultConstructorRecipe."; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new PlainTextVisitor<>() { + @Override + public PlainText visitText(PlainText text, ExecutionContext executionContext) { + if (!text.getText().contains(getDisplayName())) { + return text.withText(getDisplayName() + text.getText()); + } + return super.visitText(text, executionContext); + } + }; + } +} + +@SuppressWarnings("unused") // referenced in yaml +@NoArgsConstructor +class NoArgRecipe extends Recipe { + @Override + public String getDisplayName() { + return "NoArgRecipe"; + } + + @Override + public String getDescription() { + return "NoArgRecipe."; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new PlainTextVisitor<>() { + @Override + public PlainText visitText(PlainText text, ExecutionContext executionContext) { + if (!text.getText().contains(getDisplayName())) { + return text.withText(getDisplayName() + text.getText()); + } + return super.visitText(text, executionContext); + } + }; + } +} From fd27b0e4da6ca362c758621f8dc52fba8ac19638 Mon Sep 17 00:00:00 2001 From: Peter Streef <p.streef@gmail.com> Date: Wed, 13 Sep 2023 18:32:37 +0200 Subject: [PATCH 234/447] Add 409 and other non retryable status codes to isClientSideException() (#3542) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add 409 and other non retryable status codes to isClientSideException() to add the responses as empty in maven pom cache * Simplify client-side error method * Update ordering of conditions to be easier to read Co-authored-by: Tim te Beek <tim@moderne.io> --------- Co-authored-by: Kevin Carpenter™️ <kevin@moderne.io> Co-authored-by: Tim te Beek <tim@moderne.io> --- .../maven/internal/MavenPomDownloader.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index 18b4a2c9ef7..c0217d1a360 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -89,7 +89,7 @@ public class MavenPomDownloader { /** * @param projectPoms Other POMs in this project. - * @param ctx The execution context, which potentially contain Maven settings customization and + * @param ctx The execution context, which potentially contain Maven settings customization * and {@link HttpSender} customization. * @param mavenSettings The Maven settings to use, if any. This argument overrides any Maven settings * set on the execution context. @@ -486,6 +486,7 @@ public Pom download(GroupArtifactVersion gav, gav = handleSnapshotTimestampVersion(gav); for (MavenRepository repo : normalizedRepos) { + //noinspection DataFlowIssue if (!repositoryAcceptsVersion(repo, gav.getVersion(), containingPom)) { continue; } @@ -495,7 +496,7 @@ public Pom download(GroupArtifactVersion gav, Optional<Pom> result = mavenCache.getPom(resolvedGav); if (result == null) { URI uri = URI.create(repo.getUri() + (repo.getUri().endsWith("/") ? "" : "/") + - gav.getGroupId().replace('.', '/') + '/' + + requireNonNull(gav.getGroupId()).replace('.', '/') + '/' + gav.getArtifactId() + '/' + gav.getVersion() + '/' + gav.getArtifactId() + '-' + versionMaybeDatedSnapshot + ".pom"); @@ -576,7 +577,7 @@ public Pom download(GroupArtifactVersion gav, * Gets the base version from snapshot timestamp version. */ private GroupArtifactVersion handleSnapshotTimestampVersion(GroupArtifactVersion gav) { - Matcher m = SNAPSHOT_TIMESTAMP.matcher(gav.getVersion()); + Matcher m = SNAPSHOT_TIMESTAMP.matcher(requireNonNull(gav.getVersion())); if (m.matches()) { final String baseVersion; if (m.group(1) != null) { @@ -816,8 +817,17 @@ public HttpSenderResponseException(@Nullable Throwable cause, @Nullable Integer this.responseCode = responseCode; } + /** + * All 400s are considered client-side exceptions, but we only want to cache ones that are unlikely to change + * if requested again in order to save on time spent making HTTP calls. + * <br/> + * For 408 TIMEOUT, 425 TOO_EARLY, and 429 TOO_MANY_REQUESTS, these are likely to change if not cached. + */ public boolean isClientSideException() { - return responseCode != null && responseCode >= 400 && responseCode <= 404; + if (responseCode == null || responseCode < 400 || responseCode > 499) { + return false; + } + return responseCode != 408 && responseCode != 425 && responseCode != 429; } public String getMessage() { @@ -827,7 +837,7 @@ public String getMessage() { } public boolean isAccessDenied() { - return isClientSideException() && responseCode != 404; + return responseCode != null && 400 < responseCode && responseCode <= 403; } } From 1e509d96e573b5a8ddbe10c5a06918a08ccc833e Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Wed, 13 Sep 2023 20:07:44 +0200 Subject: [PATCH 235/447] Prevent NPE on namedStyle.styles.iterator() (#3541) --- .../org/openrewrite/style/NamedStyles.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/style/NamedStyles.java b/rewrite-core/src/main/java/org/openrewrite/style/NamedStyles.java index 35f167c0309..a0d7a1cb5ef 100644 --- a/rewrite-core/src/main/java/org/openrewrite/style/NamedStyles.java +++ b/rewrite-core/src/main/java/org/openrewrite/style/NamedStyles.java @@ -56,13 +56,16 @@ public static <S extends Style> S merge(Class<S> styleClass, Iterable<? extends NamedStyles> namedStyles) { S merged = null; for (NamedStyles namedStyle : namedStyles) { - for (Style style : namedStyle.styles) { - if (styleClass.isInstance(style)) { - style = style.applyDefaults(); - if (merged == null) { - merged = (S) style; - } else { - merged = (S) merged.merge(style); + Collection<Style> styles = namedStyle.styles; + if (styles != null) { + for (Style style : styles) { + if (styleClass.isInstance(style)) { + style = style.applyDefaults(); + if (merged == null) { + merged = (S) style; + } else { + merged = (S) merged.merge(style); + } } } } @@ -78,20 +81,20 @@ public static <S extends Style> S merge(Class<S> styleClass, */ @Nullable public static NamedStyles merge(List<NamedStyles> styles) { - if(styles.isEmpty()) { + if (styles.isEmpty()) { return null; - } else if(styles.size() == 1) { + } else if (styles.size() == 1) { return styles.get(0); } Set<Class<? extends Style>> styleClasses = new HashSet<>(); - for(NamedStyles namedStyles : styles) { - for(Style style : namedStyles.getStyles()) { + for (NamedStyles namedStyles : styles) { + for (Style style : namedStyles.getStyles()) { styleClasses.add(style.getClass()); } } List<Style> mergedStyles = new ArrayList<>(styleClasses.size()); - for(Class<? extends Style> styleClass : styleClasses) { + for (Class<? extends Style> styleClass : styleClasses) { mergedStyles.add(NamedStyles.merge(styleClass, styles)); } From 65ce5b37eb5765799f60be7f88e9b93ecc05f433 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 13 Sep 2023 12:51:04 -0700 Subject: [PATCH 236/447] Allow successive find and replace operations on the same file --- .../org/openrewrite/text/FindAndReplace.java | 16 +++++-- .../openrewrite/text/FindAndReplaceTest.java | 43 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java index 3ab12e37588..03aaf0eebbe 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java @@ -26,6 +26,7 @@ import org.openrewrite.remote.Remote; import java.util.Arrays; +import java.util.Objects; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -98,13 +99,15 @@ public String getDescription() { /** - * Ensure that a file is not find-and-replaced twice in the same recipe run. + * Ensure that the same replacement is not applied to the same file more than once per recipe run. * Used to avoid the situation where replacing "a" with "ab" results in something like "abb". */ @Value @With static class AlreadyReplaced implements Marker { UUID id; + String find; + String replace; } @Override @@ -116,8 +119,13 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (sourceFile instanceof Quark || sourceFile instanceof Remote || sourceFile instanceof Binary) { return sourceFile; } - if(sourceFile.getMarkers().findFirst(AlreadyReplaced.class).isPresent()) { - return sourceFile; + for (Marker marker : sourceFile.getMarkers().getMarkers()) { + if(marker instanceof AlreadyReplaced) { + AlreadyReplaced alreadyReplaced = (AlreadyReplaced) marker; + if(Objects.equals(find, alreadyReplaced.getFind()) && Objects.equals(replace, alreadyReplaced.getReplace())) { + return sourceFile; + } + } } String searchStr = find; if (!Boolean.TRUE.equals(regex)) { @@ -142,7 +150,7 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { } String newText = matcher.replaceAll(replace); return plainText.withText(newText) - .withMarkers(sourceFile.getMarkers().add(new AlreadyReplaced(randomId()))); + .withMarkers(sourceFile.getMarkers().add(new AlreadyReplaced(randomId(), find, replace))); } }; //noinspection DuplicatedCode diff --git a/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java b/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java index fbfcc0379fe..460445645c3 100644 --- a/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java @@ -15,10 +15,16 @@ */ package org.openrewrite.text; +import lombok.EqualsAndHashCode; +import lombok.Value; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Recipe; import org.openrewrite.test.RewriteTest; +import java.util.Arrays; +import java.util.List; + import static org.openrewrite.test.SourceSpecs.text; class FindAndReplaceTest implements RewriteTest { @@ -92,4 +98,41 @@ void noRecursive() { text("test", "tested") ); } + + @Value + @EqualsAndHashCode(callSuper = true) + static class MultiFindAndReplace extends Recipe { + + @Override + public String getDisplayName() { + return "Replaces \"one\" with \"two\" then \"three\" then \"four\""; + } + + @Override + public String getDescription() { + return "Replaces \"one\" with \"two\" then \"three\" then \"four\"."; + } + + @Override + public List<Recipe> getRecipeList() { + return Arrays.asList( + new FindAndReplace("one", "two", null, null, null, null, null), + new FindAndReplace("two", "three", null, null, null, null, null), + new FindAndReplace("three", "four", null, null, null, null, null)); + } + } + @Test + void successiveReplacement() { + rewriteRun( + spec -> spec.recipe(new MultiFindAndReplace()), + text( + """ + one + """, + """ + four + """ + ) + ); + } } From 45946c2e127000b7ad53da2157f439f2f80676e7 Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Wed, 13 Sep 2023 12:52:18 -0700 Subject: [PATCH 237/447] bump org.eclipse.jgit to 6.7.+ to fix dependency vulnerability report. --- rewrite-benchmarks/build.gradle.kts | 2 +- rewrite-core/build.gradle.kts | 4 ++-- rewrite-java-test/build.gradle.kts | 2 +- rewrite-test/build.gradle.kts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rewrite-benchmarks/build.gradle.kts b/rewrite-benchmarks/build.gradle.kts index 195fa0a4688..53b92ba0e25 100644 --- a/rewrite-benchmarks/build.gradle.kts +++ b/rewrite-benchmarks/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } dependencies { - jmh("org.eclipse.jgit:org.eclipse.jgit:5.13.+") + jmh("org.eclipse.jgit:org.eclipse.jgit:6.7.+") jmh("com.google.code.findbugs:jsr305:latest.release") jmh("org.projectlombok:lombok:latest.release") diff --git a/rewrite-core/build.gradle.kts b/rewrite-core/build.gradle.kts index 1616491549a..772636d5eac 100644 --- a/rewrite-core/build.gradle.kts +++ b/rewrite-core/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } dependencies { - compileOnly("org.eclipse.jgit:org.eclipse.jgit:5.13.+") + compileOnly("org.eclipse.jgit:org.eclipse.jgit:6.7.+") implementation("org.openrewrite.tools:java-object-diff:latest.release") @@ -28,7 +28,7 @@ dependencies { implementation("org.yaml:snakeyaml:latest.release") testImplementation(project(":rewrite-test")) - testImplementation("org.eclipse.jgit:org.eclipse.jgit:5.13.+") + testImplementation("org.eclipse.jgit:org.eclipse.jgit:6.7.+") } val shadowJar = tasks.named<ShadowJar>("shadowJar") { diff --git a/rewrite-java-test/build.gradle.kts b/rewrite-java-test/build.gradle.kts index 39e9e9d99f1..44034573d8a 100644 --- a/rewrite-java-test/build.gradle.kts +++ b/rewrite-java-test/build.gradle.kts @@ -20,7 +20,7 @@ dependencies { testRuntimeOnly("org.apache.commons:commons-lang3:latest.release") testRuntimeOnly(project(":rewrite-yaml")) testImplementation(project(":rewrite-maven")) - testRuntimeOnly("org.eclipse.jgit:org.eclipse.jgit:5.13.+") + testRuntimeOnly("org.eclipse.jgit:org.eclipse.jgit:6.7.+") } tasks.withType<Javadoc> { diff --git a/rewrite-test/build.gradle.kts b/rewrite-test/build.gradle.kts index e7f48222158..d73644a3884 100644 --- a/rewrite-test/build.gradle.kts +++ b/rewrite-test/build.gradle.kts @@ -11,5 +11,5 @@ dependencies { implementation("org.assertj:assertj-core:latest.release") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv") implementation("org.slf4j:slf4j-api:1.7.36") - implementation("org.eclipse.jgit:org.eclipse.jgit:5.13.+") + implementation("org.eclipse.jgit:org.eclipse.jgit:6.7.+") } From 9c5f9f99e1021ceed1cbf60e8b069360df9b103b Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Wed, 13 Sep 2023 12:58:38 -0700 Subject: [PATCH 238/447] Revert "bump org.eclipse.jgit to 6.7.+ to fix dependency vulnerability report." This reverts commit 45946c2e127000b7ad53da2157f439f2f80676e7. --- rewrite-benchmarks/build.gradle.kts | 2 +- rewrite-core/build.gradle.kts | 4 ++-- rewrite-java-test/build.gradle.kts | 2 +- rewrite-test/build.gradle.kts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rewrite-benchmarks/build.gradle.kts b/rewrite-benchmarks/build.gradle.kts index 53b92ba0e25..195fa0a4688 100644 --- a/rewrite-benchmarks/build.gradle.kts +++ b/rewrite-benchmarks/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } dependencies { - jmh("org.eclipse.jgit:org.eclipse.jgit:6.7.+") + jmh("org.eclipse.jgit:org.eclipse.jgit:5.13.+") jmh("com.google.code.findbugs:jsr305:latest.release") jmh("org.projectlombok:lombok:latest.release") diff --git a/rewrite-core/build.gradle.kts b/rewrite-core/build.gradle.kts index 772636d5eac..1616491549a 100644 --- a/rewrite-core/build.gradle.kts +++ b/rewrite-core/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } dependencies { - compileOnly("org.eclipse.jgit:org.eclipse.jgit:6.7.+") + compileOnly("org.eclipse.jgit:org.eclipse.jgit:5.13.+") implementation("org.openrewrite.tools:java-object-diff:latest.release") @@ -28,7 +28,7 @@ dependencies { implementation("org.yaml:snakeyaml:latest.release") testImplementation(project(":rewrite-test")) - testImplementation("org.eclipse.jgit:org.eclipse.jgit:6.7.+") + testImplementation("org.eclipse.jgit:org.eclipse.jgit:5.13.+") } val shadowJar = tasks.named<ShadowJar>("shadowJar") { diff --git a/rewrite-java-test/build.gradle.kts b/rewrite-java-test/build.gradle.kts index 44034573d8a..39e9e9d99f1 100644 --- a/rewrite-java-test/build.gradle.kts +++ b/rewrite-java-test/build.gradle.kts @@ -20,7 +20,7 @@ dependencies { testRuntimeOnly("org.apache.commons:commons-lang3:latest.release") testRuntimeOnly(project(":rewrite-yaml")) testImplementation(project(":rewrite-maven")) - testRuntimeOnly("org.eclipse.jgit:org.eclipse.jgit:6.7.+") + testRuntimeOnly("org.eclipse.jgit:org.eclipse.jgit:5.13.+") } tasks.withType<Javadoc> { diff --git a/rewrite-test/build.gradle.kts b/rewrite-test/build.gradle.kts index d73644a3884..e7f48222158 100644 --- a/rewrite-test/build.gradle.kts +++ b/rewrite-test/build.gradle.kts @@ -11,5 +11,5 @@ dependencies { implementation("org.assertj:assertj-core:latest.release") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv") implementation("org.slf4j:slf4j-api:1.7.36") - implementation("org.eclipse.jgit:org.eclipse.jgit:6.7.+") + implementation("org.eclipse.jgit:org.eclipse.jgit:5.13.+") } From 59e4634e60c38787a93f28548f708c9a8bd737e4 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 14 Sep 2023 01:45:27 +0200 Subject: [PATCH 239/447] Precompile `MavenRepositoryMirror` matching logic (#3543) --- .../maven/tree/MavenRepositoryMirror.java | 74 ++++++++++++------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java index dd6bc0e82e4..83ad7253cbd 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java @@ -28,8 +28,10 @@ @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Data public class MavenRepositoryMirror { + @Nullable String id; + @Nullable String url; /** @@ -42,6 +44,7 @@ public class MavenRepositoryMirror { * <p> * See: https://maven.apache.org/guides/mini/guide-mirror-settings.html#advanced-mirror-specification */ + @Nullable String mirrorOf; @Nullable @@ -50,6 +53,46 @@ public class MavenRepositoryMirror { @Nullable Boolean snapshots; + private final boolean externalOnly; + private final List<String> mirrorsOf; + private final Set<String> excludedRepos; + private final Set<String> includedRepos; + + public MavenRepositoryMirror(@Nullable String id, @Nullable String url, @Nullable String mirrorOf, @Nullable Boolean releases, @Nullable Boolean snapshots) { + this.id = id; + this.url = url; + this.mirrorOf = mirrorOf; + this.releases = releases; + this.snapshots = snapshots; + + if (mirrorOf != null) { + int colonIndex = mirrorOf.indexOf(':'); + String mirrorOfWithoutExternal; + if (colonIndex == -1) { + mirrorOfWithoutExternal = mirrorOf; + externalOnly = false; + } else { + externalOnly = true; + mirrorOfWithoutExternal = mirrorOf.substring(colonIndex + 1); + } + mirrorsOf = Arrays.stream(mirrorOfWithoutExternal.split(",")).collect(Collectors.toList()); + excludedRepos = new HashSet<>(); + includedRepos = new HashSet<>(); + for (String mirror : mirrorsOf) { + if (mirror.startsWith("!")) { + excludedRepos.add(mirror.substring(1)); + } else { + includedRepos.add(mirror); + } + } + } else { + externalOnly = false; + mirrorsOf = null; + includedRepos = null; + excludedRepos = null; + } + } + public static MavenRepository apply(Collection<MavenRepositoryMirror> mirrors, MavenRepository repo) { MavenRepository mapped = repo; MavenRepository next = null; @@ -63,13 +106,13 @@ public static MavenRepository apply(Collection<MavenRepositoryMirror> mirrors, M } public MavenRepository apply(MavenRepository repo) { - if (matches(repo) && !(repo.getUri().equals(url) && id.equals(repo.getId()))) { + if (repo.getUri().equals(url) && Objects.equals(id, repo.getId()) || !matches(repo)) { + return repo; + } else { return repo.withUri(url) .withId(id) .withReleases(!Boolean.FALSE.equals(releases) ? "true" : "false") .withSnapshots(!Boolean.FALSE.equals(snapshots) ? "true" : "false"); - } else { - return repo; } } @@ -81,40 +124,21 @@ public boolean matches(MavenRepository repository) { return true; } - int colonIndex = mirrorOf.indexOf(':'); - String mirrorOfWithoutExternal = mirrorOf; - boolean externalOnly = false; - if (colonIndex != -1) { - externalOnly = true; - mirrorOfWithoutExternal = mirrorOf.substring(colonIndex + 1); - } - - List<String> mirrorsOf = Arrays.stream(mirrorOfWithoutExternal.split(",")).collect(Collectors.toList()); - Set<String> excludedRepos = new HashSet<>(); - Set<String> includedRepos = new HashSet<>(); - for (String mirror : mirrorsOf) { - if (mirror.startsWith("!")) { - excludedRepos.add(mirror.substring(1)); - } else { - includedRepos.add(mirror); - } - } - if (externalOnly && isInternal(repository)) { return false; } // Named inclusion/exclusion beats wildcard inclusion/exclusion - if (excludedRepos.stream().anyMatch(it -> "*".equals(it))) { + if (excludedRepos.contains("*")) { return includedRepos.contains(repository.getId()); } - if (includedRepos.stream().anyMatch(it -> "*".equals(it))) { + if (includedRepos.contains("*")) { return !excludedRepos.contains(repository.getId()); } return !excludedRepos.contains(repository.getId()) && includedRepos.contains(repository.getId()); } private boolean isInternal(MavenRepository repo) { - if (repo.getUri().toLowerCase().startsWith("file:")) { + if (repo.getUri().regionMatches(true, 0,"file:", 0, 5)) { return true; } try { From 28b8ae57bb22365b7daa69379aa7a592170dc193 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 14 Sep 2023 09:46:50 +0200 Subject: [PATCH 240/447] No recursive application of Maven mirrors (#3545) * No recursive application of Maven mirrors As [documented](https://maven.apache.org/guides/mini/guide-mirror-settings.html) (and verified by manual testing) it is always the first matching Maven mirror that should get selected, when finding a mirror for a particular repository. I.e. the mirrors should not be applied recursively. Additionally, this commit will use the precomputed mirrors (from the `ExecutionContext`) whenever possible, as computing the mirrors is somewhat expensive. * Add test case --- .../maven/MavenExecutionContextView.java | 4 +- .../maven/tree/MavenRepositoryMirror.java | 12 +++--- .../maven/tree/MavenRepositoryMirrorTest.java | 37 +++++++++++++++++++ 3 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/tree/MavenRepositoryMirrorTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenExecutionContextView.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenExecutionContextView.java index b3652bd8b6d..7589fa767ad 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenExecutionContextView.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenExecutionContextView.java @@ -90,10 +90,10 @@ public Collection<MavenRepositoryMirror> getMirrors() { * @return The mirrors to use for dependency resolution. */ public Collection<MavenRepositoryMirror> getMirrors(@Nullable MavenSettings mavenSettings) { - if (mavenSettings != null) { + if (mavenSettings != null && !mavenSettings.equals(getSettings())) { return mapMirrors(mavenSettings); } - return getMessage(MAVEN_MIRRORS, emptyList()); + return getMirrors(); } public MavenExecutionContextView setCredentials(Collection<MavenRepositoryCredentials> credentials) { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java index 83ad7253cbd..7742bb98b34 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java @@ -94,15 +94,13 @@ public MavenRepositoryMirror(@Nullable String id, @Nullable String url, @Nullabl } public static MavenRepository apply(Collection<MavenRepositoryMirror> mirrors, MavenRepository repo) { - MavenRepository mapped = repo; - MavenRepository next = null; - while (next != mapped) { - next = mapped; - for (MavenRepositoryMirror mirror : mirrors) { - mapped = mirror.apply(mapped); + for (MavenRepositoryMirror mirror : mirrors) { + MavenRepository mapped = mirror.apply(repo); + if (mapped != repo) { + return mapped; } } - return mapped; + return repo; } public MavenRepository apply(MavenRepository repo) { diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/tree/MavenRepositoryMirrorTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/MavenRepositoryMirrorTest.java new file mode 100644 index 00000000000..c7fef1bd637 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/MavenRepositoryMirrorTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.tree; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class MavenRepositoryMirrorTest { + + MavenRepositoryMirror one = new MavenRepositoryMirror("one", "https://one.org/m2", "*", true, true); + MavenRepositoryMirror two = new MavenRepositoryMirror("two", "https://two.org/m2", "*", true, true); + MavenRepository foo = new MavenRepository("foo", "https://foo.org/m2", "true", "true", null, null); + + @Test + void useFirstMirror() { + assertThat(MavenRepositoryMirror.apply(List.of(one, two), foo)).satisfies(repo -> { + assertThat(repo.getId()).isEqualTo(one.getId()); + assertThat(repo.getUri()).isEqualTo(one.getUrl()); + }); + } +} \ No newline at end of file From 2ef36317aeca26f274c15d8ce8fa53274bb863f3 Mon Sep 17 00:00:00 2001 From: Valentin Delaye <jonesbusy@users.noreply.github.com> Date: Thu, 14 Sep 2023 10:29:28 +0200 Subject: [PATCH 241/447] Fix issue with duplicate `relativePath` on `ChangeParentPom` (#3544) * Fix issue with duplicate relativePath tag and increase coverage for relative path and change maven child module * Apply suggestions from code review Co-authored-by: Tim te Beek <timtebeek@gmail.com> --------- Co-authored-by: Tim te Beek <timtebeek@gmail.com> --- .../openrewrite/maven/ChangeParentPom.java | 7 +- .../maven/MavenTagInsertionComparator.java | 1 + .../maven/ChangeParentPomTest.java | 173 ++++++++++++++++++ 3 files changed, 179 insertions(+), 2 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java index 9cd4e38766c..1469ce10447 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java @@ -205,12 +205,15 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { changeParentTagVisitors.add(new ChangeTagValueVisitor<>(t.getChild("relativePath").get(), targetRelativePath)); } else if (tag.getChildValue("relativePath").orElse(null) == null && targetRelativePath != null) { + final Xml.Tag relativePathTag; if (StringUtils.isBlank(targetRelativePath)) { - changeParentTagVisitors.add(new AddToTagVisitor<>(t, Xml.Tag.build("<relativePath />"))); + relativePathTag = Xml.Tag.build("<relativePath />"); } else { - changeParentTagVisitors.add(new AddToTagVisitor<>(t, Xml.Tag.build("<relativePath>" + targetRelativePath + "</relativePath>"))); + relativePathTag = Xml.Tag.build("<relativePath>" + targetRelativePath + "</relativePath>"); } + doAfterVisit(new AddToTagVisitor<>(t, relativePathTag, new MavenTagInsertionComparator(t.getChildren()))); + maybeUpdateModel(); } if (changeParentTagVisitors.size() > 0) { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenTagInsertionComparator.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenTagInsertionComparator.java index dc663fe21bb..52ef101ceea 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenTagInsertionComparator.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenTagInsertionComparator.java @@ -36,6 +36,7 @@ public class MavenTagInsertionComparator implements Comparator<Content> { "groupId", "artifactId", "version", + "relativePath", "packaging", "name", "description", diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java index 21e8ea19f7f..ff3ffbc209a 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeParentPomTest.java @@ -236,6 +236,179 @@ void changeParentAddRelativePathNonEmptyValue() { ); } + @RepeatedTest(10) + void multiModuleRelativePath() { + ChangeParentPom recipe = new ChangeParentPom("org.springframework.boot", null, "spring-boot-starter-parent", null, "2.6.7", null, "", null, false, null); + rewriteRun( + spec -> spec.recipe(recipe), + mavenProject("parent", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.5.0</version> + </parent> + + <modules> + <module>module1</module> + <module>module2</module> + </modules> + </project> + """, + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.6.7</version> + <relativePath /> + </parent> + + <modules> + <module>module1</module> + <module>module2</module> + </modules> + </project> + """ + ), + mavenProject("module1", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + </parent> + <artifactId>module1</artifactId> + </project> + """ + )), + mavenProject("module2", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + </parent> + <artifactId>module2</artifactId> + </project> + """ + ) + ) + ) + ); + } + + @RepeatedTest(10) + void multiModuleRelativePathChangeChildrens() { + ChangeParentPom recipe = new ChangeParentPom("org.sample", "org.springframework.boot", "sample", "spring-boot-starter-parent", "2.5.0", null, "", null, true, null); + rewriteRun( + spec -> spec.recipe(recipe), + mavenProject("parent", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.5.0</version> + </parent> + + <modules> + <module>module1</module> + <module>module2</module> + </modules> + </project> + """ + ), + mavenProject("module1", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + </parent> + <artifactId>module1</artifactId> + </project> + """, + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.5.0</version> + <relativePath /> + </parent> + <artifactId>module1</artifactId> + </project> + """ + )), + mavenProject("module2", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + </parent> + <artifactId>module2</artifactId> + </project> + """, + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.5.0</version> + <relativePath /> + </parent> + <artifactId>module2</artifactId> + </project> + """ + ) + ) + ) + ); +} + @Test void upgradeVersion() { rewriteRun( From de8bc15be8847027e197ad4ef73df528cc5a3f7e Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Thu, 14 Sep 2023 10:33:41 +0200 Subject: [PATCH 242/447] Add test to guard against future regressions Fixes https://github.com/openrewrite/rewrite/issues/3532 Failed in previous versions, but not anymore. By adding this test now we ensure it keeps working. --- .../text/FindAndReplaceJavaTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/text/FindAndReplaceJavaTest.java b/rewrite-java-test/src/test/java/org/openrewrite/text/FindAndReplaceJavaTest.java index 35482cf7397..0042ac1bb9e 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/text/FindAndReplaceJavaTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/text/FindAndReplaceJavaTest.java @@ -17,9 +17,11 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.SourceSpecs.text; class FindAndReplaceJavaTest implements RewriteTest { @@ -34,4 +36,27 @@ void findAndReplaceJava() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3532") + void filePatternShouldLimitApplication() { + rewriteRun( + spec -> spec.recipeFromYaml(""" + type: specs.openrewrite.org/v1beta/recipe + name: com.yourorg.FindAndReplaceExample + displayName: Find and replace example + recipeList: + - org.openrewrite.text.FindAndReplace: + find: blacklist + replace: denylist + filePattern: '**/*.java' + """, + "com.yourorg.FindAndReplaceExample"), + java( + "class blacklist {}", + "class denylist {}" + ), + text("See `class blacklist {}`") + ); + } } From abf6339c0b26e0c40455c3de7bcd41d56471f339 Mon Sep 17 00:00:00 2001 From: Daniel Wallman <daniel.wallman@m.co> Date: Thu, 14 Sep 2023 15:35:12 +0200 Subject: [PATCH 243/447] AddPlugin changes; make `version` optional and add `filePattern` (#3538) * make version optional for org.openrewrite.maven.AddPlugin * add filePattern * also call super.isAcceptable() * Apply suggestions from code review --------- Co-authored-by: Tim te Beek <timtebeek@gmail.com> --- .../java/org/openrewrite/maven/AddPlugin.java | 28 +++++- .../org/openrewrite/maven/AddPluginTest.java | 88 ++++++++++++++++++- 2 files changed, 109 insertions(+), 7 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddPlugin.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddPlugin.java index c82ad1dd2f8..3a68ed4afe4 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddPlugin.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddPlugin.java @@ -20,7 +20,9 @@ import org.intellij.lang.annotations.Language; import org.openrewrite.ExecutionContext; import org.openrewrite.Option; +import org.openrewrite.PathUtils; import org.openrewrite.Recipe; +import org.openrewrite.SourceFile; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.xml.AddToTagVisitor; @@ -48,7 +50,9 @@ public class AddPlugin extends Recipe { @Option(displayName = "Version", description = "A fixed version of the plugin to add.", - example = "1.0.0") + example = "1.0.0", + required = false) + @Nullable String version; @Language("xml") @@ -73,6 +77,16 @@ public class AddPlugin extends Recipe { @Nullable String executions; + @Option(displayName = "File pattern", + description = "A glob expression that can be used to constrain which directories or source files should be searched. " + + "Multiple patterns may be specified, separated by a semicolon `;`. " + + "If multiple patterns are supplied any of the patterns matching will be interpreted as a match. " + + "When not set, all source files are searched. ", + required = false, + example = "**/*-parent/grpc-*/pom.xml") + @Nullable + String filePattern; + @Override public String getDisplayName() { return "Add Maven plugin"; @@ -90,6 +104,14 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { private class AddPluginVisitor extends MavenIsoVisitor<ExecutionContext> { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionContext) { + if (filePattern != null) { + return PathUtils.matchesGlob(sourceFile.getSourcePath(), filePattern) && super.isAcceptable(sourceFile, executionContext); + } + return super.isAcceptable(sourceFile, executionContext); + } + @Override public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { Xml.Tag root = document.getRoot(); @@ -125,7 +147,7 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { if (maybePlugin.isPresent()) { Xml.Tag plugin = maybePlugin.get(); - if (!version.equals(plugin.getChildValue("version").orElse(null))) { + if (version != null && !version.equals(plugin.getChildValue("version").orElse(null))) { //noinspection OptionalGetWithoutIsPresent t = (Xml.Tag) new ChangeTagValueVisitor<>(plugin.getChild("version").get(), version).visitNonNull(t, ctx, getCursor().getParentOrThrow()); } @@ -133,7 +155,7 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { Xml.Tag pluginTag = Xml.Tag.build("<plugin>\n" + "<groupId>" + groupId + "</groupId>\n" + "<artifactId>" + artifactId + "</artifactId>\n" + - "<version>" + version + "</version>\n" + + (version != null ? "<version>" + version + "</version>\n" : "") + (executions != null ? executions.trim() + "\n" : "") + (configuration != null ? configuration.trim() + "\n" : "") + (dependencies != null ? dependencies.trim() + "\n" : "") + diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddPluginTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddPluginTest.java index 54482d5d856..33cfaba9069 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/AddPluginTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddPluginTest.java @@ -26,7 +26,7 @@ public class AddPluginTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new AddPlugin("org.openrewrite.maven", "rewrite-maven-plugin", "100.0", null, null, null)); + spec.recipe(new AddPlugin("org.openrewrite.maven", "rewrite-maven-plugin", "100.0", null, null, null, null)); } @DocumentExample @@ -35,7 +35,7 @@ void addPluginWithConfiguration() { rewriteRun( spec -> spec.recipe(new AddPlugin("org.openrewrite.maven", "rewrite-maven-plugin", "100.0", "<configuration>\n<activeRecipes>\n<recipe>io.moderne.FindTest</recipe>\n</activeRecipes>\n</configuration>", - null, null)), + null, null, null)), pomXml( """ <project> @@ -81,7 +81,7 @@ void addPluginWithDependencies() { <version>1.0.0</version> </dependency> </dependencies> - """, null)), + """, null, null)), pomXml( """ <project> @@ -137,7 +137,7 @@ void addPluginWithExecutions() { </configuration> </execution> </executions> - """)), + """, null)), pomXml( """ <project> @@ -251,4 +251,84 @@ void updatePluginVersion() { ) ); } + + @Test + void addPluginWithoutVersion() { + rewriteRun( + spec -> spec.recipe(new AddPlugin("org.openrewrite.maven", "rewrite-maven-plugin", null, null, null, null, null)), + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </project> + """, + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.openrewrite.maven</groupId> + <artifactId>rewrite-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </project> + """ + ) + ); + } + + @Test + void addPluginWithMatchingFilePattern() { + rewriteRun( + spec -> spec.recipe(new AddPlugin("org.openrewrite.maven", "rewrite-maven-plugin", null, null, null, null, "dir/pom.xml")), + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </project> + """, + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.openrewrite.maven</groupId> + <artifactId>rewrite-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </project> + """, + spec -> spec.path("dir/pom.xml") + ) + ); + } + + @Test + void addPluginWithNonMatchingFilePattern() { + rewriteRun( + spec -> spec.recipe(new AddPlugin("org.openrewrite.maven", "rewrite-maven-plugin", null, null, null, null, "dir/pom.xml")), + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + </project> + """, + spec -> spec.path("pom.xml") + ) + ); + } } From 1a1d07cf861274e45c1c1fe62c0c48ee924ea03b Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 15 Sep 2023 11:21:38 +0200 Subject: [PATCH 244/447] Fix auto-detection of method declaration parameter alignment Fixes: #3550 --- .../java/style/AutodetectTest.java | 22 +++++++++++++++++++ .../openrewrite/java/style/Autodetect.java | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java index cf69fe19f4e..1241cea4fdc 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java @@ -57,6 +57,28 @@ boolean eq(){ assertThat(tabsAndIndents.getContinuationIndent()).isEqualTo(8); } + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3550") + void alignParametersWhenMultiple() { + var cus = jp().parse( + """ + class Test { + void foo(String s1, + String s2, + String s3) { + } + } + """ + ); + + var detector = Autodetect.detector(); + cus.forEach(detector::sample); + var styles = detector.build(); + var tabsAndIndents = NamedStyles.merge(TabsAndIndentsStyle.class, singletonList(styles)); + + assertThat(tabsAndIndents.getMethodDeclarationParameters().getAlignWhenMultiple()).isTrue(); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1221") @Test void springDemoApp() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java index 7d76e240d36..1bcde8d64a1 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java @@ -343,7 +343,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, In List<Statement> parameters = method.getParameters(); for (int i = 1; i < parameters.size(); i++) { if (parameters.get(i).getPrefix().getLastWhitespace().contains("\n")) { - if (alignTo == parameters.get(i).getPrefix().getLastWhitespace().length()) { + if (alignTo == parameters.get(i).getPrefix().getLastWhitespace().length() - 1) { stats.multilineAlignedToFirstArgument++; } else { stats.multilineNotAlignedToFirstArgument++; From b876c439ee4dc91e750ed659eb5a6f0f552f9a84 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 15 Sep 2023 12:19:00 +0200 Subject: [PATCH 245/447] Use method declaration parameters to check for continuation indentation Fixes: #3552 --- .../java/style/AutodetectTest.java | 22 +++++++++++++++++++ .../openrewrite/java/style/Autodetect.java | 1 + 2 files changed, 23 insertions(+) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java index 1241cea4fdc..b4504cc1280 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java @@ -57,6 +57,28 @@ boolean eq(){ assertThat(tabsAndIndents.getContinuationIndent()).isEqualTo(8); } + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3552") + void continuationIndentFromParameters() { + var cus = jp().parse( + """ + class Test { + void foo(String s1, + String s2, + String s3) { + } + } + """ + ); + + var detector = Autodetect.detector(); + cus.forEach(detector::sample); + var styles = detector.build(); + var tabsAndIndents = NamedStyles.merge(TabsAndIndentsStyle.class, singletonList(styles)); + + assertThat(tabsAndIndents.getContinuationIndent()).isEqualTo(5); + } + @Test @Issue("https://github.com/openrewrite/rewrite/issues/3550") void alignParametersWhenMultiple() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java index 1bcde8d64a1..e68f2ce229b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java @@ -347,6 +347,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, In stats.multilineAlignedToFirstArgument++; } else { stats.multilineNotAlignedToFirstArgument++; + countIndents(parameters.get(i).getPrefix().getWhitespace(), true, stats); } } } From 44fe2ff9f09ec783c6e240866b1b920dad741f49 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 15 Sep 2023 14:35:45 +0200 Subject: [PATCH 246/447] Align closing parentheses of method declaration parameters list --- .../openrewrite/java/format/TabsAndIndentsTest.java | 6 ++++-- .../java/format/TabsAndIndentsVisitor.java | 13 ++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java index 5a7f6d0faec..64b191138c2 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java @@ -109,7 +109,8 @@ private void firstArgNoPrefix(String first, private void firstArgOnNewLine( String first, int times, - String third) { + String third + ) { } } """, @@ -122,7 +123,8 @@ private void firstArgNoPrefix(String first, private void firstArgOnNewLine( String first, int times, - String third) { + String third + ) { } } """ diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java index cdeb41a2d12..b5d816e529c 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java @@ -210,8 +210,15 @@ public <T> JRightPadded<T> visitRightPadded(@Nullable JRightPadded<T> right, JRi } case METHOD_DECLARATION_PARAMETER: case RECORD_STATE_VECTOR: { + if (elem instanceof J.Empty) { + elem = elem.withPrefix(indentTo(elem.getPrefix(), indent, loc.getAfterLocation())); + after = right.getAfter(); + break; + } JContainer<J> container = getCursor().getParentOrThrow().getValue(); - J firstArg = container.getElements().iterator().next(); + List<J> elements = container.getElements(); + J firstArg = elements.iterator().next(); + J lastArg = elements.get(elements.size() - 1); if (style.getMethodDeclarationParameters().getAlignWhenMultiple()) { J.MethodDeclaration method = getCursor().firstEnclosing(J.MethodDeclaration.class); if (method != null) { @@ -224,13 +231,13 @@ public <T> JRightPadded<T> visitRightPadded(@Nullable JRightPadded<T> right, JRi } getCursor().getParentOrThrow().putMessage("lastIndent", alignTo - style.getContinuationIndent()); elem = visitAndCast(elem, p); - after = indentTo(right.getAfter(), alignTo, loc.getAfterLocation()); + after = indentTo(right.getAfter(), t == lastArg ? indent : alignTo, loc.getAfterLocation()); } else { after = right.getAfter(); } } else { elem = visitAndCast(elem, p); - after = indentTo(right.getAfter(), style.getContinuationIndent(), loc.getAfterLocation()); + after = indentTo(right.getAfter(), t == lastArg ? indent : style.getContinuationIndent(), loc.getAfterLocation()); } break; } From e7f52a83335e9ba2132a4fb8f0d9359799a4d178 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 18 Sep 2023 21:04:53 -0700 Subject: [PATCH 247/447] Support rarely used but valid varargs dependency notation in UpgradeDependencyVersion --- .../main/groovy/RewriteGradleProject.groovy | 28 ++--- .../gradle/UpgradeDependencyVersion.java | 112 ++++++++++-------- .../gradle/UpgradeDependencyVersionTest.java | 49 +++++++- 3 files changed, 118 insertions(+), 71 deletions(-) diff --git a/rewrite-gradle/src/main/groovy/RewriteGradleProject.groovy b/rewrite-gradle/src/main/groovy/RewriteGradleProject.groovy index e3ee6533869..dd1aed5fcde 100644 --- a/rewrite-gradle/src/main/groovy/RewriteGradleProject.groovy +++ b/rewrite-gradle/src/main/groovy/RewriteGradleProject.groovy @@ -37,33 +37,33 @@ import org.gradle.process.JavaForkOptions import org.gradle.process.ProcessForkOptions interface DependencyHandlerSpec extends DependencyHandler { - Dependency annotationProcessor(Object dependencyNotation) + Dependency annotationProcessor(Object... dependencyNotation) Dependency annotationProcessor(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency api(Object dependencyNotation) + Dependency api(Object... dependencyNotation) Dependency api(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency classpath(Object dependencyNotation) + Dependency classpath(Object... dependencyNotation) Dependency classpath(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency compile(Object dependencyNotation) + Dependency compile(Object... dependencyNotation) Dependency compile(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency compileOnly(Object dependencyNotation) + Dependency compileOnly(Object... dependencyNotation) Dependency compileOnly(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency implementation(Object dependencyNotation) + Dependency implementation(Object... dependencyNotation) Dependency implementation(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency runtime(Object dependencyNotation) + Dependency runtime(Object... dependencyNotation) Dependency runtime(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency runtimeOnly(Object dependencyNotation) + Dependency runtimeOnly(Object... dependencyNotation) Dependency runtimeOnly(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency runtimeClasspath(Object dependencyNotation) + Dependency runtimeClasspath(Object... dependencyNotation) Dependency runtimeClasspath(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency testCompile(Object dependencyNotation) + Dependency testCompile(Object... dependencyNotation) Dependency testCompile(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency testCompileOnly(Object dependencyNotation) + Dependency testCompileOnly(Object... dependencyNotation) Dependency testCompileOnly(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value=ExternalDependency) Closure closure) - Dependency testImplementation(Object dependencyNotation) + Dependency testImplementation(Object... dependencyNotation) Dependency testImplementation(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency testRuntime(Object dependencyNotation) + Dependency testRuntime(Object... dependencyNotation) Dependency testRuntime(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) - Dependency testRuntimeOnly(Object dependencyNotation) + Dependency testRuntimeOnly(Object... dependencyNotation) Dependency testRuntimeOnly(Object dependencyNotation, @DelegatesTo(strategy=Closure.DELEGATE_ONLY, value= ExternalDependency) Closure closure) } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index 8c188c92bc0..c93bb86ccbe 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -110,6 +110,8 @@ public Validated<Object> validate() { return validated; } + private static final String UPDATE_VERSION_ERROR_KEY = "UPDATE_VERSION_ERROR_KEY"; + @Override public TreeVisitor<?, ExecutionContext> getVisitor() { MethodMatcher dependencyDsl = new MethodMatcher("DependencyHandlerSpec *(..)"); @@ -164,68 +166,76 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) return m; } - private J.MethodInvocation updateDependency(J.MethodInvocation m, ExecutionContext ctx) { - List<Expression> depArgs = m.getArguments(); - if (depArgs.get(0) instanceof G.GString) { - G.GString gString = (G.GString) depArgs.get(0); - List<J> strings = gString.getStrings(); - if (strings.size() != 2 || !(strings.get(0) instanceof J.Literal) || !(strings.get(1) instanceof G.GString.Value)) { - return m; - } - J.Literal groupArtifact = (J.Literal) strings.get(0); - G.GString.Value versionValue = (G.GString.Value) strings.get(1); - if (!(versionValue.getTree() instanceof J.Identifier) || !(groupArtifact.getValue() instanceof String)) { - return m; - } - Dependency dep = DependencyStringNotationConverter.parse((String) groupArtifact.getValue()); - if (dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId())) { - String versionVariableName = ((J.Identifier) versionValue.getTree()).getSimpleName(); - getCursor().dropParentUntil(p -> p instanceof SourceFile) - .computeMessageIfAbsent(VERSION_VARIABLE_KEY, v -> new HashMap<String, Map<GroupArtifact, Set<String>>>()) - .computeIfAbsent(versionVariableName, it -> new HashMap<>()) - .computeIfAbsent(new GroupArtifact(dep.getGroupId(), dep.getArtifactId()), it -> new HashSet<>()) - .add(m.getSimpleName()); - } - } else if (depArgs.get(0) instanceof J.Literal) { - String gav = (String) ((J.Literal) depArgs.get(0)).getValue(); - if (gav == null) { - return Markup.warn(m, new IllegalStateException("Unable to update version")); - } - Dependency dep = DependencyStringNotationConverter.parse(gav); - if (dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId()) + private J.MethodInvocation updateDependency(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = method; + m = m.withArguments(ListUtils.map(m.getArguments(), arg -> { + if (arg instanceof G.GString) { + G.GString gString = (G.GString) arg; + List<J> strings = gString.getStrings(); + if (strings.size() != 2 || !(strings.get(0) instanceof J.Literal) || !(strings.get(1) instanceof G.GString.Value)) { + return arg; + } + J.Literal groupArtifact = (J.Literal) strings.get(0); + G.GString.Value versionValue = (G.GString.Value) strings.get(1); + if (!(versionValue.getTree() instanceof J.Identifier) || !(groupArtifact.getValue() instanceof String)) { + return arg; + } + Dependency dep = DependencyStringNotationConverter.parse((String) groupArtifact.getValue()); + if (dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId())) { + String versionVariableName = ((J.Identifier) versionValue.getTree()).getSimpleName(); + getCursor().dropParentUntil(p -> p instanceof SourceFile) + .computeMessageIfAbsent(VERSION_VARIABLE_KEY, v -> new HashMap<String, Map<GroupArtifact, Set<String>>>()) + .computeIfAbsent(versionVariableName, it -> new HashMap<>()) + .computeIfAbsent(new GroupArtifact(dep.getGroupId(), dep.getArtifactId()), it -> new HashSet<>()) + .add(method.getSimpleName()); + } + } else if (arg instanceof J.Literal) { + J.Literal literal = (J.Literal) arg; + String gav = (String) literal.getValue(); + if (gav == null) { + getCursor().putMessage(UPDATE_VERSION_ERROR_KEY, new IllegalStateException("Unable to update version")); + return arg; + } + Dependency dep = DependencyStringNotationConverter.parse(gav); + if (dependencyMatcher.matches(dep.getGroupId(), dep.getArtifactId()) && dep.getVersion() != null && !dep.getVersion().startsWith("$")) { - GradleProject gradleProject = getCursor().firstEnclosingOrThrow(JavaSourceFile.class) - .getMarkers() - .findFirst(GradleProject.class) - .orElseThrow(() -> new IllegalArgumentException("Gradle files are expected to have a GradleProject marker.")); - String version = dep.getVersion(); - try { - String newVersion = "classpath".equals(m.getSimpleName()) ? - findNewerPluginVersion(dep.getGroupId(), dep.getArtifactId(), version, gradleProject, ctx) : - findNewerProjectDependencyVersion(dep.getGroupId(), dep.getArtifactId(), version, gradleProject, ctx); - if (newVersion == null || version.equals(newVersion)) { - return m; - } - getCursor().dropParentUntil(p -> p instanceof SourceFile) - .computeMessageIfAbsent(NEW_VERSION_KEY, it -> new HashMap<GroupArtifactVersion, Set<String>>()) - .computeIfAbsent(new GroupArtifactVersion(dep.getGroupId(), dep.getArtifactId(), newVersion), it -> new HashSet<>()) - .add(m.getSimpleName()); + GradleProject gradleProject = getCursor().firstEnclosingOrThrow(JavaSourceFile.class) + .getMarkers() + .findFirst(GradleProject.class) + .orElseThrow(() -> new IllegalArgumentException("Gradle files are expected to have a GradleProject marker.")); + String version = dep.getVersion(); + try { + String newVersion = "classpath".equals(method.getSimpleName()) ? + findNewerPluginVersion(dep.getGroupId(), dep.getArtifactId(), version, gradleProject, ctx) : + findNewerProjectDependencyVersion(dep.getGroupId(), dep.getArtifactId(), version, gradleProject, ctx); + if (newVersion == null || version.equals(newVersion)) { + return arg; + } + getCursor().dropParentUntil(p -> p instanceof SourceFile) + .computeMessageIfAbsent(NEW_VERSION_KEY, it -> new HashMap<GroupArtifactVersion, Set<String>>()) + .computeIfAbsent(new GroupArtifactVersion(dep.getGroupId(), dep.getArtifactId(), newVersion), it -> new HashSet<>()) + .add(method.getSimpleName()); - return m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { - J.Literal literal = (J.Literal) arg; String newGav = dep .withVersion(newVersion) .toStringNotation(); return literal .withValue(newGav) .withValueSource(requireNonNull(literal.getValueSource()).replace(gav, newGav)); - })); - } catch (MavenDownloadingException e) { - return e.warn(m); + } catch (MavenDownloadingException e) { + getCursor().putMessage(UPDATE_VERSION_ERROR_KEY, e); + } } } - } else if (depArgs.size() >= 3 && depArgs.get(0) instanceof G.MapEntry + return arg; + })); + Exception err = getCursor().pollMessage(UPDATE_VERSION_ERROR_KEY); + if(err != null) { + m = Markup.warn(m, err); + } + List<Expression> depArgs = m.getArguments(); + if (depArgs.size() >= 3 && depArgs.get(0) instanceof G.MapEntry && depArgs.get(1) instanceof G.MapEntry && depArgs.get(2) instanceof G.MapEntry) { Expression groupValue = ((G.MapEntry) depArgs.get(0)).getValue(); diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java index 88a1e1c1338..1d190651c92 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java @@ -143,6 +143,43 @@ void updateVersionInVariable() { ); } + @Test + void varargsDependency() { + rewriteRun( + buildGradle( + """ + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + implementation( + 'com.google.guava:guava-gwt:29.0-jre', + 'com.google.guava:guava:29.0-jre') + } + """, + """ + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + implementation( + 'com.google.guava:guava-gwt:29.0-jre', + 'com.google.guava:guava:30.1.1-jre') + } + """) + ); + } + @Test void mapNotationVariable() { rewriteRun( @@ -327,15 +364,15 @@ void matchesGlobs() { plugins { id "java" } - + repositories { mavenCentral() } - + ext { guavaVersion = "29.0-jre" } - + def guavaVersion2 = "29.0-jre" dependencies { implementation("com.google.guava:guava:29.0-jre") @@ -348,15 +385,15 @@ void matchesGlobs() { plugins { id "java" } - + repositories { mavenCentral() } - + ext { guavaVersion = "30.1.1-jre" } - + def guavaVersion2 = "30.1.1-jre" dependencies { implementation("com.google.guava:guava:30.1.1-jre") From 62ee4dadf2b203178d1d86c7ad1a799cc9f15bce Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 18 Sep 2023 22:52:52 -0700 Subject: [PATCH 248/447] Add IncrementProjectVersion which increments the major, minor, or patch versions of a semver-versioned maven project --- .../maven/ChangeProjectVersion.java | 8 +- .../maven/IncrementProjectVersion.java | 148 ++++++++++++++++++ .../maven/IncrementProjectVersionTest.java | 74 +++++++++ 3 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeProjectVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeProjectVersion.java index 3ce3aea8850..e68af714b52 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeProjectVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeProjectVersion.java @@ -54,13 +54,13 @@ public String getDescription() { } @Option(displayName = "GroupId", - description = "The groupId of the maven project to change it's version. This can be a glob expression.", + description = "The groupId of the maven project to change its version. This can be a glob expression.", example = "org.openrewrite") String groupId; @Option(displayName = "ArtifactId", - description = "The artifactId of the maven project to change it's version. This can be a glob expression.", - example = "rewrite-maven") + description = "The artifactId of the maven project to change its version. This can be a glob expression.", + example = "*") String artifactId; @Option(displayName = "New version", @@ -69,7 +69,7 @@ public String getDescription() { String newVersion; @Option(displayName = "Override Parent Version", - description = "This flag can be set to explicitly override the inherited parent version. The default for this flag is `false`.", + description = "This flag can be set to explicitly override the inherited parent version. Default `false`.", required = false ) @Nullable diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java new file mode 100644 index 00000000000..6c8baf963c6 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java @@ -0,0 +1,148 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.With; +import org.openrewrite.*; +import org.openrewrite.marker.Marker; +import org.openrewrite.maven.tree.ResolvedPom; +import org.openrewrite.xml.ChangeTagValue; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.tree.Xml; + +import java.util.Optional; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.openrewrite.Tree.randomId; +import static org.openrewrite.internal.StringUtils.matchesGlob; + +@Value +@EqualsAndHashCode(callSuper = true) +public class IncrementProjectVersion extends Recipe { + + @Override + public String getDisplayName() { + return "Increment Maven Project Version"; + } + + @Override + public String getDescription() { + return "Increase Maven project version by incrementing either the major, minor, or patch version as defined by " + + "[semver](https://semver.org/). Other versioning schemes are not supported."; + } + + @Option(displayName = "GroupId", + description = "The groupId of the maven project to change its version. This can be a glob expression.", + example = "org.openrewrite") + String groupId; + + @Option(displayName = "ArtifactId", + description = "The artifactId of the maven project to change its version. This can be a glob expression.", + example = "*") + String artifactId; + + @Option(displayName = "Semver Digit", + description = "`MAJOR` increments the first digit, `MINOR` increments the second digit, and `PATCH` " + + "increments the third digit.", + example = "PATCH") + SemverDigit digit; + + public enum SemverDigit { + MAJOR, + MINOR, + PATCH + } + + @Value + @With + private static class AlreadyIncremented implements Marker { + UUID id; + } + + private static final Pattern SEMVER_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)\\.?(\\d+)?(-.+)?$"); + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new MavenIsoVisitor<ExecutionContext>() { + private final XPathMatcher PROJECT_MATCHER = new XPathMatcher("/project"); + + @Override + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + + if (!PROJECT_MATCHER.matches(getCursor()) || t.getMarkers().findFirst(AlreadyIncremented.class).isPresent()) { + return t; + } + ResolvedPom resolvedPom = getResolutionResult().getPom(); + + if (!(matchesGlob(resolvedPom.getValue(t.getChildValue("groupId").orElse(null)), groupId) && + matchesGlob(resolvedPom.getValue(t.getChildValue("artifactId").orElse(null)), artifactId))) { + return t; + } + Optional<Xml.Tag> versionTag = t.getChild("version"); + if (!(versionTag.isPresent() && versionTag.get().getValue().isPresent())) { + return t; + } + String versionTagValue = versionTag.get().getValue().get(); + String oldVersion = resolvedPom.getValue(versionTagValue); + if(oldVersion == null) { + return t; + } + + Matcher m = SEMVER_PATTERN.matcher(oldVersion); + if(!m.matches()) { + return t; + } + String major = m.group(1); + String minor = m.group(2); + String patch = m.group(3); + // Semver does not have a concept of a fourth number, but it is common enough to support + String fourth = m.group(4); + String extra = m.group(5); + switch (digit) { + case MAJOR: + major = String.valueOf(Integer.parseInt(major) + 1); + minor = "0"; + patch = "0"; + break; + case MINOR: + minor = String.valueOf(Integer.parseInt(minor) + 1); + patch = "0"; + break; + case PATCH: + patch = String.valueOf(Integer.parseInt(patch) + 1); + break; + } + if(fourth == null) { + fourth = ""; + } else { + fourth = ".0"; + } + if(extra == null) { + extra = ""; + } + String newVersion = major + "." + minor + "." + patch + fourth + extra; + t = (Xml.Tag) new ChangeTagValue("version", null, newVersion).getVisitor() + .visitNonNull(t, ctx); + return t.withMarkers(t.getMarkers().add(new AlreadyIncremented(randomId()))); + } + }; + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java new file mode 100644 index 00000000000..f6f3f5423da --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.maven.Assertions.pomXml; + +public class IncrementProjectVersionTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new IncrementProjectVersion("*", "*", IncrementProjectVersion.SemverDigit.MINOR)); + } + + @Test + void changeProjectVersion() { + rewriteRun( + pomXml( + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>8.40.1-SNAPSHOT</version> + </project> + """, + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>8.41.0-SNAPSHOT</version> + </project> + """ + ) + ); + } + + @Test + void extraFourthDigit() { + rewriteRun( + pomXml( + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>8.4.1.1-SNAPSHOT</version> + </project> + """, + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-maven</artifactId> + <version>8.5.0.0-SNAPSHOT</version> + </project> + """ + ) + ); + } +} From 4d0d98c300c623fcae408422f572acce488c2278 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 19 Sep 2023 13:20:01 -0700 Subject: [PATCH 249/447] Cleanup UnnecessaryParenthesesVisitor --- .../UnnecessaryParenthesesVisitor.java | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java index 0b443312b1c..3dee2ca5e12 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesVisitor.java @@ -24,9 +24,9 @@ import org.openrewrite.java.tree.*; @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) -public class UnnecessaryParenthesesVisitor extends JavaVisitor<ExecutionContext> { +public class UnnecessaryParenthesesVisitor<P> extends JavaVisitor<P> { @Override - public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionContext) { + public boolean isAcceptable(SourceFile sourceFile, P executionContext) { // Causes problems on other languages like JavaScript return sourceFile instanceof J.CompilationUnit; } @@ -37,14 +37,18 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext executionCon private UnnecessaryParenthesesStyle getStyle() { if (style == null) { - JavaSourceFile cu = getCursor().firstEnclosingOrThrow(JavaSourceFile.class); - style = ((SourceFile) cu).getStyle(UnnecessaryParenthesesStyle.class, Checkstyle.unnecessaryParentheses()); + JavaSourceFile cu = getCursor().firstEnclosing(JavaSourceFile.class); + if(cu == null) { + style = Checkstyle.unnecessaryParentheses(); + } else { + style = ((SourceFile) cu).getStyle(UnnecessaryParenthesesStyle.class, Checkstyle.unnecessaryParentheses()); + } } return style; } @Override - public <T extends J> J visitParentheses(J.Parentheses<T> parens, ExecutionContext ctx) { + public <T extends J> J visitParentheses(J.Parentheses<T> parens, P ctx) { J par = super.visitParentheses(parens, ctx); Cursor c = getCursor().pollNearestMessage(UNNECESSARY_PARENTHESES_MESSAGE); if (c != null && (c.getValue() instanceof J.Literal || c.getValue() instanceof J.Identifier)) { @@ -61,7 +65,7 @@ public <T extends J> J visitParentheses(J.Parentheses<T> parens, ExecutionContex } @Override - public J visitIdentifier(J.Identifier ident, ExecutionContext ctx) { + public J visitIdentifier(J.Identifier ident, P ctx) { J.Identifier i = (J.Identifier) super.visitIdentifier(ident, ctx); if (getStyle().getIdent() && getCursor().getParentTreeCursor().getValue() instanceof J.Parentheses) { getCursor().putMessageOnFirstEnclosing(J.Parentheses.class, UNNECESSARY_PARENTHESES_MESSAGE, getCursor()); @@ -70,7 +74,7 @@ public J visitIdentifier(J.Identifier ident, ExecutionContext ctx) { } @Override - public J visitLiteral(J.Literal literal, ExecutionContext ctx) { + public J visitLiteral(J.Literal literal, P ctx) { J.Literal l = (J.Literal) super.visitLiteral(literal, ctx); JavaType.Primitive type = l.getType(); if ((getStyle().getNumInt() && type == JavaType.Primitive.Int) || @@ -90,7 +94,7 @@ public J visitLiteral(J.Literal literal, ExecutionContext ctx) { } @Override - public J visitAssignmentOperation(J.AssignmentOperation assignOp, ExecutionContext ctx) { + public J visitAssignmentOperation(J.AssignmentOperation assignOp, P ctx) { J.AssignmentOperation a = (J.AssignmentOperation) super.visitAssignmentOperation(assignOp, ctx); J.AssignmentOperation.Type op = a.getOperator(); if (a.getAssignment() instanceof J.Parentheses && ((getStyle().getBitAndAssign() && op == J.AssignmentOperation.Type.BitAnd) || @@ -111,7 +115,7 @@ public J visitAssignmentOperation(J.AssignmentOperation assignOp, ExecutionConte } @Override - public J visitAssignment(J.Assignment assignment, ExecutionContext ctx) { + public J visitAssignment(J.Assignment assignment, P ctx) { J.Assignment a = visitAndCast(assignment, ctx, super::visitAssignment); if (getStyle().getAssign() && a.getAssignment() instanceof J.Parentheses) { a = (J.Assignment) new UnwrapParentheses<>((J.Parentheses<?>) a.getAssignment()).visitNonNull(a, ctx, @@ -121,7 +125,7 @@ public J visitAssignment(J.Assignment assignment, ExecutionContext ctx) { } @Override - public J visitReturn(J.Return return_, ExecutionContext ctx) { + public J visitReturn(J.Return return_, P ctx) { J.Return rtn = (J.Return) super.visitReturn(return_, ctx); if (getStyle().getExpr() && rtn.getExpression() instanceof J.Parentheses) { rtn = (J.Return) new UnwrapParentheses<>((J.Parentheses<?>) rtn.getExpression()).visitNonNull(rtn, ctx, @@ -131,7 +135,7 @@ public J visitReturn(J.Return return_, ExecutionContext ctx) { } @Override - public J visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext ctx) { + public J visitVariable(J.VariableDeclarations.NamedVariable variable, P ctx) { J.VariableDeclarations.NamedVariable v = (J.VariableDeclarations.NamedVariable) super.visitVariable(variable, ctx); if (getStyle().getAssign() && v.getInitializer() != null && v.getInitializer() instanceof J.Parentheses) { @@ -141,7 +145,7 @@ public J visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionC } @Override - public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { + public J visitLambda(J.Lambda lambda, P ctx) { J.Lambda l = (J.Lambda) super.visitLambda(lambda, ctx); if (l.getParameters().getParameters().size() == 1 && l.getParameters().isParenthesized() && @@ -153,7 +157,7 @@ public J visitLambda(J.Lambda lambda, ExecutionContext ctx) { } @Override - public J visitIf(J.If iff, ExecutionContext ctx) { + public J visitIf(J.If iff, P ctx) { J.If i = (J.If) super.visitIf(iff, ctx); // Unwrap when if condition is a single parenthesized expression Expression expression = i.getIfCondition().getTree(); @@ -165,7 +169,7 @@ public J visitIf(J.If iff, ExecutionContext ctx) { } @Override - public J visitWhileLoop(J.WhileLoop whileLoop, ExecutionContext ctx) { + public J visitWhileLoop(J.WhileLoop whileLoop, P ctx) { J.WhileLoop w = (J.WhileLoop) super.visitWhileLoop(whileLoop, ctx); // Unwrap when while condition is a single parenthesized expression Expression expression = w.getCondition().getTree(); @@ -177,7 +181,7 @@ public J visitWhileLoop(J.WhileLoop whileLoop, ExecutionContext ctx) { } @Override - public J visitDoWhileLoop(J.DoWhileLoop doWhileLoop, ExecutionContext ctx) { + public J visitDoWhileLoop(J.DoWhileLoop doWhileLoop, P ctx) { J.DoWhileLoop dw = (J.DoWhileLoop) super.visitDoWhileLoop(doWhileLoop, ctx); // Unwrap when while condition is a single parenthesized expression Expression expression = dw.getWhileCondition().getTree(); @@ -189,7 +193,7 @@ public J visitDoWhileLoop(J.DoWhileLoop doWhileLoop, ExecutionContext ctx) { } @Override - public J visitForControl(J.ForLoop.Control control, ExecutionContext ctx) { + public J visitForControl(J.ForLoop.Control control, P ctx) { J.ForLoop.Control fc = (J.ForLoop.Control) super.visitForControl(control, ctx); Expression condition = fc.getCondition(); if (condition instanceof J.Parentheses) { @@ -200,11 +204,11 @@ public J visitForControl(J.ForLoop.Control control, ExecutionContext ctx) { } @Override - public J visitTernary(J.Ternary ternary, ExecutionContext ctx) { + public J visitTernary(J.Ternary ternary, P ctx) { J.Ternary te = (J.Ternary) super.visitTernary(ternary, ctx); if (te.getCondition() instanceof J.Parentheses) { te = (J.Ternary) new UnwrapParentheses<>((J.Parentheses<?>) te.getCondition()).visitNonNull(te, ctx, getCursor().getParentOrThrow()); } return te; } -} \ No newline at end of file +} From a27fa13f10161e83ff692be4f03526c3decaeda9 Mon Sep 17 00:00:00 2001 From: Alexis Tual <atual@gradle.com> Date: Wed, 20 Sep 2023 18:46:07 +0200 Subject: [PATCH 250/447] Set AddGradleEnterpriseGradlePlugin server URL example to https://scans.gradle.com/ (#3554) --- .../gradle/plugins/AddGradleEnterpriseGradlePlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java index f845dd00e66..c7f34be7d90 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java @@ -68,7 +68,7 @@ public class AddGradleEnterpriseGradlePlugin extends Recipe { @Option(displayName = "Server URL", description = "The URL of the Gradle Enterprise server. If omitted the recipe will set no URL and Gradle will direct scans to https://scans.gradle.com/", required = false, - example = "https://ge.openrewrite.org/") + example = "https://scans.gradle.com/") @Nullable String server; From 3086e52b47c774ac3b6dd1ebea90e07225d09455 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 20 Sep 2023 10:03:56 -0700 Subject: [PATCH 251/447] Fix cursor messaging being incorrect in visitors applied with doAfterVisit() --- .../src/main/java/org/openrewrite/TreeVisitor.java | 2 +- .../src/test/java/org/openrewrite/TreeVisitorTest.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java b/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java index 6a6552b4d82..e30faca6de2 100644 --- a/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java +++ b/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java @@ -309,7 +309,7 @@ public T visit(@Nullable Tree tree, P p) { if (t != null && afterVisit != null) { for (TreeVisitor<?, P> v : afterVisit) { if (v != null) { - v.setCursor(getCursor()); + v.setCursor(new Cursor(cursor, tree)); //noinspection unchecked t = (T) v.visit(t, p); } diff --git a/rewrite-core/src/test/java/org/openrewrite/TreeVisitorTest.java b/rewrite-core/src/test/java/org/openrewrite/TreeVisitorTest.java index 2705a07f43d..4e3cb0a8053 100644 --- a/rewrite-core/src/test/java/org/openrewrite/TreeVisitorTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/TreeVisitorTest.java @@ -30,10 +30,13 @@ public class TreeVisitorTest { @Test void scheduleAfterOnVisitWithCursor() { + Quark quark = new Quark(Tree.randomId(), Paths.get("quark"), Markers.EMPTY, null, null); AtomicInteger visited = new AtomicInteger(0); TreeVisitor<Tree, Integer> scheduled = new TreeVisitor<>() { @Override public @Nullable Tree visit(@Nullable Tree tree, Integer i) { + assertThat(tree).isSameAs(quark); + assertThat((Tree)getCursor().getValue()).isSameAs(quark); visited.incrementAndGet(); return tree; } @@ -46,12 +49,11 @@ public Tree preVisit(Tree tree, Integer i) { return tree; } }; - Quark quark = new Quark(Tree.randomId(), Paths.get("quark"), Markers.EMPTY, null, null); - visitor.visit(quark, 0, new Cursor(new Cursor(null, Cursor.ROOT_VALUE), "foo")); + visitor.visit(quark, 0); assertThat(visited).hasValue(1); - visitor.visit(quark, 0, new Cursor(new Cursor(null, Cursor.ROOT_VALUE), "foo")); + visitor.visit(quark, 0); assertThat(visited).hasValue(2); } } From b9df9931527a6a05a9e9dee75277445861c90157 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Thu, 21 Sep 2023 00:17:47 -0400 Subject: [PATCH 252/447] Re-add BuildToolFailure for backwards compatibility --- .../openrewrite/marker/BuildToolFailure.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 rewrite-core/src/main/java/org/openrewrite/marker/BuildToolFailure.java diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/BuildToolFailure.java b/rewrite-core/src/main/java/org/openrewrite/marker/BuildToolFailure.java new file mode 100644 index 00000000000..e8d58b3a4dc --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/marker/BuildToolFailure.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.marker; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.With; +import org.openrewrite.internal.lang.Nullable; + +import java.util.UUID; + +/** + * @deprecated Only included for backwards compatibility with old LSTs. + */ +@Deprecated +@Value +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@With +public class BuildToolFailure implements Marker { + @EqualsAndHashCode.Include + UUID id; + + /** + * The name of the build tool that failed, possibly a wrapper. + */ + String type; + @Nullable + String version; + + /** + * The command that was executed. + */ + @Nullable + String command; + + @Nullable + Integer exitCode; +} From d9c6cced35ea323fec7bd206a90ed3334260bf42 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 21 Sep 2023 12:50:56 +0200 Subject: [PATCH 253/447] Better formatting for method parameter lists Issue: openrewrite/rewrite-kotlin#323 --- .../org/openrewrite/java/format/TabsAndIndentsTest.java | 6 ++++-- .../org/openrewrite/java/format/TabsAndIndentsVisitor.java | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java index 64b191138c2..4a90cd7b334 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java @@ -104,7 +104,8 @@ void alignMethodDeclarationParamsWhenMultiple() { class Test { private void firstArgNoPrefix(String first, int times, - String third) { + String third + ) { } private void firstArgOnNewLine( String first, @@ -118,7 +119,8 @@ private void firstArgOnNewLine( class Test { private void firstArgNoPrefix(String first, int times, - String third) { + String third + ) { } private void firstArgOnNewLine( String first, diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java index b5d816e529c..0ce41b00117 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java @@ -231,6 +231,7 @@ public <T> JRightPadded<T> visitRightPadded(@Nullable JRightPadded<T> right, JRi } getCursor().getParentOrThrow().putMessage("lastIndent", alignTo - style.getContinuationIndent()); elem = visitAndCast(elem, p); + getCursor().getParentOrThrow().putMessage("lastIndent", indent); after = indentTo(right.getAfter(), t == lastArg ? indent : alignTo, loc.getAfterLocation()); } else { after = right.getAfter(); From 00edaffab5b808a6d20a721fb3cec2858bc7daf8 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 21 Sep 2023 16:56:16 +0200 Subject: [PATCH 254/447] Remove uses of `String#toCharArray()` (#3556) When looping over the contents of a `String` it is typically better to work with `String#charAt()`. --- .../org/openrewrite/internal/StringUtils.java | 14 +++++----- .../ReloadableJava17JavadocVisitor.java | 27 +++++++++---------- .../ReloadableJava17ParserVisitor.java | 9 +++---- .../org/openrewrite/java/JavaPrinter.java | 5 ++-- .../org/openrewrite/java/JavadocPrinter.java | 8 +++--- .../java/format/BlankLinesVisitor.java | 3 ++- .../format/NormalizeLineBreaksVisitor.java | 5 ++-- .../format/NormalizeTabsOrSpacesVisitor.java | 11 ++++---- .../RemoveTrailingWhitespaceVisitor.java | 11 ++++---- .../java/format/TabsAndIndentsVisitor.java | 22 ++++++++------- .../openrewrite/java/style/Autodetect.java | 18 ++++++------- 11 files changed, 67 insertions(+), 66 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java index 08c50b3d28e..c84f3fe7c0e 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java @@ -103,13 +103,11 @@ public static String trimIndent(String text) { if (end == start) { end++; } - char[] charArray = text.substring(start, end).toCharArray(); - StringBuilder trimmed = new StringBuilder(); - for (int i = 0; i < charArray.length; i++) { + for (int i = start; i < end; i++) { int j = i; - for (; j < charArray.length; j++) { - char c = charArray[j]; + for (; j < end; j++) { + char c = text.charAt(j); if (c == '\r' || c == '\n') { trimmed.append(c); break; @@ -139,7 +137,8 @@ private static int minCommonIndentLevel(String text) { int minIndent = Integer.MAX_VALUE; int whiteSpaceCount = 0; boolean contentEncountered = false; - for (char c : text.toCharArray()) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); if (c == '\n' || c == '\r') { if (contentEncountered) { minIndent = Math.min(whiteSpaceCount, minIndent); @@ -565,7 +564,8 @@ private static boolean different(boolean caseSensitive, char ch, char other) { public static String indent(String text) { StringBuilder indent = new StringBuilder(); - for (char c : text.toCharArray()) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); if (c == '\n' || c == '\r') { return indent.toString(); } else if (Character.isWhitespace(c)) { diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java index ef5aea9f48f..5152b98fb7c 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java @@ -88,8 +88,6 @@ public ReloadableJava17JavadocVisitor(Context context, TreePath scope, Reloadabl } private void init() { - char[] sourceArr = source.toCharArray(); - StringBuilder firstPrefixBuilder = new StringBuilder(); StringBuilder javadocContent = new StringBuilder(); StringBuilder marginBuilder = null; @@ -98,12 +96,12 @@ private void init() { // skip past the opening '/**' int i = 3; - for (; i < sourceArr.length; i++) { - char c = sourceArr[i]; + for (; i < source.length(); i++) { + char c = source.charAt(i); if (inFirstPrefix) { // '*' characters are considered a part of the margin until a non '*' is parsed. if (Character.isWhitespace(c) || c == '*' && isPrefixAsterisk) { - if (isPrefixAsterisk && i + 1 <= sourceArr.length - 1 && sourceArr[i + 1] != '*') { + if (isPrefixAsterisk && i + 1 <= source.length() - 1 && source.charAt(i + 1) != '*') { isPrefixAsterisk = false; } firstPrefixBuilder.append(c); @@ -118,30 +116,31 @@ private void init() { } if (c == '\n') { + char prev = source.charAt(i - 1); if (inFirstPrefix) { firstPrefix = firstPrefixBuilder.toString(); inFirstPrefix = false; } else { // Handle consecutive new lines. - if ((sourceArr[i - 1] == '\n' || - sourceArr[i - 1] == '\r' && sourceArr[i - 2] == '\n')) { - String prevLineLine = sourceArr[i - 1] == '\n' ? "\n" : "\r\n"; + if ((prev == '\n' || + prev == '\r' && source.charAt(i - 2) == '\n')) { + String prevLineLine = prev == '\n' ? "\n" : "\r\n"; lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), prevLineLine, Markers.EMPTY)); } else if (marginBuilder != null) { // A new line with no '*' that only contains whitespace. - String newLine = sourceArr[i - 1] == '\r' ? "\r\n" : "\n"; + String newLine = prev == '\r' ? "\r\n" : "\n"; lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), newLine, Markers.EMPTY)); javadocContent.append(marginBuilder.substring(marginBuilder.indexOf("\n") + 1)); } javadocContent.append(c); } - String newLine = sourceArr[i - 1] == '\r' ? "\r\n" : "\n"; + String newLine = prev == '\r' ? "\r\n" : "\n"; marginBuilder = new StringBuilder(newLine); } else if (marginBuilder != null) { if (!Character.isWhitespace(c)) { if (c == '*') { // '*' characters are considered a part of the margin until a non '*' is parsed. marginBuilder.append(c); - if (i + 1 <= sourceArr.length - 1 && sourceArr[i + 1] != '*') { + if (i + 1 <= source.length() - 1 && source.charAt(i + 1) != '*') { lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), marginBuilder.toString(), Markers.EMPTY)); marginBuilder = null; @@ -152,7 +151,7 @@ private void init() { marginBuilder.toString(), Markers.EMPTY)); javadocContent.append(c); } else { - String newLine = marginBuilder.toString().startsWith("\r") ? "\r\n" : "\n"; + String newLine = marginBuilder.charAt(0) == '\r' ? "\r\n" : "\n"; lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), newLine, Markers.EMPTY)); String margin = marginBuilder.toString(); @@ -864,9 +863,9 @@ public List<Javadoc> visitText(String node) { node = node.stripLeading(); } - char[] textArr = node.toCharArray(); StringBuilder text = new StringBuilder(); - for (char c : textArr) { + for (int i = 0; i < node.length(); i++) { + char c = node.charAt(i); cursor++; if (c == '\n') { if (text.length() > 0) { 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 0ddd7bd2493..778dec66d3a 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 @@ -835,11 +835,10 @@ public J visitLiteral(LiteralTree node, Space fmt) { List<J.Literal.UnicodeEscape> unicodeEscapes = null; int i = 0; - char[] valueSourceArr = valueSource.toCharArray(); - for (int j = 0; j < valueSourceArr.length; j++) { - char c = valueSourceArr[j]; - if (c == '\\' && j < valueSourceArr.length - 1 && (j == 0 || valueSourceArr[j - 1] != '\\')) { - if (valueSourceArr[j + 1] == 'u' && j < valueSource.length() - 5) { + for (int j = 0; j < valueSource.length(); j++) { + char c = valueSource.charAt(j); + if (c == '\\' && j < valueSource.length() - 1 && (j == 0 || valueSource.charAt(j - 1) != '\\')) { + if (valueSource.charAt(j + 1) == 'u' && j < valueSource.length() - 5) { String codePoint = valueSource.substring(j + 2, j + 6); int codePointNumeric = Integer.parseInt(codePoint, 16); if (codePointNumeric >= SURR_FIRST && codePointNumeric <= SURR_LAST) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java index 9764f0856ff..db21d010b73 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java @@ -707,8 +707,9 @@ public J visitLiteral(Literal literal, PrintOutputCapture<P> p) { } } - char[] valueSourceArr = literal.getValueSource().toCharArray(); - for (char c : valueSourceArr) { + String valueSource = literal.getValueSource(); + for (int j = 0; j < valueSource.length(); j++) { + char c = valueSource.charAt(j); p.append(c); if (surrogate != null && surrogate.getValueSourceIndex() == ++i) { while (surrogate != null && surrogate.getValueSourceIndex() == i) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java index af78d3099c6..242f4485648 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java @@ -438,8 +438,10 @@ public Space visitSpace(Space space, Space.Location loc, PrintOutputCapture<P> p List<Javadoc.LineBreak> lineBreaks = getCursor().getNearestMessage("JAVADOC_LINE_BREAKS"); Integer index = getCursor().getNearestMessage("JAVADOC_LINE_BREAK_INDEX"); - if (lineBreaks != null && !lineBreaks.isEmpty() && index != null && space.getWhitespace().contains("\n")) { - for (char c : space.getWhitespace().toCharArray()) { + String whitespace = space.getWhitespace(); + if (lineBreaks != null && !lineBreaks.isEmpty() && index != null && whitespace.contains("\n")) { + for (int i = 0; i < whitespace.length(); i++) { + char c = whitespace.charAt(i); // The Space from a JavaDoc will not contain a CR because the JavaDoc parser // filters out other new line characters. CRLF is detected through the source // and only exists through LineBreaks. @@ -452,7 +454,7 @@ public Space visitSpace(Space space, Space.Location loc, PrintOutputCapture<P> p } getCursor().putMessageOnFirstEnclosing(Javadoc.DocComment.class, "JAVADOC_LINE_BREAK_INDEX", index); } else { - p.append(space.getWhitespace()); + p.append(whitespace); } return space; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/BlankLinesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/BlankLinesVisitor.java index 7f1e7c94b00..42d84f12a05 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/BlankLinesVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/BlankLinesVisitor.java @@ -302,7 +302,8 @@ private String minimumLines(String whitespace, int min) { private static int getNewLineCount(String whitespace) { int newLineCount = 0; - for (char c : whitespace.toCharArray()) { + for (int i = 0; i < whitespace.length(); i++) { + char c = whitespace.charAt(i); if (c == '\n') { newLineCount++; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaksVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaksVisitor.java index d5992948934..4ac95c1f162 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaksVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeLineBreaksVisitor.java @@ -71,9 +71,8 @@ private static String normalizeNewLines(String text, boolean useCrlf) { return text; } StringBuilder normalized = new StringBuilder(); - char[] charArray = text.toCharArray(); - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); if (useCrlf && c == '\n' && (i == 0 || text.charAt(i - 1) != '\r')) { normalized.append('\r').append('\n'); } else if (useCrlf || c != '\r') { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeTabsOrSpacesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeTabsOrSpacesVisitor.java index ee86d4410bc..01b916751e6 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeTabsOrSpacesVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/NormalizeTabsOrSpacesVisitor.java @@ -83,10 +83,9 @@ private String normalize(String text, boolean isComment) { StringBuilder textBuilder = new StringBuilder(); int consecutiveSpaces = 0; boolean inMargin = true; - char[] charArray = text.toCharArray(); outer: - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); if (c == '\n' || c == '\r') { inMargin = true; consecutiveSpaces = 0; @@ -94,10 +93,10 @@ private String normalize(String text, boolean isComment) { } else if (!inMargin) { textBuilder.append(c); } else if (style.getUseTabCharacter() && c == ' ' && (!isComment || - (i + 1 < charArray.length && charArray[i + 1] != '*'))) { + (i + 1 < text.length() && text.charAt(i + 1) != '*'))) { int j = i + 1; - for (; j < charArray.length && j < style.getTabSize(); j++) { - if (charArray[j] != ' ') { + for (; j < text.length() && j < style.getTabSize(); j++) { + if (text.charAt(j) != ' ') { continue outer; } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/RemoveTrailingWhitespaceVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/RemoveTrailingWhitespaceVisitor.java index 2ea7d7c2710..77651a80a81 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/RemoveTrailingWhitespaceVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/RemoveTrailingWhitespaceVisitor.java @@ -40,7 +40,8 @@ public RemoveTrailingWhitespaceVisitor() { public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, P p) { String eof = cu.getEof().getWhitespace(); StringBuilder builder = new StringBuilder(); - for (char c : eof.toCharArray()) { + for (int i = 0; i < eof.length(); i++) { + char c = eof.charAt(i); if (c == '\n' || c == '\r') { builder.appendCodePoint(c); } @@ -53,13 +54,13 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, P p) { @Override public Space visitSpace(Space space, Space.Location loc, P p) { Space s = space; - int lastNewline = s.getWhitespace().lastIndexOf('\n'); + String whitespace = s.getWhitespace(); + int lastNewline = whitespace.lastIndexOf('\n'); // Skip import prefixes, leave those up to OrderImports which better understands that domain if (lastNewline > 0 && loc != Space.Location.IMPORT_PREFIX) { StringBuilder ws = new StringBuilder(); - char[] charArray = s.getWhitespace().toCharArray(); - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; + for (int i = 0; i < whitespace.length(); i++) { + char c = whitespace.charAt(i); if (i >= lastNewline) { ws.append(c); } else if (c == ',' || c == '\r' || c == '\n') { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java index 0ce41b00117..a4effda558d 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java @@ -475,7 +475,8 @@ private Space indentTo(Space space, int column, Space.Location spaceLocation) { private Comment indentComment(Comment comment, String priorSuffix, int column) { if (comment instanceof TextComment) { TextComment textComment = (TextComment) comment; - if (!textComment.getText().contains("\n")) { + String text = textComment.getText(); + if (!text.contains("\n")) { return comment; } @@ -489,9 +490,8 @@ private Comment indentComment(Comment comment, String priorSuffix, int column) { String newMargin = indent(margin, shift); if (textComment.isMultiline()) { StringBuilder multiline = new StringBuilder(); - char[] chars = textComment.getText().toCharArray(); - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); if (c == '\n') { multiline.append(c).append(newMargin); i += margin.length(); @@ -504,12 +504,11 @@ private Comment indentComment(Comment comment, String priorSuffix, int column) { } else if (shift < 0) { if (textComment.isMultiline()) { StringBuilder multiline = new StringBuilder(); - char[] chars = textComment.getText().toCharArray(); - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); if (c == '\n') { multiline.append(c); - for (int j = 0; j < Math.abs(shift) && i+j+1 < chars.length && (chars[j + i + 1] == ' ' || chars[j + i + 1] == '\t'); j++) { + for (int j = 0; j < Math.abs(shift) && i+j+1 < text.length() && (text.charAt(j + i + 1) == ' ' || text.charAt(j + i + 1) == '\t'); j++) { i++; } } else { @@ -585,7 +584,8 @@ private int getLengthOfWhitespace(@Nullable String whitespace) { } int size = 0; - for (char c : whitespace.toCharArray()) { + for (int i = 0; i < whitespace.length(); i++) { + char c = whitespace.charAt(i); size += c == '\t' ? style.getTabSize() : 1; if (c == '\n' || c == '\r') { size = 0; @@ -604,7 +604,9 @@ private int forInitColumn() { int column = 0; boolean afterInitStart = false; - for (char c : alignTo.print(getCursor()).toCharArray()) { + String print = alignTo.print(getCursor()); + for (int i = 0; i < print.length(); i++) { + char c = print.charAt(i); if (c == '(') { afterInitStart = true; } else if (afterInitStart && !Character.isWhitespace(c)) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java index e68f2ce229b..0f736fbed2f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java @@ -293,12 +293,11 @@ private static class FindLineFormatJavaVisitor extends JavaIsoVisitor<GeneralFor @Override public Space visitSpace(Space space, Space.Location loc, GeneralFormatStatistics stats) { String prefix = space.getWhitespace(); - char[] chars = prefix.toCharArray(); - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); if (c == '\n') { - if (i == 0 || chars[i - 1] != '\r') { + if (i == 0 || prefix.charAt(i - 1) != '\r') { stats.linesWithLFNewLines++; } else { stats.linesWithCRLFNewLines++; @@ -435,6 +434,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation m, IndentStat } if (m.getPadding().getSelect() != null) { countIndents(m.getPadding().getSelect().getAfter().getWhitespace(), true, stats); + visit(m.getSelect(), stats); } visitContainer(m.getPadding().getTypeParameters(), JContainer.Location.TYPE_PARAMETERS, stats); @@ -461,8 +461,8 @@ private void countIndents(String space, boolean isContinuation, IndentStatistics int spaceIndent = 0; int tabIndent = 0; boolean mixed = false; - char[] chars = space.substring(ni).toCharArray(); - for (char c : chars) { + for (int i = ni; i < space.length(); i++) { + char c = space.charAt(i); if (c == ' ') { if (tabIndent > 0) { mixed = true; @@ -843,11 +843,9 @@ private String longestCommonPrefix(String pkg, @Nullable String lcp) { return pkg; } - char[] p1 = pkg.toCharArray(); - char[] p2 = lcp.toCharArray(); int i = 0; - for (; i < p1.length && i < p2.length; i++) { - if (p1[i] != p2[i]) { + for (; i < pkg.length() && i < lcp.length(); i++) { + if (pkg.charAt(i) != lcp.charAt(i)) { break; } } From f9ad4f8be97b2ffb9a7fe3e3b0351d177ff050f6 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Thu, 21 Sep 2023 22:04:09 +0200 Subject: [PATCH 255/447] Show warnings in `MavenDownloadingExceptions` previously in `UncheckedMavenDownloadingException` (#3557) * Warn for exceptions in MavenDownloadingExceptions * Drop unused UncheckedMavenDownloadingException.java * Suppress unused warning * Show warnings in message of ParseExceptionResult * Minimize changes --- .../org/openrewrite/maven/MavenParser.java | 12 +++- .../UncheckedMavenDownloadingException.java | 42 ------------ .../maven/MavenDependencyFailuresTest.java | 64 +++++++++---------- 3 files changed, 43 insertions(+), 75 deletions(-) delete mode 100755 rewrite-maven/src/main/java/org/openrewrite/maven/UncheckedMavenDownloadingException.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java index 910c980721d..8e1209e0c9c 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java @@ -106,7 +106,16 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re MavenResolutionResult model = new MavenResolutionResult(randomId(), null, resolvedPom, emptyList(), null, emptyMap(), sanitizedSettings, mavenCtx.getActiveProfiles()) .resolveDependencies(downloader, ctx); parsed.add(docToPom.getKey().withMarkers(docToPom.getKey().getMarkers().compute(model, (old, n) -> n))); - } catch (MavenDownloadingExceptions | MavenDownloadingException e) { + } catch (MavenDownloadingExceptions e) { + ParseExceptionResult parseExceptionResult = new ParseExceptionResult( + randomId(), + MavenParser.class.getSimpleName(), + e.getClass().getSimpleName(), + e.warn(docToPom.getKey()).printAll(), // Shows any underlying MavenDownloadingException + null); + parsed.add(docToPom.getKey().withMarkers(docToPom.getKey().getMarkers().add(parseExceptionResult))); + ctx.getOnError().accept(e); + } catch (MavenDownloadingException e) { parsed.add(docToPom.getKey().withMarkers(docToPom.getKey().getMarkers().add(ParseExceptionResult.build(this, e)))); ctx.getOnError().accept(e); } @@ -171,6 +180,7 @@ public Builder activeProfiles(@Nullable String... profiles) { return this; } + @SuppressWarnings("unused") // Used in `MavenMojoProjectParser.parseMaven(..)` public Builder mavenConfig(@Nullable Path mavenConfig) { if (mavenConfig != null && mavenConfig.toFile().exists()) { try { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UncheckedMavenDownloadingException.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UncheckedMavenDownloadingException.java deleted file mode 100755 index 121bffe719e..00000000000 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/UncheckedMavenDownloadingException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.maven; - -import lombok.Getter; -import org.openrewrite.xml.tree.Xml; - -@Getter -public class UncheckedMavenDownloadingException extends RuntimeException { - private final Xml.Document pomWithWarnings; - - public UncheckedMavenDownloadingException(Xml.Document pom, MavenDownloadingExceptions e) { - super("Failed to download dependencies for " + pom.getSourcePath() + ":\n" + - warn(pom, e).printAllTrimmed(), e); - this.pomWithWarnings = warn(pom, e); - } - - public UncheckedMavenDownloadingException(Xml.Document pom, MavenDownloadingException e) { - this(pom, MavenDownloadingExceptions.append(null, e)); - } - - private static Xml.Document warn(Xml.Document pom, MavenDownloadingExceptions mde) { - try { - return mde.warn(pom); - } catch (Exception e) { - return pom; - } - } -} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenDependencyFailuresTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenDependencyFailuresTest.java index 73916a5317c..c6addd04f5f 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenDependencyFailuresTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenDependencyFailuresTest.java @@ -217,38 +217,38 @@ void unresolvableTransitiveDependency(@TempDir Path localRepository) throws IOEx @Test void unresolvableDependency() { - rewriteRun( - spec -> spec.executionContext(new InMemoryExecutionContext()), - pomXml( - """ - <project> - <groupId>com.mycompany.app</groupId> - <artifactId>my-app</artifactId> - <version>1</version> - <dependencies> - <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - <version>doesnotexist</version> - </dependency> - <dependency> - <groupId>com.google.another</groupId> - <artifactId>${doesnotexist}</artifactId> - </dependency> - <dependency> - <groupId>com.google.yetanother</groupId> - <artifactId>${doesnotexist}</artifactId> - <version>1</version> - </dependency> - </dependencies> - </project> - """, - spec -> spec.afterRecipe(after -> { - Optional<ParseExceptionResult> maybeParseException = after.getMarkers().findFirst(ParseExceptionResult.class); - assertThat(maybeParseException).isPresent(); - }) - ) - ); + rewriteRun( + spec -> spec.executionContext(new InMemoryExecutionContext()), + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <dependencies> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>doesnotexist</version> + </dependency> + <dependency> + <groupId>com.google.another</groupId> + <artifactId>${doesnotexist}</artifactId> + </dependency> + <dependency> + <groupId>com.google.yetanother</groupId> + <artifactId>${doesnotexist}</artifactId> + <version>1</version> + </dependency> + </dependencies> + </project> + """, + spec -> spec.afterRecipe(after -> { + Optional<ParseExceptionResult> maybeParseException = after.getMarkers().findFirst(ParseExceptionResult.class); + assertThat(maybeParseException).hasValueSatisfying(per -> assertThat(per.getMessage()).contains("Unable to download POM. Tried repositories")); + }) + ) + ); } @Test From f27cd1bc377d66d454aaee5618a34e7517c7daf3 Mon Sep 17 00:00:00 2001 From: Kun Li <122563761+kunli2@users.noreply.github.com> Date: Thu, 21 Sep 2023 14:34:50 -0700 Subject: [PATCH 256/447] Update ChangeType recipe to support kotlin alias import (#3558) * Update ChangeType recipe to support kotlin alias import --- .../java/org/openrewrite/java/ChangeType.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java index 09701306db2..8290fb9438a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java @@ -15,10 +15,12 @@ */ package org.openrewrite.java; +import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.NonNull; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.*; @@ -85,6 +87,8 @@ public J visit(@Nullable Tree tree, ExecutionContext ctx) { private static class ChangeTypeVisitor extends JavaVisitor<ExecutionContext> { private final JavaType.Class originalType; private final JavaType targetType; + @Nullable + private J.Identifier importAlias; @Nullable private final Boolean ignoreDefinition; @@ -96,6 +100,7 @@ private ChangeTypeVisitor(String oldFullyQualifiedTypeName, String newFullyQuali this.originalType = JavaType.ShallowClass.build(oldFullyQualifiedTypeName); this.targetType = JavaType.buildType(newFullyQualifiedTypeName); this.ignoreDefinition = ignoreDefinition; + importAlias = null; } @Override @@ -125,6 +130,14 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct @Override public J visitImport(J.Import import_, ExecutionContext ctx) { + // Just Collect kotlin alias for original type here + if (import_.getAlias() != null) { + if (hasSameFQN(import_, originalType)) { + // collect Kotlin alias + importAlias = import_.getAlias(); + } + } + // visitCompilationUnit() handles changing the imports. // If we call super.visitImport() then visitFieldAccess() will change the imports before AddImport/RemoveImport see them. // visitFieldAccess() doesn't have the import-specific formatting logic that AddImport/RemoveImport do. @@ -180,6 +193,7 @@ public J visitImport(J.Import import_, ExecutionContext ctx) { if (!"java.lang".equals(fullyQualifiedTarget.getPackageName())) { maybeAddImport(fullyQualifiedTarget.getPackageName(), fullyQualifiedTarget.getClassName(), null, true); } + maybeUpdateAlias(); } } @@ -279,6 +293,7 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) if (fqn != null && TypeUtils.isOfClassType(fqn, originalType.getFullyQualifiedName()) && method.getSimpleName().equals(anImport.getQualid().getSimpleName())) { maybeAddImport(((JavaType.FullyQualified) targetType).getFullyQualifiedName(), method.getName().getSimpleName()); + maybeUpdateAlias(); break; } } @@ -288,6 +303,15 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) return super.visitMethodInvocation(method, ctx); } + private void maybeUpdateAlias() { + if (importAlias != null) { + UpdateImportAlias visitor = new UpdateImportAlias(targetType, importAlias); + if (!getAfterVisit().contains(visitor)) { + doAfterVisit(visitor); + } + } + } + private Expression updateOuterClassTypes(Expression typeTree) { if (typeTree instanceof J.FieldAccess) { JavaType.FullyQualified type = (JavaType.FullyQualified) targetType; @@ -437,6 +461,25 @@ private boolean isTargetFullyQualifiedType(@Nullable JavaType.FullyQualified fq) } } + + + // Visitor to backfill the missing kotlin alias import + @AllArgsConstructor + private static class UpdateImportAlias extends JavaIsoVisitor<ExecutionContext> { + @NonNull + private final JavaType targetType; + @NonNull + private J.Identifier importAlias; + + @Override + public J.Import visitImport(J.Import _import, ExecutionContext executionContext) { + if (hasSameFQN(_import, targetType)) { + return _import.withAlias(importAlias); + } + return _import; + } + } + private static class ChangeClassDefinition extends JavaIsoVisitor<ExecutionContext> { private final JavaType.Class originalType; private final JavaType.Class targetType; @@ -590,4 +633,14 @@ public static JavaType.FullyQualified getTopLevelClassName(JavaType.FullyQualifi } return getTopLevelClassName(classType.getOwningClass()); } + + public static boolean hasSameFQN(J.Import import_, JavaType targetType) { + JavaType.FullyQualified type = TypeUtils.asFullyQualified(targetType); + String fqn = type != null ? type.getFullyQualifiedName() : null; + + JavaType.FullyQualified curType = TypeUtils.asFullyQualified(Optional.ofNullable(import_.getQualid()).map(J.FieldAccess::getType).orElse(null)); + String curFqn = curType != null ? curType.getFullyQualifiedName() : null; + + return fqn != null && fqn.equals(curFqn); + } } From 80712b66e057ebad30f8dc9a7c889ce575c5cc9f Mon Sep 17 00:00:00 2001 From: Kun Li <122563761+kunli2@users.noreply.github.com> Date: Fri, 22 Sep 2023 14:46:01 -0700 Subject: [PATCH 257/447] Update AddImport with alias to support Kotlin alias import (#3560) * Update AddImport with alias to support Kotlin alias import * optimization --- .../org/openrewrite/java/AddImportTest.java | 2 +- .../java/org/openrewrite/java/AddImport.java | 8 +- .../java/org/openrewrite/java/ChangeType.java | 81 ++++++++++--------- .../org/openrewrite/java/JavaVisitor.java | 6 +- .../java/service/ImportService.java | 8 +- 5 files changed, 60 insertions(+), 45 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java index 27dd0f13052..9636a2879b8 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java @@ -536,7 +536,7 @@ class A {} ); rewriteRun( - spec -> spec.recipe(toRecipe(() -> new AddImport<>(pkg, "B", null, false))), + spec -> spec.recipe(toRecipe(() -> new AddImport<>(pkg, "B", null, null, false))), sources.toArray(new SourceSpecs[0]) ); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java b/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java index e27df64a85a..7a7ca03c0b2 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/AddImport.java @@ -72,6 +72,10 @@ public class AddImport<P> extends JavaIsoVisitor<P> { @EqualsAndHashCode.Include private final boolean onlyIfReferenced; + @EqualsAndHashCode.Include + @Nullable + private final String alias; + public AddImport(String type, @Nullable String member, boolean onlyIfReferenced) { int lastDotIdx = type.lastIndexOf('.'); this.packageName = lastDotIdx != -1 ? type.substring(0, lastDotIdx) : null; @@ -79,14 +83,16 @@ public AddImport(String type, @Nullable String member, boolean onlyIfReferenced) this.fullyQualifiedName = type; this.member = member; this.onlyIfReferenced = onlyIfReferenced; + alias = null; } - public AddImport(@Nullable String packageName, String typeName, @Nullable String member, boolean onlyIfReferenced) { + public AddImport(@Nullable String packageName, String typeName, @Nullable String member, @Nullable String alias, boolean onlyIfReferenced) { this.packageName = packageName; this.typeName = typeName.replace('.', '$'); this.fullyQualifiedName = packageName == null ? typeName : packageName + "." + typeName; this.member = member; this.onlyIfReferenced = onlyIfReferenced; + this.alias = alias; } @Override diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java index 8290fb9438a..1daf64b6196 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java @@ -20,7 +20,6 @@ import lombok.Value; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.NonNull; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.*; @@ -89,6 +88,8 @@ private static class ChangeTypeVisitor extends JavaVisitor<ExecutionContext> { private final JavaType targetType; @Nullable private J.Identifier importAlias; + private boolean hasImportWithoutAlias; + private boolean isKotlinSource; @Nullable private final Boolean ignoreDefinition; @@ -101,11 +102,16 @@ private ChangeTypeVisitor(String oldFullyQualifiedTypeName, String newFullyQuali this.targetType = JavaType.buildType(newFullyQualifiedTypeName); this.ignoreDefinition = ignoreDefinition; importAlias = null; + hasImportWithoutAlias = false; + isKotlinSource = false; } @Override public J visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree instanceof JavaSourceFile) { + if (tree.getClass().getName().equals("org.openrewrite.kotlin.tree.K$CompilationUnit")) { + isKotlinSource = true; + } JavaSourceFile cu = (JavaSourceFile) requireNonNull(tree); if (!Boolean.TRUE.equals(ignoreDefinition)) { JavaType.FullyQualified fq = TypeUtils.asFullyQualified(targetType); @@ -130,11 +136,16 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct @Override public J visitImport(J.Import import_, ExecutionContext ctx) { - // Just Collect kotlin alias for original type here - if (import_.getAlias() != null) { + if (isKotlinSource) { + // Collect kotlin alias information here + // If there is an existing import with an alias, we need to add a target import with an alias accordingly. + // If there is an existing import without an alias, we need to add a target import with an alias accordingly. if (hasSameFQN(import_, originalType)) { - // collect Kotlin alias - importAlias = import_.getAlias(); + if (import_.getAlias() != null) { + importAlias = import_.getAlias(); + } else { + hasImportWithoutAlias = true; + } } } @@ -149,6 +160,16 @@ public J visitImport(J.Import import_, ExecutionContext ctx) { return updateType(javaType); } + private void maybeAddKotlinImport(JavaType.FullyQualified owningClass) { + if (importAlias != null) { + maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, importAlias.getSimpleName(), true); + } + + if (hasImportWithoutAlias) { + maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, null, true); + } + } + @Override public @Nullable J postVisit(J tree, ExecutionContext ctx) { J j = super.postVisit(tree, ctx); @@ -188,12 +209,19 @@ public J visitImport(J.Import import_, ExecutionContext ctx) { JavaType.FullyQualified owningClass = fullyQualifiedTarget.getOwningClass(); if (!(owningClass != null && topLevelClassnames.contains(getTopLevelClassName(fullyQualifiedTarget).getFullyQualifiedName()))) { if (owningClass != null && !"java.lang".equals(fullyQualifiedTarget.getPackageName())) { - maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, true); + if (isKotlinSource) { + maybeAddKotlinImport(owningClass); + } else { + maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, null, true); + } } if (!"java.lang".equals(fullyQualifiedTarget.getPackageName())) { - maybeAddImport(fullyQualifiedTarget.getPackageName(), fullyQualifiedTarget.getClassName(), null, true); + if (isKotlinSource) { + maybeAddKotlinImport(fullyQualifiedTarget); + } else { + maybeAddImport(fullyQualifiedTarget.getPackageName(), fullyQualifiedTarget.getClassName(), null, null, true); + } } - maybeUpdateAlias(); } } @@ -292,8 +320,13 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) JavaType.FullyQualified fqn = TypeUtils.asFullyQualified(anImport.getQualid().getTarget().getType()); if (fqn != null && TypeUtils.isOfClassType(fqn, originalType.getFullyQualifiedName()) && method.getSimpleName().equals(anImport.getQualid().getSimpleName())) { - maybeAddImport(((JavaType.FullyQualified) targetType).getFullyQualifiedName(), method.getName().getSimpleName()); - maybeUpdateAlias(); + JavaType.FullyQualified targetFqn = (JavaType.FullyQualified) targetType; + + if (isKotlinSource) { + maybeAddKotlinImport(targetFqn); + } else { + maybeAddImport((targetFqn).getFullyQualifiedName(), method.getName().getSimpleName()); + } break; } } @@ -303,15 +336,6 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) return super.visitMethodInvocation(method, ctx); } - private void maybeUpdateAlias() { - if (importAlias != null) { - UpdateImportAlias visitor = new UpdateImportAlias(targetType, importAlias); - if (!getAfterVisit().contains(visitor)) { - doAfterVisit(visitor); - } - } - } - private Expression updateOuterClassTypes(Expression typeTree) { if (typeTree instanceof J.FieldAccess) { JavaType.FullyQualified type = (JavaType.FullyQualified) targetType; @@ -461,25 +485,6 @@ private boolean isTargetFullyQualifiedType(@Nullable JavaType.FullyQualified fq) } } - - - // Visitor to backfill the missing kotlin alias import - @AllArgsConstructor - private static class UpdateImportAlias extends JavaIsoVisitor<ExecutionContext> { - @NonNull - private final JavaType targetType; - @NonNull - private J.Identifier importAlias; - - @Override - public J.Import visitImport(J.Import _import, ExecutionContext executionContext) { - if (hasSameFQN(_import, targetType)) { - return _import.withAlias(importAlias); - } - return _import; - } - } - private static class ChangeClassDefinition extends JavaIsoVisitor<ExecutionContext> { private final JavaType.Class originalType; private final JavaType.Class targetType; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index e8667897c50..07b456c7cd5 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -124,11 +124,11 @@ public void maybeAddImport(String fullyQualifiedName, @Nullable String member, b int lastDotIdx = fullyQualifiedName.lastIndexOf('.'); String packageName = lastDotIdx != -1 ? fullyQualifiedName.substring(0, lastDotIdx) : null; String typeName = lastDotIdx != -1 ? fullyQualifiedName.substring(lastDotIdx + 1) : fullyQualifiedName; - maybeAddImport(packageName, typeName, member, onlyIfReferenced); + maybeAddImport(packageName, typeName, member, null, onlyIfReferenced); } - public void maybeAddImport(@Nullable String packageName, String typeName, @Nullable String member, boolean onlyIfReferenced) { - JavaVisitor<P> visitor = service(ImportService.class).addImportVisitor(packageName, typeName, member, onlyIfReferenced); + public void maybeAddImport(@Nullable String packageName, String typeName, @Nullable String member, @Nullable String alias, boolean onlyIfReferenced) { + JavaVisitor<P> visitor = service(ImportService.class).addImportVisitor(packageName, typeName, member, alias, onlyIfReferenced); if (!getAfterVisit().contains(visitor)) { doAfterVisit(visitor); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java b/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java index 6b854638a1f..c2a8a2d42ac 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java @@ -23,7 +23,11 @@ @Incubating(since = "8.2.0") public class ImportService { - public <P> JavaVisitor<P> addImportVisitor(@Nullable String packageName, String typeName, @Nullable String member, boolean onlyIfReferenced) { - return new AddImport<>(packageName, typeName, member, onlyIfReferenced); + public <P> JavaVisitor<P> addImportVisitor(@Nullable String packageName, + String typeName, + @Nullable String member, + @Nullable String alias, + boolean onlyIfReferenced) { + return new AddImport<>(packageName, typeName, member, alias, onlyIfReferenced); } } From 45487362be7b4e5703b9ad7ea5dcc654fff0a40c Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Fri, 22 Sep 2023 18:41:55 -0700 Subject: [PATCH 258/447] Update ChangeType's fix to not limit to Kotlin but more generic --- .../java/org/openrewrite/java/ChangeType.java | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java index 1daf64b6196..8f77687c450 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java @@ -89,7 +89,6 @@ private static class ChangeTypeVisitor extends JavaVisitor<ExecutionContext> { @Nullable private J.Identifier importAlias; private boolean hasImportWithoutAlias; - private boolean isKotlinSource; @Nullable private final Boolean ignoreDefinition; @@ -103,15 +102,11 @@ private ChangeTypeVisitor(String oldFullyQualifiedTypeName, String newFullyQuali this.ignoreDefinition = ignoreDefinition; importAlias = null; hasImportWithoutAlias = false; - isKotlinSource = false; } @Override public J visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree instanceof JavaSourceFile) { - if (tree.getClass().getName().equals("org.openrewrite.kotlin.tree.K$CompilationUnit")) { - isKotlinSource = true; - } JavaSourceFile cu = (JavaSourceFile) requireNonNull(tree); if (!Boolean.TRUE.equals(ignoreDefinition)) { JavaType.FullyQualified fq = TypeUtils.asFullyQualified(targetType); @@ -136,16 +131,14 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct @Override public J visitImport(J.Import import_, ExecutionContext ctx) { - if (isKotlinSource) { - // Collect kotlin alias information here - // If there is an existing import with an alias, we need to add a target import with an alias accordingly. - // If there is an existing import without an alias, we need to add a target import with an alias accordingly. - if (hasSameFQN(import_, originalType)) { - if (import_.getAlias() != null) { - importAlias = import_.getAlias(); - } else { - hasImportWithoutAlias = true; - } + // Collect alias import information here + // If there is an existing import with an alias, we need to add a target import with an alias accordingly. + // If there is an existing import without an alias, we need to add a target import with an alias accordingly. + if (hasSameFQN(import_, originalType)) { + if (import_.getAlias() != null) { + importAlias = import_.getAlias(); + } else { + hasImportWithoutAlias = true; } } @@ -160,7 +153,7 @@ public J visitImport(J.Import import_, ExecutionContext ctx) { return updateType(javaType); } - private void maybeAddKotlinImport(JavaType.FullyQualified owningClass) { + private void addImport(JavaType.FullyQualified owningClass) { if (importAlias != null) { maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, importAlias.getSimpleName(), true); } @@ -209,18 +202,10 @@ private void maybeAddKotlinImport(JavaType.FullyQualified owningClass) { JavaType.FullyQualified owningClass = fullyQualifiedTarget.getOwningClass(); if (!(owningClass != null && topLevelClassnames.contains(getTopLevelClassName(fullyQualifiedTarget).getFullyQualifiedName()))) { if (owningClass != null && !"java.lang".equals(fullyQualifiedTarget.getPackageName())) { - if (isKotlinSource) { - maybeAddKotlinImport(owningClass); - } else { - maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, null, true); - } + addImport(owningClass); } if (!"java.lang".equals(fullyQualifiedTarget.getPackageName())) { - if (isKotlinSource) { - maybeAddKotlinImport(fullyQualifiedTarget); - } else { - maybeAddImport(fullyQualifiedTarget.getPackageName(), fullyQualifiedTarget.getClassName(), null, null, true); - } + addImport(fullyQualifiedTarget); } } } @@ -322,11 +307,8 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) method.getSimpleName().equals(anImport.getQualid().getSimpleName())) { JavaType.FullyQualified targetFqn = (JavaType.FullyQualified) targetType; - if (isKotlinSource) { - maybeAddKotlinImport(targetFqn); - } else { - maybeAddImport((targetFqn).getFullyQualifiedName(), method.getName().getSimpleName()); - } + addImport(targetFqn); + maybeAddImport((targetFqn).getFullyQualifiedName(), method.getName().getSimpleName()); break; } } @@ -639,7 +621,7 @@ public static JavaType.FullyQualified getTopLevelClassName(JavaType.FullyQualifi return getTopLevelClassName(classType.getOwningClass()); } - public static boolean hasSameFQN(J.Import import_, JavaType targetType) { + private static boolean hasSameFQN(J.Import import_, JavaType targetType) { JavaType.FullyQualified type = TypeUtils.asFullyQualified(targetType); String fqn = type != null ? type.getFullyQualifiedName() : null; From bf8b83c13533da5670ff3b491d0c6a199d827ad2 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sun, 24 Sep 2023 13:46:33 -0400 Subject: [PATCH 259/447] Defer classpath scanning in GradleParser until parse time This allows the parser to be used for source file acceptance matching without triggering Classgraph scanning. --- .../org/openrewrite/gradle/GradleParser.java | 97 +++++++++++-------- .../org/openrewrite/maven/MavenSettings.java | 35 ++++++- .../openrewrite/maven/MavenSettingsTest.java | 43 ++++++++ 3 files changed, 133 insertions(+), 42 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/GradleParser.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/GradleParser.java index 5af96a2260f..d7fb61a8727 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/GradleParser.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/GradleParser.java @@ -15,6 +15,7 @@ */ package org.openrewrite.gradle; +import lombok.RequiredArgsConstructor; import org.openrewrite.ExecutionContext; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Parser; @@ -32,30 +33,43 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +@RequiredArgsConstructor public class GradleParser implements Parser { - private final GroovyParser buildParser; - private final GroovyParser settingsParser; - - private GradleParser(Builder builder) { - GroovyParser.Builder base = builder.groovyParser; - this.buildParser = GroovyParser.builder(base) - .classpath(builder.buildscriptClasspath) - .compilerCustomizers( - new DefaultImportsCustomizer(), - config -> config.setScriptBaseClass("RewriteGradleProject") - ) - .build(); - this.settingsParser = GroovyParser.builder(base) - .classpath(builder.settingsClasspath) - .compilerCustomizers( - new DefaultImportsCustomizer(), - config -> config.setScriptBaseClass("RewriteSettings") - ) - .build(); - } + private final GradleParser.Builder base; + + private Collection<Path> defaultClasspath; + private GroovyParser buildParser; + private GroovyParser settingsParser; @Override public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path relativeTo, ExecutionContext ctx) { + if (buildParser == null) { + if (base.buildscriptClasspath == null && defaultClasspath == null) { + defaultClasspath = loadDefaultClasspath(); + base.buildscriptClasspath = defaultClasspath; + } + buildParser = GroovyParser.builder(base.groovyParser) + .classpath(base.buildscriptClasspath) + .compilerCustomizers( + new DefaultImportsCustomizer(), + config -> config.setScriptBaseClass("RewriteGradleProject") + ) + .build(); + } + if (settingsParser == null) { + if (base.settingsClasspath == null && defaultClasspath == null) { + defaultClasspath = loadDefaultClasspath(); + base.settingsClasspath = defaultClasspath; + } + settingsParser = GroovyParser.builder(base.groovyParser) + .classpath(base.settingsClasspath) + .compilerCustomizers( + new DefaultImportsCustomizer(), + config -> config.setScriptBaseClass("RewriteSettings") + ) + .build(); + } + return StreamSupport.stream(sources.spliterator(), false) .flatMap(source -> { if (source.getPath().endsWith("settings.gradle")) { @@ -83,10 +97,10 @@ public static class Builder extends Parser.Builder { protected GroovyParser.Builder groovyParser = GroovyParser.builder(); @Nullable - private Collection<Path> buildscriptClasspath = loadDefaultClasspath(); + private Collection<Path> buildscriptClasspath; @Nullable - private Collection<Path> settingsClasspath = loadDefaultClasspath(); + private Collection<Path> settingsClasspath; public Builder() { super(G.CompilationUnit.class); @@ -136,25 +150,26 @@ public String getDslName() { return "gradle"; } - private static List<Path> loadDefaultClasspath() { - try { - Class.forName("org.gradle.api.Project"); - return JavaParser.runtimeClasspath(); - } catch (ClassNotFoundException e) { - return JavaParser.dependenciesFromResources(new InMemoryExecutionContext(), - "gradle-base-services", - "gradle-core-api", - "gradle-language-groovy", - "gradle-language-java", - "gradle-logging", - "gradle-messaging", - "gradle-native", - "gradle-process-services", - "gradle-resources", - "gradle-testing-base", - "gradle-testing-jvm", - "gradle-enterprise-gradle-plugin"); - } + } + + private static List<Path> loadDefaultClasspath() { + try { + Class.forName("org.gradle.api.Project"); + return JavaParser.runtimeClasspath(); + } catch (ClassNotFoundException e) { + return JavaParser.dependenciesFromResources(new InMemoryExecutionContext(), + "gradle-base-services", + "gradle-core-api", + "gradle-language-groovy", + "gradle-language-java", + "gradle-logging", + "gradle-messaging", + "gradle-native", + "gradle-process-services", + "gradle-resources", + "gradle-testing-base", + "gradle-testing-jvm", + "gradle-enterprise-gradle-plugin"); } } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSettings.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSettings.java index 99873a65853..5286e8b7c37 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSettings.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenSettings.java @@ -231,8 +231,22 @@ private Servers interpolate(@Nullable Servers servers) { return new Servers(ListUtils.map(servers.getServers(), this::interpolate)); } + @Nullable + private ServerConfiguration interpolate(@Nullable ServerConfiguration configuration) { + if (configuration == null) { + return null; + } + return new ServerConfiguration(configuration.httpHeaders == null ? null : + ListUtils.map(configuration.httpHeaders, this::interpolate)); + } + + private HttpHeader interpolate(HttpHeader httpHeader) { + return new HttpHeader(interpolate(httpHeader.getName()), interpolate(httpHeader.getValue())); + } + private Server interpolate(Server server) { - return new Server(interpolate(server.id), interpolate(server.username), interpolate(server.password)); + return new Server(interpolate(server.id), interpolate(server.username), interpolate(server.password), + interpolate(server.configuration)); } @Nullable @@ -382,5 +396,24 @@ public static class Server { String username; String password; + + @Nullable + ServerConfiguration configuration; + } + + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) + @Data + @With + public static class ServerConfiguration { + @Nullable + List<HttpHeader> httpHeaders; + } + + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) + @Data + @With + public static class HttpHeader { + String name; + String value; } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenSettingsTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenSettingsTest.java index ecefe8ea7d5..5ca2035a69a 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenSettingsTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenSettingsTest.java @@ -659,4 +659,47 @@ void replacesElementsWithMatchingIds() { .hasFieldOrPropertyWithValue("password", null); } } + + /** + * See the <a href="https://maven.apache.org/guides/mini/guide-http-settings.html#Taking_Control_of_Your_HTTP_Headers">Maven guide</a> + * on HTTP headers. + */ + @Test + void serverHttpHeaders() { + var settings = MavenSettings.parse(new Parser.Input(Paths.get("settings.xml"), () -> new ByteArrayInputStream( + //language=xml + """ + <settings> + <servers> + <server> + <id>maven-snapshots</id> + <configuration> + <httpHeaders> + <property> + <name>X-JFrog-Art-Api</name> + <value>myApiToken</value> + </property> + </httpHeaders> + </configuration> + </server> + </servers> + <profiles> + <profile> + <id>my-profile</id> + <repositories> + <repository> + <id>maven-snapshots</id> + <name>Private Repo</name> + <url>https://repo.company.net/maven</url> + </repository> + </repositories> + </profile> + </profiles> + </settings> + """.getBytes() + )), new InMemoryExecutionContext()); + + MavenSettings.Server server = settings.getServers().getServers().get(0); + assertThat(server.getConfiguration().getHttpHeaders().get(0).getName()).isEqualTo("X-JFrog-Art-Api"); + } } From 4d74818d9d76eed72af6fc57b937ee6913ebfc57 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sun, 24 Sep 2023 14:27:39 -0400 Subject: [PATCH 260/447] Parse Jenkinsfile as Groovy --- .../src/main/java/org/openrewrite/groovy/GroovyParser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java index c8d86e4848e..f9ed2f83e89 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java @@ -180,7 +180,8 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re @Override public boolean accept(Path path) { - return path.toString().endsWith(".groovy"); + return path.toString().endsWith(".groovy") || + path.toFile().getName().equals("Jenkinsfile"); } @Override From 55634c987a1b08f8a8c9ac993ce5dada67ee25ec Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sun, 24 Sep 2023 15:37:56 -0400 Subject: [PATCH 261/447] GroovyParser handles Jenkinsfile, and fix inside parentheses of ternary/binary/literal --- .../groovy/GroovyParserVisitor.java | 225 +++++++++--------- .../openrewrite/groovy/GroovyParserTest.java | 122 ++++++++++ .../openrewrite/groovy/tree/BinaryTest.java | 13 + .../openrewrite/groovy/tree/LiteralTest.java | 9 + .../groovy/tree/ParenthesisTest.java | 56 ----- .../openrewrite/groovy/tree/TernaryTest.java | 19 +- 6 files changed, 269 insertions(+), 175 deletions(-) create mode 100644 rewrite-groovy/src/test/java/org/openrewrite/groovy/GroovyParserTest.java delete mode 100644 rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ParenthesisTest.java diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index 8b85a276346..2e6b59f5975 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -35,16 +35,17 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.marker.ImplicitReturn; +import org.openrewrite.java.marker.Semicolon; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.Statement; import org.openrewrite.java.tree.*; -import org.openrewrite.java.marker.Semicolon; import org.openrewrite.marker.Markers; import java.math.BigDecimal; import java.nio.charset.Charset; import java.nio.file.Path; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -69,7 +70,6 @@ public class GroovyParserVisitor { private final Charset charset; private final boolean charsetBomMarked; private final GroovyTypeMapping typeMapping; - private final ExecutionContext ctx; private int cursor = 0; @@ -83,6 +83,7 @@ public class GroovyParserVisitor { */ private int columnOffset; + @SuppressWarnings("unused") public GroovyParserVisitor(Path sourcePath, @Nullable FileAttributes fileAttributes, EncodingDetectingInputStream source, JavaTypeCache typeCache, ExecutionContext ctx) { this.sourcePath = sourcePath; this.fileAttributes = fileAttributes; @@ -90,7 +91,6 @@ public GroovyParserVisitor(Path sourcePath, @Nullable FileAttributes fileAttribu this.charset = source.getCharset(); this.charsetBomMarked = source.isCharsetBomMarked(); this.typeMapping = new GroovyTypeMapping(typeCache); - this.ctx = ctx; } public G.CompilationUnit visit(SourceUnit unit, ModuleNode ast) throws GroovyParsingException { @@ -581,10 +581,21 @@ private <T> List<JRightPadded<T>> visitRightPadded(ASTNode[] nodes, @Nullable St return ts; } - private void visitParenthesized(ASTNode node, Space fmt) { - skip("("); - queue.add(new J.Parentheses<Expression>(randomId(), fmt, Markers.EMPTY, - convert(node, t -> sourceBefore(")")))); + private Expression insideParentheses(ASTNode node, Function<Space, Expression> parenthesizedTree) { + AtomicInteger insideParenthesesLevel = node.getNodeMetaData("_INSIDE_PARENTHESES_LEVEL"); + if (insideParenthesesLevel != null) { + Stack<Space> openingParens = new Stack<>(); + for (int i = 0; i < insideParenthesesLevel.get(); i++) { + openingParens.push(sourceBefore("(")); + } + Expression parenthesized = parenthesizedTree.apply(whitespace()); + for (int i = 0; i < insideParenthesesLevel.get(); i++) { + parenthesized = new J.Parentheses<>(randomId(), openingParens.pop(), Markers.EMPTY, + padRight(parenthesized, sourceBefore(")"))); + } + return parenthesized; + } + return parenthesizedTree.apply(whitespace()); } @Override @@ -690,19 +701,15 @@ public void visitClassExpression(ClassExpression clazz) { @Override public void visitBinaryExpression(BinaryExpression binary) { - Space fmt = whitespace(); - - if (source.charAt(cursor) == '(') { - visitParenthesized(binary, fmt); - } else { + queue.add(insideParentheses(binary, fmt -> { Expression left = visit(binary.getLeftExpression()); - Space opPrefix = whitespace(); boolean assignment = false; boolean instanceOf = false; J.AssignmentOperation.Type assignOp = null; J.Binary.Type binaryOp = null; G.Binary.Type gBinaryOp = null; + switch (binary.getOperation().getText()) { case "+": binaryOp = J.Binary.Type.Addition; @@ -818,31 +825,32 @@ public void visitBinaryExpression(BinaryExpression binary) { Expression right = visit(binary.getRightExpression()); if (assignment) { - queue.add(new J.Assignment(randomId(), fmt, Markers.EMPTY, + return new J.Assignment(randomId(), fmt, Markers.EMPTY, left, JLeftPadded.build(right).withBefore(opPrefix), - typeMapping.type(binary.getType()))); + typeMapping.type(binary.getType())); } else if (instanceOf) { - queue.add(new J.InstanceOf(randomId(), fmt, Markers.EMPTY, + return new J.InstanceOf(randomId(), fmt, Markers.EMPTY, JRightPadded.build(left).withAfter(opPrefix), right, null, - typeMapping.type(binary.getType()))); + typeMapping.type(binary.getType())); } else if (assignOp != null) { - queue.add(new J.AssignmentOperation(randomId(), fmt, Markers.EMPTY, + return new J.AssignmentOperation(randomId(), fmt, Markers.EMPTY, left, JLeftPadded.build(assignOp).withBefore(opPrefix), - right, typeMapping.type(binary.getType()))); + right, typeMapping.type(binary.getType())); } else if (binaryOp != null) { - queue.add(new J.Binary(randomId(), fmt, Markers.EMPTY, + return new J.Binary(randomId(), fmt, Markers.EMPTY, left, JLeftPadded.build(binaryOp).withBefore(opPrefix), - right, typeMapping.type(binary.getType()))); + right, typeMapping.type(binary.getType())); } else if (gBinaryOp != null) { Space after = EMPTY; if (gBinaryOp == G.Binary.Type.Access) { after = sourceBefore("]"); } - queue.add(new G.Binary(randomId(), fmt, Markers.EMPTY, + return new G.Binary(randomId(), fmt, Markers.EMPTY, left, JLeftPadded.build(gBinaryOp).withBefore(opPrefix), - right, after, typeMapping.type(binary.getType()))); + right, after, typeMapping.type(binary.getType())); } - } + throw new IllegalStateException("Unknown binary expression " + binary.getClass().getSimpleName()); + })); } @Override @@ -950,7 +958,7 @@ public void visitCaseStatement(CaseStatement statement) { null, JContainer.build(singletonList(JRightPadded.build(visit(statement.getExpression())))), JContainer.build(sourceBefore(":"), - convertStatements(((BlockStatement) statement.getCode()).getStatements(), t -> Space.EMPTY), Markers.EMPTY), + convertStatements(((BlockStatement) statement.getCode()).getStatements(), t -> Space.EMPTY), Markers.EMPTY), null ) ); @@ -996,7 +1004,7 @@ public void visitClosureExpression(ClosureExpression expression) { Space prefix = whitespace(); LambdaStyle ls = new LambdaStyle(randomId(), expression instanceof LambdaExpression, true); boolean parenthesized = false; - if(source.charAt(cursor) == '(') { + if (source.charAt(cursor) == '(') { parenthesized = true; cursor += 1; // skip '(' } else if (source.charAt(cursor) == '{') { @@ -1031,12 +1039,12 @@ null, emptyList(), } } else { Space argPrefix = EMPTY; - if(parenthesized) { + if (parenthesized) { argPrefix = whitespace(); } paramExprs = singletonList(JRightPadded.build(new J.Empty(randomId(), argPrefix, Markers.EMPTY))); } - if(parenthesized) { + if (parenthesized) { cursor += 1; } J.Lambda.Parameters params = new J.Lambda.Parameters(randomId(), EMPTY, Markers.EMPTY, parenthesized, paramExprs); @@ -1054,7 +1062,7 @@ null, emptyList(), arrowPrefix, body, closureType)); - if(cursor < source.length() && source.charAt(cursor) == '}') { + if (cursor < source.length() && source.charAt(cursor) == '}') { cursor++; } } @@ -1074,83 +1082,78 @@ public void visitClosureListExpression(ClosureListExpression closureListExpressi @Override public void visitConstantExpression(ConstantExpression expression) { - Space prefix = whitespace(); - - JavaType.Primitive jType; - // The unaryPlus is not included in the expression and must be handled through the source. - String text = expression.getText(); - Object value = expression.getValue(); - ClassNode type = expression.getType(); - if (type == ClassHelper.BigDecimal_TYPE) { - // TODO: Proper support for BigDecimal literals - jType = JavaType.Primitive.Double; - value = ((BigDecimal) value).doubleValue(); - } else if (type == ClassHelper.boolean_TYPE) { - jType = JavaType.Primitive.Boolean; - } else if (type == ClassHelper.byte_TYPE) { - jType = JavaType.Primitive.Byte; - } else if (type == ClassHelper.char_TYPE) { - jType = JavaType.Primitive.Char; - } else if (type == ClassHelper.double_TYPE || "java.lang.Double".equals(type.getName())) { - jType = JavaType.Primitive.Double; - if (expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT") instanceof String) { - text = (String) expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT"); - } - } else if (type == ClassHelper.float_TYPE || "java.lang.Float".equals(type.getName())) { - jType = JavaType.Primitive.Float; - if (expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT") instanceof String) { - text = (String) expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT"); - } - } else if (type == ClassHelper.int_TYPE || "java.lang.Integer".equals(type.getName())) { - jType = JavaType.Primitive.Int; - if (expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT") instanceof String) { - text = (String) expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT"); - } - } else if (type == ClassHelper.long_TYPE || "java.lang.Long".equals(type.getName())) { - if (expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT") instanceof String) { - text = (String) expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT"); - } - jType = JavaType.Primitive.Long; - } else if (type == ClassHelper.short_TYPE || "java.lang.Short".equals(type.getName())) { - jType = JavaType.Primitive.Short; - } else if (type == ClassHelper.STRING_TYPE) { - jType = JavaType.Primitive.String; - // String literals value returned by getValue()/getText() has already processed sequences like "\\" -> "\" - int length = sourceLengthOfNext(expression); - text = source.substring(cursor, cursor + length); - int delimiterLength = 0; - if (text.startsWith("$/")) { - delimiterLength = 2; - } else if (text.startsWith("\"\"\"") || text.startsWith("'''")) { - delimiterLength = 3; - } else if (text.startsWith("/") || text.startsWith("\"") || text.startsWith("'")) { - delimiterLength = 1; - } - value = text.substring(delimiterLength, text.length() - delimiterLength); - } else if (expression.isNullExpression()) { - if(source.startsWith("null", cursor)) { - text = "null"; + queue.add(insideParentheses(expression, fmt -> { + JavaType.Primitive jType; + // The unaryPlus is not included in the expression and must be handled through the source. + String text = expression.getText(); + Object value = expression.getValue(); + ClassNode type = expression.getType(); + if (type == ClassHelper.BigDecimal_TYPE) { + // TODO: Proper support for BigDecimal literals + jType = JavaType.Primitive.Double; + value = ((BigDecimal) value).doubleValue(); + } else if (type == ClassHelper.boolean_TYPE) { + jType = JavaType.Primitive.Boolean; + } else if (type == ClassHelper.byte_TYPE) { + jType = JavaType.Primitive.Byte; + } else if (type == ClassHelper.char_TYPE) { + jType = JavaType.Primitive.Char; + } else if (type == ClassHelper.double_TYPE || "java.lang.Double".equals(type.getName())) { + jType = JavaType.Primitive.Double; + if (expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT") instanceof String) { + text = (String) expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT"); + } + } else if (type == ClassHelper.float_TYPE || "java.lang.Float".equals(type.getName())) { + jType = JavaType.Primitive.Float; + if (expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT") instanceof String) { + text = (String) expression.getNodeMetaData().get("_FLOATING_POINT_LITERAL_TEXT"); + } + } else if (type == ClassHelper.int_TYPE || "java.lang.Integer".equals(type.getName())) { + jType = JavaType.Primitive.Int; + if (expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT") instanceof String) { + text = (String) expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT"); + } + } else if (type == ClassHelper.long_TYPE || "java.lang.Long".equals(type.getName())) { + if (expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT") instanceof String) { + text = (String) expression.getNodeMetaData().get("_INTEGER_LITERAL_TEXT"); + } + jType = JavaType.Primitive.Long; + } else if (type == ClassHelper.short_TYPE || "java.lang.Short".equals(type.getName())) { + jType = JavaType.Primitive.Short; + } else if (type == ClassHelper.STRING_TYPE) { + jType = JavaType.Primitive.String; + // String literals value returned by getValue()/getText() has already processed sequences like "\\" -> "\" + int length = sourceLengthOfNext(expression); + text = source.substring(cursor, cursor + length); + int delimiterLength = 0; + if (text.startsWith("$/")) { + delimiterLength = 2; + } else if (text.startsWith("\"\"\"") || text.startsWith("'''")) { + delimiterLength = 3; + } else if (text.startsWith("/") || text.startsWith("\"") || text.startsWith("'")) { + delimiterLength = 1; + } + value = text.substring(delimiterLength, text.length() - delimiterLength); + } else if (expression.isNullExpression()) { + if (source.startsWith("null", cursor)) { + text = "null"; + } else { + text = ""; + } + jType = JavaType.Primitive.Null; } else { - text = ""; + throw new IllegalStateException("Unexpected constant type " + type); } - jType = JavaType.Primitive.Null; - } else { - ctx.getOnError().accept(new IllegalStateException("Unexpected constant type " + type)); - return; - } - if (source.charAt(cursor) == '(') { - visitParenthesized(expression, prefix); - } else { if (source.charAt(cursor) == '+' && !text.startsWith("+")) { // A unaryPlus operator is implied on numerics and needs to be manually detected / added via the source. text = "+" + text; } cursor += text.length(); - queue.add(new J.Literal(randomId(), prefix, Markers.EMPTY, value, text, - null, jType)); - } + return new J.Literal(randomId(), fmt, Markers.EMPTY, value, text, + null, jType); + })); } @Override @@ -1374,8 +1377,8 @@ public void visitGStringExpression(GStringExpression gstring) { } else { columnOffset--; } - strings.add(new G.GString.Value(randomId(), Markers.EMPTY, visit(e), inCurlies ? sourceBefore("}") : Space.EMPTY, inCurlies)); - if(!inCurlies) { + strings.add(new G.GString.Value(randomId(), Markers.EMPTY, visit(e), inCurlies ? sourceBefore("}") : Space.EMPTY, inCurlies)); + if (!inCurlies) { columnOffset++; } } else if (e instanceof ConstantExpression) { @@ -1704,7 +1707,7 @@ public void visitSwitch(SwitchStatement statement) { randomId(), sourceBefore("{"), Markers.EMPTY, JRightPadded.build(false), ListUtils.concat( - convertAll(statement.getCaseStatements(), t -> Space.EMPTY, t -> Space.EMPTY), + convertAll(statement.getCaseStatements(), t -> Space.EMPTY, t -> Space.EMPTY), statement.getDefaultStatement().isEmpty() ? null : JRightPadded.build(visitDefaultCaseStatement((BlockStatement) statement.getDefaultStatement())) ), sourceBefore("}")))); @@ -1721,17 +1724,11 @@ public void visitSynchronizedStatement(SynchronizedStatement statement) { @Override public void visitTernaryExpression(TernaryExpression ternary) { - Space prefix = whitespace(); - - if (source.charAt(cursor) == '(') { - visitParenthesized(ternary, prefix); - } else { - queue.add(new J.Ternary(randomId(), prefix, Markers.EMPTY, - visit(ternary.getBooleanExpression()), - padLeft(sourceBefore("?"), visit(ternary.getTrueExpression())), - padLeft(sourceBefore(":"), visit(ternary.getFalseExpression())), - typeMapping.type(ternary.getType()))); - } + queue.add(insideParentheses(ternary, fmt -> new J.Ternary(randomId(), fmt, Markers.EMPTY, + visit(ternary.getBooleanExpression()), + padLeft(sourceBefore("?"), visit(ternary.getTrueExpression())), + padLeft(sourceBefore(":"), visit(ternary.getFalseExpression())), + typeMapping.type(ternary.getType())))); } @Override @@ -2136,9 +2133,9 @@ private <T extends TypeTree & Expression> T typeTree(@Nullable ClassNode classNo assert expr != null; if (classNode != null) { - if(classNode.isUsingGenerics() && !classNode.isGenericsPlaceHolder()) { + if (classNode.isUsingGenerics() && !classNode.isGenericsPlaceHolder()) { expr = new J.ParameterizedType(randomId(), EMPTY, Markers.EMPTY, (NameTree) expr, visitTypeParameterizations(classNode.getGenericsTypes()), typeMapping.type(classNode)); - } else if(classNode.isArray()) { + } else if (classNode.isArray()) { expr = new J.ArrayType(randomId(), EMPTY, Markers.EMPTY, (TypeTree) expr, arrayDimensionsFrom(classNode)); } } @@ -2147,7 +2144,7 @@ private <T extends TypeTree & Expression> T typeTree(@Nullable ClassNode classNo private List<JRightPadded<Space>> arrayDimensionsFrom(ClassNode classNode) { List<JRightPadded<Space>> result = new ArrayList<>(); - while(classNode != null && classNode.isArray()) { + while (classNode != null && classNode.isArray()) { classNode = classNode.getComponentType(); result.add(JRightPadded.build(sourceBefore("[")).withAfter(sourceBefore("]"))); } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/GroovyParserTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/GroovyParserTest.java new file mode 100644 index 00000000000..f86c2ba3e34 --- /dev/null +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/GroovyParserTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.groovy; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.groovy.Assertions.groovy; + +public class GroovyParserTest implements RewriteTest { + + @Test + void jenkinsfile() { + // the Jenkinsfile from spring-projects/spring-data-release + rewriteRun( + groovy( + """ + def p = [:] + node { + checkout scm + p = readProperties interpolate: true, file: 'ci/release.properties' + } + pipeline { + agent none + triggers { + pollSCM 'H/10 * * * *' + } + options { + disableConcurrentBuilds() + buildDiscarder(logRotator(numToKeepStr: '14')) + } + stages { + stage('Build the Spring Data release tools container') { + when { + anyOf { + changeset 'ci/Dockerfile' + changeset 'ci/java-tools.properties' + } + } + agent { + label 'data' + } + steps { + script { + def image = docker.build("springci/spring-data-release-tools:0.12", "ci") + docker.withRegistry('', 'hub.docker.com-springbuildmaster') { + image.push() + } + } + } + } + stage('Ship It') { + when { + branch 'release' + } + agent { + docker { + image 'springci/spring-data-release-tools:0.12' + } + } + options { timeout(time: 4, unit: 'HOURS') } + environment { + GITHUB = credentials('3a20bcaa-d8ad-48e3-901d-9fbc941376ee') + GITHUB_TOKEN = credentials('7b3ebbea-7001-479b-8578-b8c464dab973') + REPO_SPRING_IO = credentials('repo_spring_io-jenkins-release-token') + ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') + STAGING_PROFILE_ID = credentials('spring-data-release-deployment-maven-central-staging-profile-id') + MAVEN_SIGNING_KEY = credentials('spring-gpg-private-key') + MAVEN_SIGNING_KEY_PASSWORD = credentials('spring-gpg-passphrase') + GIT_SIGNING_KEY = credentials('spring-gpg-github-private-key-jenkins') + GIT_SIGNING_KEY_PASSWORD = credentials('spring-gpg-github-passphrase-jenkins') + SONATYPE = credentials('oss-login') + } + steps { + script { + sh "ci/build-spring-data-release-cli.bash" + sh "ci/build-and-distribute.bash ${p['release.version']}" + slackSend( + color: (currentBuild.currentResult == 'SUCCESS') ? 'good' : 'danger', + channel: '#spring-data-dev', + message: (currentBuild.currentResult == 'SUCCESS') + ? "`${env.BUILD_URL}` - Build and distribute ${p['release.version']} passed! Release the build (if needed)." + : "`${env.BUILD_URL}` - Build and distribute ${p['release.version']} failed!") + } + } + } + } + post { + changed { + script { + slackSend( + color: (currentBuild.currentResult == 'SUCCESS') ? 'good' : 'danger', + channel: '#spring-data-dev', + message: "${currentBuild.fullDisplayName} - `${currentBuild.currentResult}`\\n${env.BUILD_URL}") + emailext( + subject: "[${currentBuild.fullDisplayName}] ${currentBuild.currentResult}", + mimeType: 'text/html', + recipientProviders: [[$class: 'CulpritsRecipientProvider'], [$class: 'RequesterRecipientProvider']], + body: "<a href=\\"${env.BUILD_URL}\\">${currentBuild.fullDisplayName} is reported as ${currentBuild.currentResult}</a>") + } + } + } + } + """, + spec -> spec.path("Jenkinsfile") + ) + ); + } +} diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/BinaryTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/BinaryTest.java index bc10ffe9a34..05d62e5b00f 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/BinaryTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/BinaryTest.java @@ -24,6 +24,19 @@ @SuppressWarnings({"GroovyUnusedAssignment", "GrUnnecessarySemicolon", "UnnecessaryQualifiedReference"}) class BinaryTest implements RewriteTest { + @SuppressWarnings("GroovyConstantConditional") + @Test + void insideParentheses() { + rewriteRun( + groovy("(1 + 1)"), + groovy("((1 + 1))"), + + // NOT inside parentheses, but verifies the parser's + // test for "inside parentheses" condition + groovy("(1) + 1") + ); + } + @Test void equals() { rewriteRun( diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LiteralTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LiteralTest.java index bc7f16319f6..f5dc61dd4dd 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LiteralTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/LiteralTest.java @@ -30,6 +30,15 @@ @SuppressWarnings("GroovyUnusedAssignment") class LiteralTest implements RewriteTest { + @SuppressWarnings("GroovyConstantConditional") + @Test + void insideParentheses() { + rewriteRun( + groovy("(1)"), + groovy("((1))") + ); + } + @Test void string() { rewriteRun( diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ParenthesisTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ParenthesisTest.java deleted file mode 100644 index 7c764e7b957..00000000000 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/ParenthesisTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.groovy.tree; - -import org.junit.jupiter.api.Test; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.groovy.Assertions.groovy; - -class ParenthesisTest implements RewriteTest { - @Test - void parentheses() { - rewriteRun( - groovy( - """ - int n = (0) - """ - ) - ); - } - - @Test - void binary() { - rewriteRun( - groovy( - """ - int n = (1 + 1) - """ - ) - ); - } - - @Test - void ternary() { - rewriteRun( - groovy( - """ - boolean b = (1 == 2 ? true : false) - """ - ) - ); - } -} diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TernaryTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TernaryTest.java index e7c15d882ff..a5f3fb26683 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TernaryTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/TernaryTest.java @@ -22,14 +22,23 @@ class TernaryTest implements RewriteTest { + @SuppressWarnings("GroovyConstantConditional") + @Test + void insideParentheses() { + rewriteRun( + groovy("(true ? 1 : 2)"), + groovy("((true ? 1 : 2))"), + + // NOT inside parentheses, but verifies the parser's + // test for "inside parentheses" condition + groovy("(true) ? 1 : 2") + ); + } + @Test void ternary() { rewriteRun( - groovy( - """ - 1 == 2 ? /no it isn't/ : /yes it is/ - """ - ) + groovy("1 == 2 ? /no it isn't/ : /yes it is/") ); } From 0800a08b7b77773900adfbec90dc5d91fb9a1823 Mon Sep 17 00:00:00 2001 From: Peter Streef <peter@moderne.io> Date: Mon, 25 Sep 2023 14:38:10 +0200 Subject: [PATCH 262/447] avoid creating an empty file if nothing will be written to it. (#3561) * avoid creating an empty file if nothing will be written to it. * add test * license --- .../maven/cache/LocalMavenArtifactCache.java | 21 ++--- .../cache/LocalMavenArtifactCacheTest.java | 90 +++++++++++++++++++ 2 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/cache/LocalMavenArtifactCacheTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/cache/LocalMavenArtifactCache.java b/rewrite-maven/src/main/java/org/openrewrite/maven/cache/LocalMavenArtifactCache.java index 74d36c65b65..f0ed18ccfd7 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/cache/LocalMavenArtifactCache.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/cache/LocalMavenArtifactCache.java @@ -35,7 +35,7 @@ public class LocalMavenArtifactCache implements MavenArtifactCache { private final Path cache; public LocalMavenArtifactCache(Path cache) { - if(!cache.toFile().exists() && !cache.toFile().mkdirs()) { + if (!cache.toFile().exists() && !cache.toFile().mkdirs()) { throw new IllegalStateException("Unable to find or create maven artifact cache at " + cache); } this.cache = cache; @@ -51,15 +51,16 @@ public Path getArtifact(ResolvedDependency dependency) { @Override @Nullable public Path putArtifact(ResolvedDependency dependency, InputStream artifactInputStream, Consumer<Throwable> onError) { + if (artifactInputStream == null) { + return null; + } Path path = dependencyPath(dependency); try (InputStream is = artifactInputStream; OutputStream out = Files.newOutputStream(path)) { - if (is != null) { - byte[] buffer = new byte[1024]; - int read; - while ((read = is.read(buffer, 0, 1024)) >= 0) { - out.write(buffer, 0, read); - } + byte[] buffer = new byte[1024]; + int read; + while ((read = is.read(buffer, 0, 1024)) >= 0) { + out.write(buffer, 0, read); } } catch (Throwable t) { onError.accept(t); @@ -85,8 +86,8 @@ private Path dependencyPath(ResolvedDependency dependency) { } return resolvedPath.resolve(dependency.getArtifactId() + "-" + - (dependency.getDatedSnapshotVersion() == null ? dependency.getVersion() : dependency.getDatedSnapshotVersion()) + - (dependency.getRequested().getClassifier() == null ? "" : dependency.getRequested().getClassifier()) + - ".jar"); + (dependency.getDatedSnapshotVersion() == null ? dependency.getVersion() : dependency.getDatedSnapshotVersion()) + + (dependency.getRequested().getClassifier() == null ? "" : dependency.getRequested().getClassifier()) + + ".jar"); } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/cache/LocalMavenArtifactCacheTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/cache/LocalMavenArtifactCacheTest.java new file mode 100644 index 00000000000..4301b243c95 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/cache/LocalMavenArtifactCacheTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2020 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.cache; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.SourceFile; +import org.openrewrite.maven.MavenParser; +import org.openrewrite.maven.tree.*; + +import java.io.ByteArrayInputStream; +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class LocalMavenArtifactCacheTest { + + LocalMavenArtifactCache cache; + + @BeforeEach + void setup() { + + } + + @Test + void saveFileWhenInputStringExists(@TempDir Path tempDir) { + cache = new LocalMavenArtifactCache(tempDir); + Path output = cache.putArtifact(findDependency(), new ByteArrayInputStream("hi".getBytes()), Throwable::printStackTrace); + assertThat(output).exists(); + } + + @Test + void dontCreateFileWhenInputStringNull(@TempDir Path tempDir) { + cache = new LocalMavenArtifactCache(tempDir); + Path output = cache.putArtifact(findDependency(), null, Throwable::printStackTrace); + assertThat(output).isNull(); + assertThat(tempDir).isEmptyDirectory(); + } + + private static ResolvedDependency findDependency() { + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + ResolvedGroupArtifactVersion recipeGav = new ResolvedGroupArtifactVersion( + "https://repo1.maven.org/maven2", + "org.openrewrite.recipe", + "rewrite-testing-frameworks", + "1.6.0", null); + + MavenParser mavenParser = MavenParser.builder().build(); + SourceFile parsed = mavenParser.parse(ctx, + String.format( + //language=xml + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>maven-downloader-test</artifactId> + <version>1</version> + <dependencies> + <dependency> + <groupId>%s</groupId> + <artifactId>%s</artifactId> + <version>%s</version> + </dependency> + </dependencies> + </project> + """.formatted(recipeGav.getGroupId(), recipeGav.getArtifactId(), recipeGav.getVersion())) + ).findFirst().orElseThrow(() -> new IllegalArgumentException("Could not parse as XML")); + + MavenResolutionResult mavenModel = parsed.getMarkers().findFirst(MavenResolutionResult.class).orElseThrow(); + assertThat(mavenModel.getDependencies()).isNotEmpty(); + List<ResolvedDependency> runtimeDependencies = mavenModel.getDependencies().get(Scope.Runtime); + return runtimeDependencies.get(0); + } +} \ No newline at end of file From a301363dcb75b70cb2cb09a6333eb2742857ddd2 Mon Sep 17 00:00:00 2001 From: Michael Nielson <safetytrick@gmail.com> Date: Mon, 25 Sep 2023 13:01:34 -0600 Subject: [PATCH 263/447] rewrite escaped groovy method names, add gradle dependencies with the default scope using the escaped method name syntax (#3563) Co-authored-by: Michael Nielson <michael.nielson@collectivemedicaltech.com> --- .../gradle/AddDependencyVisitor.java | 12 ++++-- .../openrewrite/gradle/AddDependencyTest.java | 38 +++++++++++++++++++ .../groovy/GroovyParserVisitor.java | 15 ++++++-- .../groovy/tree/MethodInvocationTest.java | 26 ++++++++++++- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java index dba4be114a7..a2393902b84 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java @@ -16,6 +16,7 @@ package org.openrewrite.gradle; import lombok.RequiredArgsConstructor; + import org.openrewrite.*; import org.openrewrite.gradle.internal.InsertDependencyComparator; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; @@ -37,7 +38,6 @@ import org.openrewrite.semver.*; import org.openrewrite.tree.ParseError; -import java.text.ParseException; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -239,11 +239,11 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu DependencyStyle style = autodetectDependencyStyle(body.getStatements()); if (style == DependencyStyle.String) { codeTemplate = "dependencies {\n" + - configuration + " \"" + groupId + ":" + artifactId + (resolvedVersion == null ? "" : ":" + resolvedVersion) + (resolvedVersion == null || classifier == null ? "" : ":" + classifier) + (extension == null ? "" : "@" + extension) + "\"" + + escapeIfNecessary(configuration) + " \"" + groupId + ":" + artifactId + (resolvedVersion == null ? "" : ":" + resolvedVersion) + (resolvedVersion == null || classifier == null ? "" : ":" + classifier) + (extension == null ? "" : "@" + extension) + "\"" + "\n}"; } else { codeTemplate = "dependencies {\n" + - configuration + " group: \"" + groupId + "\", name: \"" + artifactId + "\"" + (resolvedVersion == null ? "" : ", version: \"" + resolvedVersion + "\"") + (classifier == null ? "" : ", classifier: \"" + classifier + "\"") + (extension == null ? "" : ", ext: \"" + extension + "\"") + + escapeIfNecessary(configuration) + " group: \"" + groupId + "\", name: \"" + artifactId + "\"" + (resolvedVersion == null ? "" : ", version: \"" + resolvedVersion + "\"") + (classifier == null ? "" : ", classifier: \"" + classifier + "\"") + (extension == null ? "" : ", ext: \"" + extension + "\"") + "\n}"; } @@ -333,6 +333,12 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu } } + private String escapeIfNecessary(String configurationName) { + // default is a gradle configuration created by the base plugin and a groovy keyword if + // it is used it needs to be escaped + return configurationName.equals("default") ? "'" + configurationName + "'" : configurationName; + } + enum DependencyStyle { Map, String } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java index 6116fb771bb..1615f9dfed6 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java @@ -1210,6 +1210,44 @@ void addDependencyWithVariable() { ); } + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3559") + void defaultConfigurationEscaped() { + String onlyIfUsing = "com.google.common.math.IntMath"; + rewriteRun( + spec -> spec.recipe(addDependency("com.google.guava:guava:29.0-jre", onlyIfUsing, "default")), + mavenProject("project", + srcTestJava( + java(usingGuavaIntMath) + ), + buildGradle( + """ + plugins { + id 'java' + } + + repositories { + mavenCentral() + } + """, + """ + plugins { + id 'java' + } + + repositories { + mavenCentral() + } + + dependencies { + 'default' "com.google.guava:guava:29.0-jre" + } + """ + ) + ) + ); + } + private AddDependency addDependency(String gav, String onlyIfUsing) { return addDependency(gav, onlyIfUsing, null); } diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index 2e6b59f5975..7617e6fd7ed 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -1469,9 +1469,18 @@ public void visitMethodCallExpression(MethodCallExpression call) { // closure() has implicitThis set to false // So the "select" that was just parsed _may_ have actually been the method name J.Identifier name; - if (call.getMethodAsString().equals(source.substring(cursor, cursor + call.getMethodAsString().length()))) { - name = new J.Identifier(randomId(), sourceBefore(call.getMethodAsString()), Markers.EMPTY, - emptyList(), call.getMethodAsString(), null, null); + + String methodNameExpression = call.getMethodAsString(); + if (source.charAt(cursor) == '"' || source.charAt(cursor) == '\'') { + // we have an escaped groovy method name, commonly used for test `def 'some scenario description'() {}` + // or to workaround names that are also keywords in groovy + methodNameExpression = source.charAt(cursor) + methodNameExpression + source.charAt(cursor); + } + + + if (methodNameExpression.equals(source.substring(cursor, cursor + methodNameExpression.length()))) { + name = new J.Identifier(randomId(), sourceBefore(methodNameExpression), Markers.EMPTY, + emptyList(), methodNameExpression, null, null); } else if (select != null && select.getElement() instanceof J.Identifier) { name = (J.Identifier) select.getElement(); diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodInvocationTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodInvocationTest.java index 1b01abe825c..3df4b41bf33 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodInvocationTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/MethodInvocationTest.java @@ -22,7 +22,6 @@ import static org.openrewrite.groovy.Assertions.groovy; class MethodInvocationTest implements RewriteTest { - @Test void gradle() { rewriteRun( @@ -263,4 +262,29 @@ static void main(String[] args) { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3559") + void escapedMethodNameTest() { + rewriteRun( + groovy( + """ + def 'default'() {} + 'default'() + """ + ) + ); + } + + @Test + void escapedMethodNameWithSpacesTest() { + rewriteRun( + groovy( + """ + def 'some test scenario description'() {} + 'some test scenario description'() + """ + ) + ); + } } From d7e67bf7172d9614bcbc4c40ff915f8f77e86f8c Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Tue, 26 Sep 2023 13:44:23 -0500 Subject: [PATCH 264/447] Handle unnecessary parentheses minimum spacing for throw --- .../cleanup/UnnecessaryParenthesesTest.java | 29 +++++++++++++++++++ .../openrewrite/java/UnwrapParentheses.java | 17 ++++++----- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java index 3ea94ee4fda..529f8807f39 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java @@ -55,6 +55,35 @@ private static Consumer<RecipeSpec> unnecessaryParentheses(UnaryOperator<Unneces ); } + @SuppressWarnings({"EmptyTryBlock", "CaughtExceptionImmediatelyRethrown"}) + @Test + void minimumSpaceThrow() { + rewriteRun( + java( + """ + class Test { + int test() { + try { + } catch(Exception e) { + throw(e); + } + } + } + """, + """ + class Test { + int test() { + try { + } catch(Exception e) { + throw e; + } + } + } + """ + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/2170") @Test void minimumSpace() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/UnwrapParentheses.java b/rewrite-java/src/main/java/org/openrewrite/java/UnwrapParentheses.java index 4ca6c1c7f66..da4a4a74e49 100755 --- a/rewrite-java/src/main/java/org/openrewrite/java/UnwrapParentheses.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/UnwrapParentheses.java @@ -30,8 +30,11 @@ public UnwrapParentheses(J.Parentheses<?> scope) { public <T extends J> J visitParentheses(J.Parentheses<T> parens, P p) { if (scope.isScope(parens) && isUnwrappable(getCursor())) { J tree = parens.getTree().withPrefix(parens.getPrefix()); - if (tree.getPrefix().isEmpty() && getCursor().getParentOrThrow().getValue() instanceof J.Return) { - tree = tree.withPrefix(tree.getPrefix().withWhitespace(" ")); + if (tree.getPrefix().isEmpty()) { + Object parent = getCursor().getParentOrThrow().getValue(); + if (parent instanceof J.Return || parent instanceof J.Throw) { + tree = tree.withPrefix(tree.getPrefix().withWhitespace(" ")); + } } return tree; } @@ -44,11 +47,11 @@ public static boolean isUnwrappable(Cursor parensScope) { } Tree parent = parensScope.getParentTreeCursor().getValue(); if (parent instanceof J.If || - parent instanceof J.Switch || - parent instanceof J.Synchronized || - parent instanceof J.Try.Catch || - parent instanceof J.TypeCast || - parent instanceof J.WhileLoop) { + parent instanceof J.Switch || + parent instanceof J.Synchronized || + parent instanceof J.Try.Catch || + parent instanceof J.TypeCast || + parent instanceof J.WhileLoop) { return false; } else if (parent instanceof J.DoWhileLoop) { return !(parensScope.getValue() == ((J.DoWhileLoop) parent).getWhileCondition()); From 596cb39a71a1902b9f14f8bd9cda44c5069831c9 Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Tue, 26 Sep 2023 15:53:23 -0700 Subject: [PATCH 265/447] Fix a bug of ChangeType with star import --- .../org/openrewrite/java/ChangeTypeTest.java | 29 +++++++++++++++++++ .../java/org/openrewrite/java/ChangeType.java | 10 +------ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java index a4b993d2382..054e297eaf9 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java @@ -67,6 +67,35 @@ void doNotAddJavaLangWrapperImports() { ); } + @SuppressWarnings({"deprecation", "KotlinRedundantDiagnosticSuppress"}) + @Test + void starImport() { + rewriteRun( + spec -> spec.recipe(new ChangeType("java.util.logging.LoggingMXBean", "java.lang.management.PlatformLoggingMXBean", true)), + java( + """ + import java.util.logging.*; + + class Test { + static void method() { + LoggingMXBean loggingBean = null; + } + } + """, + """ + import java.lang.management.PlatformLoggingMXBean; + import java.util.logging.*; + + class Test { + static void method() { + PlatformLoggingMXBean loggingBean = null; + } + } + """ + ) + ); + } + @SuppressWarnings({"deprecation", "KotlinRedundantDiagnosticSuppress"}) @Test void allowJavaLangSubpackages() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java index 8f77687c450..8016c440819 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java @@ -15,7 +15,6 @@ */ package org.openrewrite.java; -import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.*; @@ -88,8 +87,6 @@ private static class ChangeTypeVisitor extends JavaVisitor<ExecutionContext> { private final JavaType targetType; @Nullable private J.Identifier importAlias; - private boolean hasImportWithoutAlias; - @Nullable private final Boolean ignoreDefinition; @@ -101,7 +98,6 @@ private ChangeTypeVisitor(String oldFullyQualifiedTypeName, String newFullyQuali this.targetType = JavaType.buildType(newFullyQualifiedTypeName); this.ignoreDefinition = ignoreDefinition; importAlias = null; - hasImportWithoutAlias = false; } @Override @@ -137,8 +133,6 @@ public J visitImport(J.Import import_, ExecutionContext ctx) { if (hasSameFQN(import_, originalType)) { if (import_.getAlias() != null) { importAlias = import_.getAlias(); - } else { - hasImportWithoutAlias = true; } } @@ -158,9 +152,7 @@ private void addImport(JavaType.FullyQualified owningClass) { maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, importAlias.getSimpleName(), true); } - if (hasImportWithoutAlias) { - maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, null, true); - } + maybeAddImport(owningClass.getPackageName(), owningClass.getClassName(), null, null, true); } @Override From b8639725ab77f3898008f6ad168fce34833e5bad Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Wed, 27 Sep 2023 08:57:59 -0700 Subject: [PATCH 266/447] bump snappy-java version to address dependency vulnerability report --- rewrite-java/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/build.gradle.kts b/rewrite-java/build.gradle.kts index 6e0ea97d926..632f09205aa 100644 --- a/rewrite-java/build.gradle.kts +++ b/rewrite-java/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { implementation("org.apache.commons:commons-text:latest.release") implementation("io.github.classgraph:classgraph:latest.release") - implementation("org.xerial.snappy:snappy-java:1.1.10.1") + implementation("org.xerial.snappy:snappy-java:1.1.10.+") api("com.fasterxml.jackson.core:jackson-annotations") From ac6d3f2912f49db5cec95162c2a2875a5bc7278a Mon Sep 17 00:00:00 2001 From: Nick McKinney <mckinneynicholas@gmail.com> Date: Wed, 27 Sep 2023 15:47:32 -0400 Subject: [PATCH 267/447] improved Autodetect interpretation of annotations for continuationIndent. fixes #3568 --- .../java/style/AutodetectTest.java | 122 +++++++++++++++++- .../openrewrite/java/style/Autodetect.java | 8 +- 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java index b4504cc1280..28b18b6bb6d 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java @@ -15,7 +15,9 @@ */ package org.openrewrite.java.style; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ExpectedToFail; import org.openrewrite.Issue; import org.openrewrite.java.JavaParser; import org.openrewrite.style.GeneralFormatStyle; @@ -25,7 +27,7 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; -@SuppressWarnings({"ConstantConditions", "ResultOfMethodCallIgnored"}) +@SuppressWarnings({"ConstantConditions", "ResultOfMethodCallIgnored", "PointlessBooleanExpression"}) class AutodetectTest implements RewriteTest { private static JavaParser jp() { @@ -1045,4 +1047,122 @@ public void cont() { assertThat(tabsAndIndents.getIndentSize()).isEqualTo(4); assertThat(tabsAndIndents.getContinuationIndent()).isEqualTo(12); } + + @Nested + class ContinuationIndentForAnnotations { + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3568") + void ignoreSpaceBetweenAnnotations() { + var cus = jp().parse( + """ + class Test { + @SafeVarargs + @Deprecated + @SuppressWarnings({"mistakes"}) + boolean count(String... strings) { + return strings.length; + } + } + """ + ); + + var detector = Autodetect.detector(); + cus.forEach(detector::sample); + var styles = detector.build(); + var tabsAndIndents = NamedStyles.merge(TabsAndIndentsStyle.class, singletonList(styles)); + + assertThat(tabsAndIndents.getIndentSize()).isEqualTo(4); + assertThat(tabsAndIndents.getContinuationIndent()) + .as("With no actual continuation indents to go off of, assume IntelliJ IDEA default of 2x the normal indent") + .isEqualTo(8); + } + + @Test + void includeAnnotationAsAnnotationArg() { + var cus = jp().parse( + """ + @interface Foo{} + @interface Foos{ + Foo[] value(); + } + + class Test { + @Foos( + @Foo) + boolean count(String... strings) { + return strings.length; + } + } + """ + ); + + var detector = Autodetect.detector(); + cus.forEach(detector::sample); + var styles = detector.build(); + var tabsAndIndents = NamedStyles.merge(TabsAndIndentsStyle.class, singletonList(styles)); + + assertThat(tabsAndIndents.getIndentSize()).isEqualTo(4); + assertThat(tabsAndIndents.getContinuationIndent()) + .isEqualTo(3); + } + + @Test + void includeAnnotationArgArray() { + var cus = jp().parse( + """ + @interface Foo{} + @interface Foos{ + Foo[] value(); + } + + class Test { + @Foos( + {@Foo}) + boolean count(String... strings) { + return strings.length; + } + } + """ + ); + + var detector = Autodetect.detector(); + cus.forEach(detector::sample); + var styles = detector.build(); + var tabsAndIndents = NamedStyles.merge(TabsAndIndentsStyle.class, singletonList(styles)); + + assertThat(tabsAndIndents.getIndentSize()).isEqualTo(4); + assertThat(tabsAndIndents.getContinuationIndent()) + .isEqualTo(3); + } + + @Test + @ExpectedToFail("existing visitor does not super-visit newArray trees") + void includeAnnotationArgArrayElements() { + var cus = jp().parse( + """ + @interface Foo{} + @interface Foos{ + Foo[] value(); + } + + class Test { + @Foos({ + @Foo}) + boolean count(String... strings) { + return strings.length; + } + } + """ + ); + + var detector = Autodetect.detector(); + cus.forEach(detector::sample); + var styles = detector.build(); + var tabsAndIndents = NamedStyles.merge(TabsAndIndentsStyle.class, singletonList(styles)); + + assertThat(tabsAndIndents.getIndentSize()).isEqualTo(4); + assertThat(tabsAndIndents.getContinuationIndent()) + .isEqualTo(3); + } + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java index 0f736fbed2f..9b9a696c7fa 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/style/Autodetect.java @@ -418,7 +418,13 @@ public Expression visitExpression(Expression expression, IndentStatistics stats) if (statementExpressions.contains(expression)) { return expression; } - countIndents(expression.getPrefix().getWhitespace(), true, stats); + // (newline-separated) annotations on some common target are not continuations + boolean isContinuation = !(expression instanceof J.Annotation && !( + // ...but annotations which are *arguments* to other annotations can be continuations + getCursor().getParentTreeCursor().getValue() instanceof J.Annotation + || getCursor().getParentTreeCursor().getValue() instanceof J.NewArray + )); + countIndents(expression.getPrefix().getWhitespace(), isContinuation, stats); return expression; } From 0b0df943a54565496f45a00b1c62f4ab4541d4b3 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 27 Sep 2023 22:04:12 +0200 Subject: [PATCH 268/447] Fix a few more missing types in tests --- .../openrewrite/java/ChangeFieldNameTest.java | 4 ++-- .../java/format/TabsAndIndentsTest.java | 2 ++ .../search/FindRepeatableAnnotationsTest.java | 17 +++++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeFieldNameTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeFieldNameTest.java index 6c3fd3f6934..badcfebe6c5 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeFieldNameTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeFieldNameTest.java @@ -187,7 +187,7 @@ class Test { List collection = null; class Nested { Object collection = Test.this.collection; - Object collection2 = A.this.collection; + Object collection2 = new A().collection; } } """, @@ -197,7 +197,7 @@ class Test { List list = null; class Nested { Object collection = Test.this.list; - Object collection2 = A.this.collection; + Object collection2 = new A().collection; } } """ diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java index 4a90cd7b334..90dd75b751e 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java @@ -26,6 +26,7 @@ import org.openrewrite.style.NamedStyles; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import java.util.function.Consumer; import java.util.function.UnaryOperator; @@ -942,6 +943,7 @@ class Test { @Test void moreAnnotations() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), java( """ import lombok.EqualsAndHashCode; diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindRepeatableAnnotationsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindRepeatableAnnotationsTest.java index eb1ddfdd9ae..1832200080a 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindRepeatableAnnotationsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindRepeatableAnnotationsTest.java @@ -19,6 +19,7 @@ import org.openrewrite.DocumentExample; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpec; import static org.openrewrite.java.Assertions.java; @@ -75,7 +76,8 @@ void test() { String target(); } - """ + """, + SourceSpec::skip ), java( """ @@ -91,7 +93,18 @@ void test() { public @interface ValueMappings { ValueMapping[] value(); } - """ + """, + SourceSpec::skip + ), + java( + """ + package org.mapstruct; + + public class MappingConstants { + public static final String NULL = "null"; + } + """, + SourceSpec::skip ) ); } From 3b92f4f16a15af2ac50fbee03bf75ffd4371eff3 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 28 Sep 2023 10:58:20 -0700 Subject: [PATCH 269/447] Put recipe internal markers into the "markers" package to ensure they are classloaded appropriately on the saas --- .../openrewrite/marker/AlreadyReplaced.java | 18 ++++++++++++++++++ .../org/openrewrite/text/FindAndReplace.java | 16 +--------------- .../maven/IncrementProjectVersion.java | 10 +--------- .../maven/marker/AlreadyIncremented.java | 13 +++++++++++++ 4 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 rewrite-core/src/main/java/org/openrewrite/marker/AlreadyReplaced.java create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/marker/AlreadyIncremented.java diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/AlreadyReplaced.java b/rewrite-core/src/main/java/org/openrewrite/marker/AlreadyReplaced.java new file mode 100644 index 00000000000..7ba88cf514b --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/marker/AlreadyReplaced.java @@ -0,0 +1,18 @@ +package org.openrewrite.marker; + +import lombok.Value; +import lombok.With; + +import java.util.UUID; + +/** + * Ensure that the same replacement is not applied to the same file more than once per recipe run. + * Used to avoid the situation where replacing "a" with "ab" results in something like "abb". + */ +@Value +@With +public class AlreadyReplaced implements Marker { + UUID id; + String find; + String replace; +} diff --git a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java index 03aaf0eebbe..899a47c22e6 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java @@ -17,17 +17,16 @@ import lombok.EqualsAndHashCode; import lombok.Value; -import lombok.With; import org.openrewrite.*; import org.openrewrite.binary.Binary; import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.AlreadyReplaced; import org.openrewrite.marker.Marker; import org.openrewrite.quark.Quark; import org.openrewrite.remote.Remote; import java.util.Arrays; import java.util.Objects; -import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -97,19 +96,6 @@ public String getDescription() { "will not be able to operate on language-specific type."; } - - /** - * Ensure that the same replacement is not applied to the same file more than once per recipe run. - * Used to avoid the situation where replacing "a" with "ab" results in something like "abb". - */ - @Value - @With - static class AlreadyReplaced implements Marker { - UUID id; - String find; - String replace; - } - @Override public TreeVisitor<?, ExecutionContext> getVisitor() { TreeVisitor<?, ExecutionContext> visitor = new TreeVisitor<Tree, ExecutionContext>() { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java index 6c8baf963c6..7d5480157e1 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java @@ -17,16 +17,14 @@ import lombok.EqualsAndHashCode; import lombok.Value; -import lombok.With; import org.openrewrite.*; -import org.openrewrite.marker.Marker; +import org.openrewrite.maven.marker.AlreadyIncremented; import org.openrewrite.maven.tree.ResolvedPom; import org.openrewrite.xml.ChangeTagValue; import org.openrewrite.xml.XPathMatcher; import org.openrewrite.xml.tree.Xml; import java.util.Optional; -import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -70,12 +68,6 @@ public enum SemverDigit { PATCH } - @Value - @With - private static class AlreadyIncremented implements Marker { - UUID id; - } - private static final Pattern SEMVER_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)\\.?(\\d+)?(-.+)?$"); @Override diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/marker/AlreadyIncremented.java b/rewrite-maven/src/main/java/org/openrewrite/maven/marker/AlreadyIncremented.java new file mode 100644 index 00000000000..4b35c712313 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/marker/AlreadyIncremented.java @@ -0,0 +1,13 @@ +package org.openrewrite.maven.marker; + +import lombok.Value; +import lombok.With; +import org.openrewrite.marker.Marker; + +import java.util.UUID; + +@Value +@With +public class AlreadyIncremented implements Marker { + UUID id; +} From a91d6fc7b369c453edaf04445e5c44edd8d440e8 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 28 Sep 2023 11:55:22 -0700 Subject: [PATCH 270/447] License headers --- .../org/openrewrite/marker/AlreadyReplaced.java | 15 +++++++++++++++ .../maven/marker/AlreadyIncremented.java | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/AlreadyReplaced.java b/rewrite-core/src/main/java/org/openrewrite/marker/AlreadyReplaced.java index 7ba88cf514b..fe4e4ac00fd 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/AlreadyReplaced.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/AlreadyReplaced.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite.marker; import lombok.Value; diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/marker/AlreadyIncremented.java b/rewrite-maven/src/main/java/org/openrewrite/maven/marker/AlreadyIncremented.java index 4b35c712313..6054b9512ce 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/marker/AlreadyIncremented.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/marker/AlreadyIncremented.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite.maven.marker; import lombok.Value; From e0457795a5722fdc7292a3defaebeb2b95f9785c Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 28 Sep 2023 22:22:21 +0200 Subject: [PATCH 271/447] Support templating annotation values on variables Add support for replacing annotation parameter values where the annotation is declared on a variable (or parameter). Fixes: #3555 --- ...eplaceConstantWithAnotherConstantTest.java | 39 +++++++++++++++++++ .../BlockStatementTemplateGenerator.java | 8 +++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java index b7843984316..de6c2bfbd15 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java @@ -16,8 +16,10 @@ package org.openrewrite.java; import org.junit.jupiter.api.Test; +import org.openrewrite.Issue; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.openrewrite.java.Assertions.java; @@ -136,4 +138,41 @@ class Test { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3555") + void replaceConstantForAnnotatedParameter() { + rewriteRun( + spec -> spec.recipe(new ReplaceConstantWithAnotherConstant("com.google.common.base.Charsets.UTF_8", "com.constant.B.UTF_8")) + .typeValidationOptions(TypeValidation.builder().identifiers(false).build()), + java( + """ + package com.constant; + public class B { + public static final String UTF_8 = "UTF_8"; + } + """ + ), + java( + """ + import com.google.common.base.Charsets; + + class Test { + void foo(@SuppressWarnings(value = Charsets.UTF_8) String param) { + System.out.println(param); + } + } + """, + """ + import com.constant.B; + + class Test { + void foo(@SuppressWarnings(value = B.UTF_8) String param) { + System.out.println(param); + } + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java index 18fe6af8b36..3c4ca45f181 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java @@ -461,7 +461,13 @@ private void contextTemplate(Cursor cursor, J prior, StringBuilder before, Strin after.append(";"); } } else if (j instanceof J.VariableDeclarations) { - before.insert(0, variable((J.VariableDeclarations) j, false, cursor) + '='); + if (prior instanceof J.Annotation) { + after.append(variable((J.VariableDeclarations) j, false, cursor)) + .append('=') + .append(valueOfType(((J.VariableDeclarations) j).getType())); + } else { + before.insert(0, variable((J.VariableDeclarations) j, false, cursor) + '='); + } after.append(";"); } else if (j instanceof J.MethodInvocation) { // If prior is an argument, wrap in __M__.any(prior) From f658564cb06f732024268903eed4e7f815b41e19 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 28 Sep 2023 22:40:24 +0200 Subject: [PATCH 272/447] Polish test Don't use field which requires disabling type checking. --- ...eplaceConstantWithAnotherConstantTest.java | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java index de6c2bfbd15..3360803b120 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java @@ -19,7 +19,6 @@ import org.openrewrite.Issue; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; -import org.openrewrite.test.TypeValidation; import static org.openrewrite.java.Assertions.java; @@ -48,7 +47,7 @@ public class B { java( """ import com.google.common.base.Charsets; - + @SuppressWarnings(Charsets.UTF_8) class Test { @SuppressWarnings(value = Charsets.UTF_8) @@ -59,7 +58,7 @@ void foo() { """, """ import com.constant.B; - + @SuppressWarnings(B.UTF_8) class Test { @SuppressWarnings(value = B.UTF_8) @@ -143,31 +142,22 @@ class Test { @Issue("https://github.com/openrewrite/rewrite/issues/3555") void replaceConstantForAnnotatedParameter() { rewriteRun( - spec -> spec.recipe(new ReplaceConstantWithAnotherConstant("com.google.common.base.Charsets.UTF_8", "com.constant.B.UTF_8")) - .typeValidationOptions(TypeValidation.builder().identifiers(false).build()), + spec -> spec.recipe(new ReplaceConstantWithAnotherConstant("java.io.File.pathSeparator", "java.io.File.separator")), java( """ - package com.constant; - public class B { - public static final String UTF_8 = "UTF_8"; - } - """ - ), - java( - """ - import com.google.common.base.Charsets; + import java.io.File; class Test { - void foo(@SuppressWarnings(value = Charsets.UTF_8) String param) { + void foo(@SuppressWarnings(value = File.pathSeparator) String param) { System.out.println(param); } } """, """ - import com.constant.B; + import java.io.File; class Test { - void foo(@SuppressWarnings(value = B.UTF_8) String param) { + void foo(@SuppressWarnings(value = File.separator) String param) { System.out.println(param); } } From a60b83a92c789ee838a0caed02ca74774141930b Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Thu, 28 Sep 2023 14:34:14 -0700 Subject: [PATCH 273/447] Limit Spaces recipe to work only on Java files to avoid making breaking changes to Kotlin or Gradle files --- .../src/main/java/org/openrewrite/java/format/Spaces.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/Spaces.java b/rewrite-java/src/main/java/org/openrewrite/java/format/Spaces.java index 37fa5eafd7a..265db7e8980 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/Spaces.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/Spaces.java @@ -45,6 +45,11 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { } private static class SpacesFromCompilationUnitStyle extends JavaIsoVisitor<ExecutionContext> { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return sourceFile instanceof J.CompilationUnit; + } + @Override public J visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree instanceof JavaSourceFile) { From 88e7c82c685601047998dc166ae89be31462c2cf Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Fri, 29 Sep 2023 13:48:39 +0200 Subject: [PATCH 274/447] Handle varargs in ReloadableJava17JavadocVisitor (#3577) --- .../ReloadableJava17JavadocVisitor.java | 6 ++- .../openrewrite/java/tree/JavadocTest.java | 47 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java index 5152b98fb7c..f238083d420 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java @@ -1148,7 +1148,11 @@ public J visitArrayType(ArrayTypeTree node, Space fmt) { if (dimCount > 0) { dimensions = new ArrayList<>(dimCount); for (int n = 0; n < dimCount; n++) { - dimensions.add(padRight(Space.build(sourceBeforeAsString("["), emptyList()), Space.build(sourceBeforeAsString("]"), emptyList()))); + if (!source.substring(cursor).startsWith("...")) { + dimensions.add(padRight( + Space.build(sourceBeforeAsString("["), emptyList()), + Space.build(sourceBeforeAsString("]"), emptyList()))); + } } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java index 5f7e50be314..1e24d2b586e 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java @@ -1660,4 +1660,51 @@ void arrayTypeLiterals2() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3575") + void varargsMethod() { + rewriteRun( + java( + """ + class A { + /** + * A dummy main method. This method is not actually called, but we'll use its Javadoc comment to test that + * OpenRewrite can handle references like the following: {@link A#varargsMethod(String...)}. + * + * @param args The arguments to the method. + */ + public static void main(String[] args) { + System.out.println("Hello, world! This is my original class' main method."); + } + public static void varargsMethod(String... args) { + System.out.println("Hello, world! This is my original class' varargs method."); + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3575") + void arrayMethod() { + rewriteRun( + java( + """ + class A { + /** + * A dummy main method. This method is not actually called, but we'll use its Javadoc comment to test that + * OpenRewrite can handle references like the following: {@link A#main(String[])}. + * + * @param args The arguments to the method. + */ + public static void main(String[] args) { + System.out.println("Hello, world! This is my original class' main method."); + } + } + """ + ) + ); + } } From 0d526a0dc6593d3c47e0d622f9b6f7676aee6615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Carpenter=E2=84=A2=EF=B8=8F?= <kevin@moderne.io> Date: Fri, 29 Sep 2023 14:40:07 -0300 Subject: [PATCH 275/447] fix: Add option annotation to interfaceFullyQualifiedName in FindImplementations (#3578) --- .../java/org/openrewrite/java/search/FindImplementations.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java index d7ebb5f35b6..d550df5cae5 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java @@ -16,6 +16,7 @@ package org.openrewrite.java.search; import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.JavaIsoVisitor; @@ -24,6 +25,9 @@ import org.openrewrite.marker.SearchResult; public class FindImplementations extends Recipe { + @Option(displayName = "Interface fully-qualified name", + description = "A fully-qualified interface name to search for.", + example = "org.openrewrite.Recipe") private final String interfaceFullyQualifiedName; public FindImplementations(String interfaceFullyQualifiedName) { From b67120eecb0b0da8201e5d19f11147f439b80f40 Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Fri, 29 Sep 2023 12:23:40 -0700 Subject: [PATCH 276/447] polish FindImplementations --- .../openrewrite/java/search/FindImplementations.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java index d550df5cae5..2de50764442 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java @@ -15,6 +15,8 @@ */ package org.openrewrite.java.search; +import lombok.EqualsAndHashCode; +import lombok.Value; import org.openrewrite.ExecutionContext; import org.openrewrite.Option; import org.openrewrite.Recipe; @@ -24,15 +26,13 @@ import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.marker.SearchResult; +@Value +@EqualsAndHashCode(callSuper = false) public class FindImplementations extends Recipe { @Option(displayName = "Interface fully-qualified name", description = "A fully-qualified interface name to search for.", example = "org.openrewrite.Recipe") - private final String interfaceFullyQualifiedName; - - public FindImplementations(String interfaceFullyQualifiedName) { - this.interfaceFullyQualifiedName = interfaceFullyQualifiedName; - } + String interfaceFullyQualifiedName; @Override public String getDisplayName() { From 6ac7b351149165d02689a5dd8a3825c5aade9f77 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Sat, 30 Sep 2023 01:58:36 +0200 Subject: [PATCH 277/447] Drop prefixless expressions in POM (#3581) Fixes #1621 --- .../main/resources/META-INF/rewrite/maven.yml | 35 ++++++ .../maven4/PrefixlessExpressionsTest.java | 106 ++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/maven4/PrefixlessExpressionsTest.java diff --git a/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml b/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml new file mode 100644 index 00000000000..1a8a106553b --- /dev/null +++ b/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml @@ -0,0 +1,35 @@ +# +# Copyright 2023 the original author or authors. +# <p> +# 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 +# <p> +# https://www.apache.org/licenses/LICENSE-2.0 +# <p> +# 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. +# +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.maven.BestPractices +displayName: Apache Maven best practices +description: Applies best practices to Maven POMs. +recipeList: + - org.openrewrite.maven.cleanup.PrefixlessExpressions + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.maven.cleanup.PrefixlessExpressions +displayName: Drop prefixless expressions in POM +description: MNG-7404 drops support for prefixless in POMs. This recipe will add the `project.` prefix where missing. +recipeList: + - org.openrewrite.maven.RenamePropertyKey: + oldKey: version + newKey: project.version + - org.openrewrite.maven.RenamePropertyKey: + oldKey: pom.version + newKey: project.version diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/maven4/PrefixlessExpressionsTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/maven4/PrefixlessExpressionsTest.java new file mode 100644 index 00000000000..a77401b8c1a --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/maven4/PrefixlessExpressionsTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.maven4; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.maven.Assertions.pomXml; + +class PrefixlessExpressionsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipeFromResource("/META-INF/rewrite/maven.yml", "org.openrewrite.maven.cleanup.PrefixlessExpressions"); + } + + @DocumentExample + @Test + void renamePrefixlessExpressions() { + rewriteRun( + pomXml( + """ + <project> + <modelVersion>4.0.0</modelVersion> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1.0</version> + <properties> + <other.version>1.0.0</other.version> + </properties> + <dependencies> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${version}</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${pom.version}</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${other.version}</version> + </dependency> + </dependencies> + </project> + """, + """ + <project> + <modelVersion>4.0.0</modelVersion> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1.0</version> + <properties> + <other.version>1.0.0</other.version> + </properties> + <dependencies> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${other.version}</version> + </dependency> + </dependencies> + </project> + """ + ) + ); + } +} From 6873fbd6d6550d5412db9b448e364832a7a539f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Schn=C3=A9ider?= <jkschneider@gmail.com> Date: Fri, 29 Sep 2023 17:44:08 -0700 Subject: [PATCH 278/447] FindFields matches on type patterns and field globs (#3579) --- .github/workflows/ci.yml | 33 ------- build.gradle.kts | 11 --- .../java/search/FindDeprecatedFieldsTest.java | 6 +- .../java/search/FindFieldsOfTypeTest.java | 30 +++--- .../java/search/FindFieldsTest.java | 21 +++- .../java/search/FindImplementationsTest.java | 17 ++-- .../java/search/FindImportsTest.java | 4 +- .../main/antlr/TemplateParameterLexer.tokens | 23 ++--- .../org/openrewrite/java/MethodMatcher.java | 51 +++++----- .../org/openrewrite/java/TypeMatcher.java | 97 +++++++++++-------- .../java/search/FindDeprecatedClasses.java | 3 +- .../java/search/FindDeprecatedFields.java | 9 +- .../java/search/FindDeprecatedUses.java | 2 +- .../openrewrite/java/search/FindFields.java | 34 ++++--- .../java/search/FindFieldsOfType.java | 24 +++-- .../java/search/FindImplementations.java | 7 ++ .../openrewrite/java/search/FindImports.java | 9 +- .../openrewrite/java/search/UsesField.java | 6 +- 18 files changed, 209 insertions(+), 178 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f08d3aad5e6..d50cb1998d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,36 +27,3 @@ jobs: ossrh_token: ${{ secrets.OSSRH_TOKEN }} ossrh_signing_key: ${{ secrets.OSSRH_SIGNING_KEY }} ossrh_signing_password: ${{ secrets.OSSRH_SIGNING_PASSWORD }} -# test-downstream: -# needs: build -# strategy: -# fail-fast: false -# matrix: -# repository: [ rewrite-java-security , rewrite-spring, rewrite-migrate-java ] -# runs-on: ubuntu-latest -# steps: -# - name: Checkout -# uses: actions/checkout@v3 -# with: -# path: rewrite -# fetch-depth: 0 -# - name: Checkout ${{ matrix.repository }} repo -# uses: actions/checkout@v3 -# with: -# repository: openrewrite/${{ matrix.repository }} -# path: ${{ matrix.repository }} -# fetch-depth: 0 -# - name: Setup Java -# uses: actions/setup-java@v3.11.0 -# with: -# distribution: temurin -# java-version: 17 -# - name: Build -# uses: gradle/gradle-build-action@v2 -# with: -# arguments: --console=plain --info --stacktrace --warning-mode=all --no-daemon --include-build ../rewrite build -# build-root-directory: ${{ matrix.repository }} -# env: -# GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} -# GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} -# GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} diff --git a/build.gradle.kts b/build.gradle.kts index eddf8702a4e..330dc566c64 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,23 +1,12 @@ plugins { id("org.openrewrite.build.root") version("latest.release") id("org.openrewrite.build.java-base") version("latest.release") - id("org.openrewrite.rewrite") version("latest.release") } repositories { mavenCentral() } -dependencies { - rewrite(project(":rewrite-core")) -} - -rewrite { - failOnDryRunResults = true - activeRecipe("org.openrewrite.self.Rewrite") -} - - allprojects { group = "org.openrewrite" description = "Eliminate tech-debt. Automatically." diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindDeprecatedFieldsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindDeprecatedFieldsTest.java index 0545ab80ac8..c65a9ea7aca 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindDeprecatedFieldsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindDeprecatedFieldsTest.java @@ -41,7 +41,7 @@ public class D { @Test void ignoreDeprecationsInDeprecatedMethod() { rewriteRun( - spec -> spec.recipe(new FindDeprecatedFields("org.old.types..*", true)), + spec -> spec.recipe(new FindDeprecatedFields("org.old.types..*", null, true)), java( """ import org.old.types.D; @@ -59,7 +59,7 @@ void test(int n) { @Test void ignoreDeprecationsInDeprecatedClass() { rewriteRun( - spec -> spec.recipe(new FindDeprecatedFields("org.old.types..*", true)), + spec -> spec.recipe(new FindDeprecatedFields("org.old.types..*", null, true)), java( """ import org.old.types.D; @@ -79,7 +79,7 @@ void test(int n) { @Test void findDeprecations() { rewriteRun( - spec -> spec.recipe(new FindDeprecatedFields("org.old.types..*", false)), + spec -> spec.recipe(new FindDeprecatedFields("org.old.types..*", null, false)), java( """ import org.old.types.D; diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindFieldsOfTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindFieldsOfTypeTest.java index ff484eb36c2..2ad348d9b5f 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindFieldsOfTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindFieldsOfTypeTest.java @@ -16,7 +16,8 @@ package org.openrewrite.java.search; import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; @@ -28,7 +29,7 @@ class FindFieldsOfTypeTest implements RewriteTest { @Test void findFieldNotVariable() { rewriteRun( - spec -> spec.recipe(new FindFieldsOfType("java.io.File")), + spec -> spec.recipe(new FindFieldsOfType("java.io.File", null)), java( """ import java.io.*; @@ -42,24 +43,26 @@ public static void main(String[] args) { ); } - @DocumentExample - @Test - void findPrivateNonInheritedField() { + @ParameterizedTest + @ValueSource(strings = {"java.util.List", "java.util.*", "java.util..*"}) + void findPrivateNonInheritedField(String type) { rewriteRun( - spec -> spec.recipe(new FindFieldsOfType("java.util.List")), + spec -> spec.recipe(new FindFieldsOfType(type, null)), java( """ - import java.util.*; + import java.security.SecureRandom; + import java.util.List; public class A { private List<?> list; - private Set<?> set; + private SecureRandom rand; } """, """ - import java.util.*; + import java.security.SecureRandom; + import java.util.List; public class A { /*~~>*/private List<?> list; - private Set<?> set; + private SecureRandom rand; } """ ) @@ -69,7 +72,7 @@ public class A { @Test void findArrayOfType() { rewriteRun( - spec -> spec.recipe(new FindFieldsOfType("java.lang.String")), + spec -> spec.recipe(new FindFieldsOfType("java.lang.String", null)), java( """ import java.util.*; @@ -83,7 +86,7 @@ public class A { /*~~>*/private String[] s; } """ - ) + ) ); } @@ -91,7 +94,7 @@ public class A { @Test void skipsMultiCatches() { rewriteRun( - spec -> spec.recipe(new FindFieldsOfType("java.io.File")), + spec -> spec.recipe(new FindFieldsOfType("java.io.File", null)), java( """ import java.io.*; @@ -116,5 +119,4 @@ public void test() { ) ); } - } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindFieldsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindFieldsTest.java index 46b28bd77b1..9889442ca50 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindFieldsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindFieldsTest.java @@ -26,7 +26,26 @@ class FindFieldsTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new FindFields("java.nio.charset.StandardCharsets", "UTF_8")); + spec.recipe(new FindFields("java.nio.charset.StandardCharsets", null,"UTF_8")); + } + + @Test + void fieldMatch() { + rewriteRun( + spec -> spec.recipe(new FindFields("java.nio..*", true, "*")), + java( + """ + class Test { + Object o = java.nio.charset.StandardCharsets.UTF_8; + } + """, + """ + class Test { + Object o = /*~~>*/java.nio.charset.StandardCharsets.UTF_8; + } + """ + ) + ); } @DocumentExample diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindImplementationsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindImplementationsTest.java index 5a108d1e8c9..429af1c3f01 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindImplementationsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindImplementationsTest.java @@ -27,7 +27,7 @@ class FindImplementationsTest implements RewriteTest { @Test void found() { rewriteRun( - spec -> spec.recipe(new FindImplementations("java.lang.Runnable")), + spec -> spec.recipe(new FindImplementations("java.lang.Runnable", null)), java( """ class Test implements Runnable { @@ -50,7 +50,7 @@ public void run() { @Test void notFound() { rewriteRun( - spec -> spec.recipe(new FindImplementations("java.lang.Runnable")), + spec -> spec.recipe(new FindImplementations("java.lang.Runnable", null)), java( """ class Test { @@ -62,10 +62,11 @@ public void run() { ); } + @SuppressWarnings("NullableProblems") @Test void genericType() { rewriteRun( - spec -> spec.recipe(new FindImplementations("java.lang.Comparable<java.lang.String>")), + spec -> spec.recipe(new FindImplementations("java.lang.Comparable<java.lang.String>", null)), java( """ class Test implements Comparable<String> { @@ -87,10 +88,11 @@ public int compareTo(String o) { ); } + @SuppressWarnings("NullableProblems") @Test void genericType2() { rewriteRun( - spec -> spec.recipe(new FindImplementations("java.lang.Comparable")), + spec -> spec.recipe(new FindImplementations("java.lang.Comparable", null)), java( """ class Test implements Comparable<String> { @@ -112,10 +114,11 @@ public int compareTo(String o) { ); } + @SuppressWarnings("NullableProblems") @Test void unmatchedGenericType() { rewriteRun( - spec -> spec.recipe(new FindImplementations("java.lang.Comparable<java.lang.Runnable>")), + spec -> spec.recipe(new FindImplementations("java.lang.Comparable<java.lang.Runnable>", null)), java( """ class Test implements Comparable<String> { @@ -132,7 +135,7 @@ public int compareTo(String o) { @Test void transitiveImplementsExtends() { rewriteRun( - spec -> spec.recipe(new FindImplementations("org.x.A")), + spec -> spec.recipe(new FindImplementations("org.x.A", null)), java( """ package org.x; @@ -189,7 +192,7 @@ public void bar() { @Test void transitiveExtendsImplements() { rewriteRun( - spec -> spec.recipe(new FindImplementations("org.x.A")), + spec -> spec.recipe(new FindImplementations("org.x.A", null)), java( """ package org.x; diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindImportsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindImportsTest.java index 53ff05fc206..8bcb6657ada 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindImportsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindImportsTest.java @@ -28,7 +28,7 @@ class FindImportsTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new FindImports("java.util..*")); + spec.recipe(new FindImports("java.util..*", null)); } @Test @@ -79,7 +79,7 @@ class Test { @Test void starImportMatchesExact() { rewriteRun( - spec -> spec.recipe(new FindImports("java.util.List")), + spec -> spec.recipe(new FindImports("java.util.List", null)), java( """ import java.util.*; diff --git a/rewrite-java/src/main/antlr/TemplateParameterLexer.tokens b/rewrite-java/src/main/antlr/TemplateParameterLexer.tokens index c24e72bc9b4..aad1837fbc6 100644 --- a/rewrite-java/src/main/antlr/TemplateParameterLexer.tokens +++ b/rewrite-java/src/main/antlr/TemplateParameterLexer.tokens @@ -1,17 +1,14 @@ LPAREN=1 RPAREN=2 -LBRACK=3 -RBRACK=4 -DOT=5 -COMMA=6 -SPACE=7 -FullyQualifiedName=8 -Number=9 -Identifier=10 +DOT=3 +COLON=4 +COMMA=5 +FullyQualifiedName=6 +Number=7 +Identifier=8 +S=9 '('=1 ')'=2 -'['=3 -']'=4 -'.'=5 -','=6 -' '=7 +'.'=3 +':'=4 +','=5 diff --git a/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java b/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java index 67e469b6630..2df748b65f3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/MethodMatcher.java @@ -62,18 +62,21 @@ */ @SuppressWarnings("NotNullFieldNotInitialized") public class MethodMatcher { - private static final JavaType.ShallowClass OBJECT_CLASS = JavaType.ShallowClass.build("java.lang.Object"); private static final String ASPECTJ_DOT_PATTERN = StringUtils.aspectjNameToPattern("."); private static final String ASPECTJ_DOTDOT_PATTERN = StringUtils.aspectjNameToPattern(".."); @Getter private Pattern targetTypePattern; + @Getter private Pattern methodNamePattern; + @Getter private Pattern argumentPattern; + @Nullable private String targetType; + @Nullable private String methodName; @@ -115,11 +118,11 @@ public Void visitMethodPattern(MethodSignatureParser.MethodPatternContext ctx) { } private static boolean isPlainIdentifier(MethodSignatureParser.TargetTypePatternContext context) { - return context.BANG() == null - && context.AND() == null - && context.OR() == null - && context.classNameOrInterface().DOTDOT().isEmpty() - && context.classNameOrInterface().WILDCARD().isEmpty(); + return context.BANG() == null && + context.AND() == null && + context.OR() == null && + context.classNameOrInterface().DOTDOT().isEmpty() && + context.classNameOrInterface().WILDCARD().isEmpty(); } private static boolean isPlainIdentifier(MethodSignatureParser.SimpleNamePatternContext context) { @@ -149,16 +152,16 @@ private boolean matchesTargetTypeName(String fullyQualifiedTypeName) { boolean matchesTargetType(@Nullable JavaType.FullyQualified type) { return TypeUtils.isOfTypeWithName( - type, - matchOverrides, - this::matchesTargetTypeName - ); + type, + matchOverrides, + this::matchesTargetTypeName + ); } @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean matchesMethodName(String methodName) { - return this.methodName != null && this.methodName.equals(methodName) - || this.methodName == null && methodNamePattern.matcher(methodName).matches(); + return this.methodName != null && this.methodName.equals(methodName) || + this.methodName == null && methodNamePattern.matcher(methodName).matches(); } private boolean matchesParameterTypes(List<JavaType> parameterTypes) { @@ -206,7 +209,7 @@ public boolean matches(J.MethodDeclaration method, J.ClassDeclaration enclosing) // aspectJUtils does not support matching classes separated by packages. // [^.]* is the product of a fully wild card match for a method. `* foo()` boolean matchesTargetType = (targetType == null && "[^.]*".equals(targetTypePattern.pattern())) - || matchesTargetType(enclosing.getType()); + || matchesTargetType(enclosing.getType()); if (!matchesTargetType) { return false; } @@ -265,8 +268,8 @@ private boolean matchesAllowingUnknownTypes(J.MethodInvocation method) { } if (method.getSelect() != null - && method.getSelect() instanceof J.Identifier - && !matchesSelectBySimpleNameAlone(((J.Identifier) method.getSelect()))) { + && method.getSelect() instanceof J.Identifier + && !matchesSelectBySimpleNameAlone(((J.Identifier) method.getSelect()))) { return false; } @@ -281,11 +284,11 @@ private boolean matchesSelectBySimpleNameAlone(J.Identifier select) { if (targetType != null) { return targetType.equals(select.getSimpleName()) || targetType.endsWith('.' + select.getSimpleName()); } - return targetTypePattern.matcher(select.getSimpleName()).matches() - || Pattern.compile(targetTypePattern.pattern() - .replaceAll(".*" + Pattern.quote(ASPECTJ_DOT_PATTERN), "") - .replaceAll(".*" + Pattern.quote(ASPECTJ_DOTDOT_PATTERN), "")) - .matcher(select.getSimpleName()).matches(); + return targetTypePattern.matcher(select.getSimpleName()).matches() || + Pattern.compile(targetTypePattern.pattern() + .replaceAll(".*" + Pattern.quote(ASPECTJ_DOT_PATTERN), "") + .replaceAll(".*" + Pattern.quote(ASPECTJ_DOTDOT_PATTERN), "")) + .matcher(select.getSimpleName()).matches(); } private String argumentsFromExpressionTypes(J.MethodInvocation method) { @@ -314,8 +317,8 @@ public boolean isFullyQualifiedClassReference(J.FieldAccess fieldAccess) { hopefullyFullyQualifiedMethod = targetType + "." + methodNamePattern.pattern(); } else { hopefullyFullyQualifiedMethod = targetTypePattern.pattern() - .replace(ASPECTJ_DOT_PATTERN, ".") - + "." + methodNamePattern.pattern(); + .replace(ASPECTJ_DOT_PATTERN, ".") + + "." + methodNamePattern.pattern(); } return fieldAccess.isFullyQualifiedClassReference(hopefullyFullyQualifiedMethod); } @@ -327,7 +330,7 @@ private static String typePattern(JavaType type) { return ((JavaType.Primitive) type).getClassName(); } return ((JavaType.Primitive) type).getKeyword(); - } else if(type instanceof JavaType.Unknown) { + } else if (type instanceof JavaType.Unknown) { return "*"; } else if (type instanceof JavaType.FullyQualified) { return ((JavaType.FullyQualified) type).getFullyQualifiedName(); @@ -353,7 +356,7 @@ public static String methodPattern(JavaType.Method method) { } return typePattern(method.getDeclaringType()) + " " + - method.getName() + "(" + parameters + ")"; + method.getName() + "(" + parameters + ")"; } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java b/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java index 9e450dd136d..c7dceeaa988 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/TypeMatcher.java @@ -18,6 +18,7 @@ import lombok.Getter; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; +import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.grammar.MethodSignatureLexer; import org.openrewrite.java.internal.grammar.MethodSignatureParser; @@ -28,41 +29,28 @@ import java.util.regex.Pattern; +import static org.openrewrite.java.tree.TypeUtils.fullyQualifiedNamesAreEqual; + @SuppressWarnings("NotNullFieldNotInitialized") @Getter public class TypeMatcher { + private static final String ASPECTJ_DOT_PATTERN = StringUtils.aspectjNameToPattern("."); + + @SuppressWarnings("NotNullFieldNotInitialized") + @Getter private Pattern targetTypePattern; + @Nullable + private String targetType; + private final String signature; /** - * Whether to match overridden forms of the method on subclasses of {@link #targetTypePattern}. + * Whether to match on subclasses of {@link #targetTypePattern}. */ + @Getter private final boolean matchInherited; - public TypeMatcher(String signature, boolean matchInherited) { - this.signature = signature; - this.matchInherited = matchInherited; - MethodSignatureParser parser = new MethodSignatureParser(new CommonTokenStream(new MethodSignatureLexer( - CharStreams.fromString(signature + " *(..)")))); - - new MethodSignatureParserBaseVisitor<Void>() { - @Override - public Void visitMethodPattern(MethodSignatureParser.MethodPatternContext ctx) { - targetTypePattern = Pattern.compile(new TypeVisitor().visitTargetTypePattern(ctx.targetTypePattern())); - return null; - } - }.visit(parser.methodPattern()); - } - - public TypeMatcher(String signature) { - this(signature, false); - } - - public boolean matches(@Nullable JavaType type) { - return matchesTargetType(TypeUtils.asFullyQualified(type)); - } - public boolean matches(@Nullable TypeTree tt) { return tt != null && matches(tt.getType()); } @@ -73,29 +61,52 @@ public boolean matchesPackage(String packageName) { "." + signature.substring(signature.lastIndexOf('.') + 1))).matches(); } - boolean matchesTargetType(@Nullable JavaType.FullyQualified type) { - if (type == null) { - return false; - } - - if (targetTypePattern.matcher(type.getFullyQualifiedName()).matches()) { - return true; - } else if (!"java.lang.Object".equals(type.getFullyQualifiedName()) && matchesTargetType(type.getSupertype() == null ? JavaType.ShallowClass.build("java.lang.Object") : type.getSupertype())) { - return true; - } + public TypeMatcher(String fieldType) { + this(fieldType, false); + } - if (matchInherited) { - if (matchesTargetType(type.getSupertype())) { - return true; - } + public TypeMatcher(String fieldType, boolean matchInherited) { + this.signature = fieldType; + this.matchInherited = matchInherited; - for (JavaType.FullyQualified anInterface : type.getInterfaces()) { - if (matchesTargetType(anInterface)) { - return true; + if (StringUtils.isBlank(fieldType)) { + targetTypePattern = Pattern.compile(".*"); + } else { + MethodSignatureParser parser = new MethodSignatureParser(new CommonTokenStream(new MethodSignatureLexer( + CharStreams.fromString(fieldType)))); + + new MethodSignatureParserBaseVisitor<Void>() { + @Override + public Void visitTargetTypePattern(MethodSignatureParser.TargetTypePatternContext ctx) { + String pattern = new TypeVisitor().visitTargetTypePattern(ctx); + targetTypePattern = Pattern.compile(new TypeVisitor().visitTargetTypePattern(ctx)); + targetType = isPlainIdentifier(ctx) + ? pattern.replace(ASPECTJ_DOT_PATTERN, ".").replace("\\", "") + : null; + return null; } - } + }.visitTargetTypePattern(parser.targetTypePattern()); } + } + + public boolean matches(@Nullable JavaType type) { + return TypeUtils.isOfTypeWithName( + TypeUtils.asFullyQualified(type), + matchInherited, + this::matchesTargetTypeName + ); + } + + private boolean matchesTargetTypeName(String fullyQualifiedTypeName) { + return this.targetType != null && fullyQualifiedNamesAreEqual(this.targetType, fullyQualifiedTypeName) || + this.targetType == null && this.targetTypePattern.matcher(fullyQualifiedTypeName).matches(); + } - return false; + private static boolean isPlainIdentifier(MethodSignatureParser.TargetTypePatternContext context) { + return context.BANG() == null && + context.AND() == null && + context.OR() == null && + context.classNameOrInterface().DOTDOT().isEmpty() && + context.classNameOrInterface().WILDCARD().isEmpty(); } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedClasses.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedClasses.java index d9508006e18..e5d9668a27d 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedClasses.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedClasses.java @@ -64,7 +64,8 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - TypeMatcher typeMatcher = typePattern == null ? null : new TypeMatcher(typePattern); + TypeMatcher typeMatcher = typePattern == null ? null : new TypeMatcher(typePattern, + Boolean.TRUE.equals(matchInherited)); return Preconditions.check(new JavaIsoVisitor<ExecutionContext>() { @Override diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedFields.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedFields.java index 6a4ebe74e65..6e26583abe6 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedFields.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedFields.java @@ -42,6 +42,12 @@ public class FindDeprecatedFields extends Recipe { @Nullable String typePattern; + @Option(displayName = "Match inherited", + description = "When enabled, find types that inherit from a deprecated type.", + required = false) + @Nullable + Boolean matchInherited; + @Option(displayName = "Ignore deprecated scopes", description = "When a deprecated method is used in a deprecated method or class, ignore it.", required = false) @@ -60,7 +66,8 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - TypeMatcher typeMatcher = typePattern == null ? null : new TypeMatcher(typePattern); + TypeMatcher typeMatcher = typePattern == null ? null : new TypeMatcher(typePattern, + Boolean.TRUE.equals(matchInherited)); return Preconditions.check(new JavaIsoVisitor<ExecutionContext>() { @Override diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedUses.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedUses.java index d9ea02c366a..bddd1ae5759 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedUses.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindDeprecatedUses.java @@ -51,7 +51,7 @@ public List<Recipe> getRecipeList() { return Arrays.asList( new FindDeprecatedMethods((typePattern == null || typePattern.isEmpty() ? null : typePattern + " *(..)"), ignoreDeprecatedScopes), new FindDeprecatedClasses(typePattern, matchInherited, ignoreDeprecatedScopes), - new FindDeprecatedFields(typePattern, ignoreDeprecatedScopes) + new FindDeprecatedFields(typePattern, matchInherited, ignoreDeprecatedScopes) ); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindFields.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindFields.java index 60b7be4c5e3..30f190d800f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindFields.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindFields.java @@ -18,11 +18,13 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.*; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.TypeMatcher; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.marker.SearchResult; import java.util.HashSet; @@ -36,6 +38,12 @@ public class FindFields extends Recipe { example = "com.fasterxml.jackson.core.json.JsonWriteFeature") String fullyQualifiedTypeName; + @Option(displayName = "Match inherited", + description = "When enabled, find types that inherit from a deprecated type.", + required = false) + @Nullable + Boolean matchInherited; + @Option(displayName = "Field name", description = "The name of a field on the type.", example = "QUOTE_FIELD_NAMES") @@ -57,8 +65,8 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { @Override public J.FieldAccess visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext executionContext) { JavaType.Variable varType = fieldAccess.getName().getFieldType(); - if (varType != null && TypeUtils.isOfClassType(varType.getOwner(), fullyQualifiedTypeName) && - varType.getName().equals(fieldName)) { + if (varType != null && new TypeMatcher(fullyQualifiedTypeName, Boolean.TRUE.equals(matchInherited)).matches(varType.getOwner()) && + StringUtils.matchesGlob(varType.getName(), fieldName)) { return SearchResult.found(fieldAccess); } return super.visitFieldAccess(fieldAccess, executionContext); @@ -68,8 +76,8 @@ public J.FieldAccess visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContex public J.Identifier visitIdentifier(J.Identifier identifier, ExecutionContext executionContext) { J.Identifier i = super.visitIdentifier(identifier, executionContext); JavaType.Variable varType = identifier.getFieldType(); - if (varType != null && TypeUtils.isOfClassType(varType.getOwner(), fullyQualifiedTypeName) && - varType.getName().equals(fieldName)) { + if (varType != null && new TypeMatcher(fullyQualifiedTypeName, Boolean.TRUE.equals(matchInherited)).matches(varType.getOwner()) && + StringUtils.matchesGlob(varType.getName(), fieldName)) { i = SearchResult.found(i); } return i; @@ -79,8 +87,8 @@ public J.Identifier visitIdentifier(J.Identifier identifier, ExecutionContext ex public J.MemberReference visitMemberReference(J.MemberReference memberRef, ExecutionContext ctx) { J.MemberReference m = super.visitMemberReference(memberRef, ctx); JavaType.Variable varType = memberRef.getVariableType(); - if (varType != null && TypeUtils.isOfClassType(varType.getOwner(), fullyQualifiedTypeName) && - varType.getName().equals(fieldName)) { + if (varType != null && new TypeMatcher(fullyQualifiedTypeName, Boolean.TRUE.equals(matchInherited)).matches(varType.getOwner()) && + StringUtils.matchesGlob(varType.getName(), fieldName)) { m = m.withReference(SearchResult.found(m.getReference())); } return m; @@ -94,8 +102,8 @@ public static Set<J> find(J j, String fullyQualifiedTypeName, String fieldName) public J.FieldAccess visitFieldAccess(J.FieldAccess fieldAccess, Set<J> vs) { J.FieldAccess f = super.visitFieldAccess(fieldAccess, vs); JavaType.Variable varType = fieldAccess.getName().getFieldType(); - if (varType != null && TypeUtils.isOfClassType(varType.getOwner(), fullyQualifiedTypeName) && - varType.getName().equals(fieldName)) { + if (varType != null && new TypeMatcher(fullyQualifiedTypeName, true).matches(varType.getOwner()) && + StringUtils.matchesGlob(varType.getName(), fieldName)) { vs.add(f); } return f; @@ -105,8 +113,8 @@ public J.FieldAccess visitFieldAccess(J.FieldAccess fieldAccess, Set<J> vs) { public J.Identifier visitIdentifier(J.Identifier identifier, Set<J> vs) { J.Identifier i = super.visitIdentifier(identifier, vs); JavaType.Variable varType = identifier.getFieldType(); - if (varType != null && TypeUtils.isOfClassType(varType.getOwner(), fullyQualifiedTypeName) && - varType.getName().equals(fieldName)) { + if (varType != null && new TypeMatcher(fullyQualifiedTypeName, true).matches(varType.getOwner()) && + StringUtils.matchesGlob(varType.getName(), fieldName)) { vs.add(i); } return i; @@ -116,8 +124,8 @@ public J.Identifier visitIdentifier(J.Identifier identifier, Set<J> vs) { public J.MemberReference visitMemberReference(J.MemberReference memberRef, Set<J> vs) { J.MemberReference m = super.visitMemberReference(memberRef, vs); JavaType.Variable varType = memberRef.getVariableType(); - if (varType != null && TypeUtils.isOfClassType(varType.getOwner(), fullyQualifiedTypeName) && - varType.getName().equals(fieldName)) { + if (varType != null && new TypeMatcher(fullyQualifiedTypeName, true).matches(varType.getOwner()) && + StringUtils.matchesGlob(varType.getName(), fieldName)) { vs.add(m); } return m; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindFieldsOfType.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindFieldsOfType.java index e0461dba4ed..2ea870aed34 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindFieldsOfType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindFieldsOfType.java @@ -20,6 +20,7 @@ import org.openrewrite.*; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.TypeMatcher; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.marker.SearchResult; @@ -40,6 +41,12 @@ public class FindFieldsOfType extends Recipe { example = "org.slf4j.api.Logger") String fullyQualifiedTypeName; + @Option(displayName = "Match inherited", + description = "When enabled, find types that inherit from a deprecated type.", + required = false) + @Nullable + Boolean matchInherited; + @Override public String getDisplayName() { return "Find fields of type"; @@ -59,8 +66,9 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return multiVariable; } if (multiVariable.getTypeExpression() != null && - hasElementType(multiVariable.getTypeExpression().getType(), fullyQualifiedTypeName) && - isField(getCursor())) { + hasElementType(multiVariable.getTypeExpression().getType(), fullyQualifiedTypeName, + Boolean.TRUE.equals(matchInherited)) && + isField(getCursor())) { return SearchResult.found(multiVariable); } return multiVariable; @@ -76,7 +84,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m return multiVariable; } if (multiVariable.getTypeExpression() != null && - hasElementType(multiVariable.getTypeExpression().getType(), fullyQualifiedTypeName) && + hasElementType(multiVariable.getTypeExpression().getType(), fullyQualifiedTypeName, true) && isField(getCursor())) { vs.add(multiVariable); } @@ -103,20 +111,20 @@ private static boolean isField(Cursor cursor) { return true; } - private static boolean hasElementType(@Nullable JavaType type, String fullyQualifiedName) { + private static boolean hasElementType(@Nullable JavaType type, String fullyQualifiedName, + boolean matchOverrides) { if (type instanceof JavaType.Array) { - return hasElementType(((JavaType.Array) type).getElemType(), fullyQualifiedName); + return hasElementType(((JavaType.Array) type).getElemType(), fullyQualifiedName, matchOverrides); } else if (type instanceof JavaType.FullyQualified) { - return fullyQualifiedName.equals(((JavaType.FullyQualified) type).getFullyQualifiedName()); + return new TypeMatcher(fullyQualifiedName, matchOverrides).matches(type); } else if (type instanceof JavaType.GenericTypeVariable) { JavaType.GenericTypeVariable generic = (JavaType.GenericTypeVariable) type; for (JavaType bound : generic.getBounds()) { - if (hasElementType(bound, fullyQualifiedName)) { + if (hasElementType(bound, fullyQualifiedName, matchOverrides)) { return true; } } } return false; } - } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java index 2de50764442..32bf3e92bcb 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindImplementations.java @@ -21,6 +21,7 @@ import org.openrewrite.Option; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.TypeUtils; @@ -34,6 +35,12 @@ public class FindImplementations extends Recipe { example = "org.openrewrite.Recipe") String interfaceFullyQualifiedName; + @Option(displayName = "Match on inherited types", + description = "When enabled, find methods that are overrides of the method pattern.", + required = false) + @Nullable + Boolean matchInherited; + @Override public String getDisplayName() { return "Find class declarations implementing an interface"; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindImports.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindImports.java index 3a4183ca700..e431ec43464 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindImports.java @@ -21,6 +21,7 @@ import org.openrewrite.Option; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.TypeMatcher; import org.openrewrite.java.tree.J; @@ -35,6 +36,12 @@ public class FindImports extends Recipe { required = false) String typePattern; + @Option(displayName = "Match inherited", + description = "When enabled, find types that inherit from a deprecated type.", + required = false) + @Nullable + Boolean matchInherited; + @Override public String getDisplayName() { return "Find source files with imports"; @@ -48,7 +55,7 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - TypeMatcher typeMatcher = new TypeMatcher(typePattern); + TypeMatcher typeMatcher = new TypeMatcher(typePattern, Boolean.TRUE.equals(matchInherited)); return new JavaIsoVisitor<ExecutionContext>() { @Override public J.Import visitImport(J.Import anImport, ExecutionContext executionContext) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/UsesField.java b/rewrite-java/src/main/java/org/openrewrite/java/search/UsesField.java index 23cba757cb2..9cc53dad760 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/UsesField.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/UsesField.java @@ -17,12 +17,13 @@ import lombok.RequiredArgsConstructor; import org.openrewrite.Tree; +import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.TypeMatcher; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.marker.SearchResult; import static java.util.Objects.requireNonNull; @@ -37,7 +38,8 @@ public J visit(@Nullable Tree tree, P p) { if (tree instanceof JavaSourceFile) { JavaSourceFile cu = (JavaSourceFile) requireNonNull(tree); for (JavaType.Variable variable : cu.getTypesInUse().getVariables()) { - if (variable.getName().equals(field) && TypeUtils.isOfClassType(variable.getOwner(), owner)) { + if (StringUtils.matchesGlob(variable.getName(), field) && + new TypeMatcher(owner, true).matches(variable.getOwner())) { return SearchResult.found(cu); } } From ba69707bada394155c759974c232e3d14811be63 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Fri, 29 Sep 2023 10:48:41 -0700 Subject: [PATCH 279/447] Named JavaTemplate parameters --- .../org/openrewrite/internal/StringUtils.java | 1 - .../java/JavaTemplateMatchTest.java | 42 +-- .../java/JavaTemplateNamedTest.java | 67 ++++ .../java/search/SemanticallyEqualTest.java | 47 ++- .../src/main/antlr/TemplateParameterLexer.g4 | 7 +- .../src/main/antlr/TemplateParameterParser.g4 | 15 +- .../org/openrewrite/java/JavaTemplate.java | 200 ------------ .../java/JavaTemplateSemanticallyEqual.java | 93 ++++-- .../grammar/AnnotationSignatureLexer.java | 13 +- .../grammar/AnnotationSignatureParser.java | 18 +- ...AnnotationSignatureParserBaseListener.java | 2 +- .../AnnotationSignatureParserBaseVisitor.java | 2 +- .../AnnotationSignatureParserListener.java | 2 +- .../AnnotationSignatureParserVisitor.java | 2 +- .../grammar/MethodSignatureLexer.java | 13 +- .../grammar/MethodSignatureParser.java | 18 +- .../MethodSignatureParserBaseListener.java | 2 +- .../MethodSignatureParserBaseVisitor.java | 2 +- .../MethodSignatureParserListener.java | 2 +- .../grammar/MethodSignatureParserVisitor.java | 2 +- .../grammar/TemplateParameterLexer.interp | 17 +- .../grammar/TemplateParameterLexer.java | 178 +++++----- .../grammar/TemplateParameterLexer.tokens | 23 +- .../grammar/TemplateParameterParser.interp | 15 +- .../grammar/TemplateParameterParser.java | 308 ++++++++++++++---- .../grammar/TemplateParameterParser.tokens | 23 +- .../TemplateParameterParserBaseListener.java | 38 ++- .../TemplateParameterParserBaseVisitor.java | 23 +- .../TemplateParameterParserListener.java | 32 +- .../TemplateParameterParserVisitor.java | 20 +- .../java/internal/template/Substitutions.java | 160 +++++---- .../openrewrite/java/template/Matcher.java | 25 -- .../openrewrite/java/template/Matches.java | 28 -- .../openrewrite/java/template/NotMatches.java | 28 -- .../openrewrite/java/template/Primitive.java | 26 -- .../java/template/package-info.java | 19 -- 36 files changed, 807 insertions(+), 706 deletions(-) create mode 100644 rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateNamedTest.java delete mode 100644 rewrite-java/src/main/java/org/openrewrite/java/template/Matcher.java delete mode 100644 rewrite-java/src/main/java/org/openrewrite/java/template/Matches.java delete mode 100644 rewrite-java/src/main/java/org/openrewrite/java/template/NotMatches.java delete mode 100644 rewrite-java/src/main/java/org/openrewrite/java/template/Primitive.java delete mode 100644 rewrite-java/src/main/java/org/openrewrite/java/template/package-info.java diff --git a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java index c84f3fe7c0e..47cf15b95a0 100644 --- a/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/internal/StringUtils.java @@ -355,7 +355,6 @@ public static int indexOf(String text, Predicate<Character> test) { * @return the number of times the substring is found in the target. 0 if no occurrences are found. */ public static int countOccurrences(@NonNull String text, @NonNull String substring) { - if (text.isEmpty() || substring.isEmpty()) { return 0; } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateMatchTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateMatchTest.java index 29ff36230ba..35dff7db112 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateMatchTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateMatchTest.java @@ -16,7 +16,6 @@ package org.openrewrite.java; import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; import org.openrewrite.ExecutionContext; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; @@ -28,19 +27,15 @@ class JavaTemplateMatchTest implements RewriteTest { - // IMPORTANT: This test needs to stay at the top, so that the name of `JavaTemplateMatchTest$1_Equals1` matches the expectations - @DocumentExample - @SuppressWarnings("ConstantValue") + @SuppressWarnings({"ConstantValue", "ConstantConditions"}) @Test - void matchBinaryUsingCompile() { + void matchBinary() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new JavaVisitor<>() { - // matches manually written class JavaTemplateMatchTest$1_Equals1 below - private final JavaTemplate template = JavaTemplate.compile(this, "Equals1", (Integer i) -> 1 == i).build(); - @Override public J visitBinary(J.Binary binary, ExecutionContext ctx) { - return template.matches(getCursor()) ? SearchResult.found(binary) : super.visitBinary(binary, ctx); + return JavaTemplate.matches("1 == #{any(int)}", getCursor()) ? + SearchResult.found(binary) : super.visitBinary(binary, ctx); } })), java( @@ -63,14 +58,14 @@ class Test { )); } - @SuppressWarnings("ConstantValue") + @SuppressWarnings("ConstantConditions") @Test - void matchBinary() { + void matchNamedParameter() { rewriteRun( spec -> spec.recipe(toRecipe(() -> new JavaVisitor<>() { @Override public J visitBinary(J.Binary binary, ExecutionContext ctx) { - return JavaTemplate.matches("1 == #{any(int)}", getCursor()) ? + return JavaTemplate.matches("#{n:any(int)} == #{n}", getCursor()) ? SearchResult.found(binary) : super.visitBinary(binary, ctx); } })), @@ -78,23 +73,19 @@ public J visitBinary(J.Binary binary, ExecutionContext ctx) { """ class Test { boolean b1 = 1 == 2; - boolean b2 = 1 == 3; - - boolean b3 = 2 == 1; + boolean b2 = 1 == 1; } """, """ class Test { - boolean b1 = /*~~>*/1 == 2; - boolean b2 = /*~~>*/1 == 3; - - boolean b3 = 2 == 1; + boolean b1 = 1 == 2; + boolean b2 = /*~~>*/1 == 1; } """ )); } - @SuppressWarnings("ConstantValue") + @SuppressWarnings({"ConstantValue", "ConstantConditions"}) @Test void extractParameterUsingMatcher() { rewriteRun( @@ -532,14 +523,3 @@ class Test { } } -/** - * This class looks like a class which would be generated by the `rewrite-templating` annotation processor - * and is used by the test {@link JavaTemplateMatchTest#matchBinaryUsingCompile()}. - */ -@SuppressWarnings("unused") -class JavaTemplateMatchTest$1_Equals1 { - static JavaTemplate.Builder getTemplate(JavaVisitor<?> visitor) { - return JavaTemplate.builder("1 == #{any(int)}"); - } - -} diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateNamedTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateNamedTest.java new file mode 100644 index 00000000000..2a1eb937382 --- /dev/null +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateNamedTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java; + +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.tree.J; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.RewriteTest.toRecipe; + +public class JavaTemplateNamedTest implements RewriteTest { + + @Test + void replaceSingleStatement() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new JavaVisitor<>() { + @Override + public J visitAssert(J.Assert anAssert, ExecutionContext p) { + return JavaTemplate.builder( + """ + if(#{name:any(int)} != #{}) { + #{name}++; + }""" + ) + .build() + .apply(getCursor(), anAssert.getCoordinates().replace(), + ((J.Binary) anAssert.getCondition()).getLeft(), "1"); + } + })), + java( + """ + class Test { + int n; + void test() { + assert n == 0; + } + } + """, + """ + class Test { + int n; + void test() { + if (n != 1) { + n++; + } + } + } + """ + ) + ); + } +} diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/SemanticallyEqualTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/SemanticallyEqualTest.java index 13d22ad3289..0891839f8a1 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/SemanticallyEqualTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/SemanticallyEqualTest.java @@ -22,28 +22,49 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -public class SemanticallyEqualTest { +public class SemanticallyEqualTest { private final JavaParser javaParser = JavaParser.fromJavaVersion().build(); @Test void compareAbstractMethods() { - assertEqualToSelf(""" - abstract class A { - abstract void m(); - } - """); + assertEqualToSelf( + """ + abstract class A { + abstract void m(); + } + """ + ); } @Test void compareClassModifierLists() { - assertEqual(""" - public abstract class A { - } - """, """ - abstract public class A { - } - """); + assertEqual( + """ + public abstract class A { + } + """, + """ + abstract public class A { + } + """ + ); + } + + @Test + void compareLiterals() { + assertEqual( + """ + class A { + int n = 1; + } + """, + """ + class A { + int n = 1; + } + """ + ); } private void assertEqualToSelf(@Language("java") String a) { diff --git a/rewrite-java/src/main/antlr/TemplateParameterLexer.g4 b/rewrite-java/src/main/antlr/TemplateParameterLexer.g4 index d10291fb735..76ebd718835 100644 --- a/rewrite-java/src/main/antlr/TemplateParameterLexer.g4 +++ b/rewrite-java/src/main/antlr/TemplateParameterLexer.g4 @@ -2,12 +2,9 @@ lexer grammar TemplateParameterLexer; LPAREN : '('; RPAREN : ')'; -LBRACK : '['; -RBRACK : ']'; DOT : '.'; - +COLON : ':'; COMMA : ','; -SPACE : ' '; FullyQualifiedName : 'boolean' @@ -52,3 +49,5 @@ JavaLetterOrDigit [\uD800-\uDBFF] [\uDC00-\uDFFF] {Character.isJavaIdentifierPart(Character.toCodePoint((char)_input.LA(-2), (char)_input.LA(-1)))}? ; + +S : [ \t\r\n] -> skip ; diff --git a/rewrite-java/src/main/antlr/TemplateParameterParser.g4 b/rewrite-java/src/main/antlr/TemplateParameterParser.g4 index 127e6d6f2fb..e338698dbcb 100644 --- a/rewrite-java/src/main/antlr/TemplateParameterParser.g4 +++ b/rewrite-java/src/main/antlr/TemplateParameterParser.g4 @@ -3,7 +3,16 @@ parser grammar TemplateParameterParser; options { tokenVocab=TemplateParameterLexer; } matcherPattern - : matcherName LPAREN matcherParameter* RPAREN + : typedPattern + | parameterName + ; + +typedPattern + : (parameterName COLON)? patternType + ; + +patternType + : matcherName LPAREN ((matcherParameter COMMA)* matcherParameter)? RPAREN ; matcherParameter @@ -12,6 +21,10 @@ matcherParameter | Number ; +parameterName + : Identifier + ; + matcherName : Identifier ; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java index 0c1b994605a..ab383da372d 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java @@ -27,8 +27,6 @@ import org.openrewrite.java.tree.JavaCoordinates; import org.openrewrite.template.SourceTemplate; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; @@ -59,10 +57,6 @@ public <J2 extends J> J2 apply(Cursor scope, JavaCoordinates coordinates, Object throw new IllegalArgumentException("`scope` must point to a J instance."); } - if (parameters.length != parameterCount) { - throw new IllegalArgumentException("This template requires " + parameterCount + " parameters."); - } - Substitutions substitutions = new Substitutions(code, parameters); String substitutedTemplate = substitutions.substitute(); onAfterVariableSubstitution.accept(substitutedTemplate); @@ -202,198 +196,4 @@ public JavaTemplate build() { onAfterVariableSubstitution, onBeforeParseTemplate); } } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P0 p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P1<?> p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P2<?, ?> p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P3<?, ?, ?> p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P4<?, ?, ?, ?> p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P5<?, ?, ?, ?, ?> p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P6<?, ?, ?, ?, ?, ?> p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P7<?, ?, ?, ?, ?, ?, ?> p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P8<?, ?, ?, ?, ?, ?, ?, ?> p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P9<?, ?, ?, ?, ?, ?, ?, ?, ?> p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, P10<?, ?, ?, ?, ?, ?, ?, ?, ?, ?> p) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F0<?> f) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F1<?, ?> f) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F2<?, ?, ?> f) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F3<?, ?, ?, ?> f) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F4<?, ?, ?, ?, ?> f) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F5<?, ?, ?, ?, ?, ?> f) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F6<?, ?, ?, ?, ?, ?, ?> f) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F7<?, ?, ?, ?, ?, ?, ?, ?> f) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F8<?, ?, ?, ?, ?, ?, ?, ?, ?> f) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F9<?, ?, ?, ?, ?, ?, ?, ?, ?, ?> f) { - return new PatternBuilder(name).build(owner); - } - - public static JavaTemplate.Builder compile(JavaVisitor<?> owner, String name, F10<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> f) { - return new PatternBuilder(name).build(owner); - } - - @Value - @SuppressWarnings("unused") - static class PatternBuilder { - String name; - - public JavaTemplate.Builder build(JavaVisitor<?> owner) { - try { - Class<?> templateClass = Class.forName(owner.getClass().getName() + "_" + name, true, - owner.getClass().getClassLoader()); - Method getTemplate = templateClass.getDeclaredMethod("getTemplate", JavaVisitor.class); - return (JavaTemplate.Builder) getTemplate.invoke(null, owner); - } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | - IllegalAccessException e) { - throw new RuntimeException(e); - } - } - } - - public interface P0 { - void accept() throws Exception; - } - - public interface P1<P1> { - void accept(P1 p1) throws Exception; - } - - public interface P2<P1, P2> { - void accept(P1 p1, P2 p2) throws Exception; - } - - public interface P3<P1, P2, P3> { - void accept(P1 p1, P2 p2, P3 p3) throws Exception; - } - - public interface P4<P1, P2, P3, P4> { - void accept(P1 p1, P2 p2, P3 p3, P4 p4) throws Exception; - } - - public interface P5<P1, P2, P3, P4, P5> { - void accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) throws Exception; - } - - public interface P6<P1, P2, P3, P4, P5, P6> { - void accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6) throws Exception; - } - - public interface P7<P1, P2, P3, P4, P5, P6, P7> { - void accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7) throws Exception; - } - - public interface P8<P1, P2, P3, P4, P5, P6, P7, P8> { - void accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8) throws Exception; - } - - public interface P9<P1, P2, P3, P4, P5, P6, P7, P8, P9> { - void accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9) throws Exception; - } - - public interface P10<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> { - void accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9, P10 p10) throws Exception; - } - - public interface F0<R> { - R accept() throws Exception; - } - - public interface F1<R, P1> { - R accept(P1 p1) throws Exception; - } - - public interface F2<R, P1, P2> { - R accept(P1 p1, P2 p2) throws Exception; - } - - public interface F3<R, P1, P2, P3> { - R accept(P1 p1, P2 p2, P3 p3) throws Exception; - } - - public interface F4<R, P1, P2, P3, P4> { - R accept(P1 p1, P2 p2, P3 p3, P4 p4) throws Exception; - } - - public interface F5<R, P1, P2, P3, P4, P5> { - R accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) throws Exception; - } - - public interface F6<R, P1, P2, P3, P4, P5, P6> { - R accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6) throws Exception; - } - - public interface F7<R, P1, P2, P3, P4, P5, P6, P7> { - R accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7) throws Exception; - } - - public interface F8<R, P1, P2, P3, P4, P5, P6, P7, P8> { - R accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8) throws Exception; - } - - public interface F9<R, P1, P2, P3, P4, P5, P6, P7, P8, P9> { - R accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9) throws Exception; - } - - public interface F10<R, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> { - R accept(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9, P10 p10) throws Exception; - } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplateSemanticallyEqual.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplateSemanticallyEqual.java index 7f02c4b037b..9b4f7c47697 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplateSemanticallyEqual.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaTemplateSemanticallyEqual.java @@ -20,6 +20,7 @@ import org.antlr.v4.runtime.*; import org.openrewrite.Cursor; import org.openrewrite.internal.PropertyPlaceholderHelper; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.grammar.TemplateParameterLexer; import org.openrewrite.java.internal.grammar.TemplateParameterParser; import org.openrewrite.java.search.SemanticallyEqual; @@ -27,10 +28,7 @@ import org.openrewrite.marker.Marker; import org.openrewrite.marker.Markers; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; +import java.util.*; import static org.openrewrite.Tree.randomId; @@ -68,6 +66,7 @@ private static J[] createTemplateParameters(String code) { List<J> parameters = new ArrayList<>(); String substituted = code; + Map<String, String> typedPatternByName = new HashMap<>(); while (true) { String previous = substituted; substituted = propertyPlaceholderHelper.replacePlaceholders(substituted, key -> { @@ -87,28 +86,24 @@ public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, }); TemplateParameterParser.MatcherPatternContext ctx = parser.matcherPattern(); - String matcherName = ctx.matcherName().Identifier().getText(); - List<TemplateParameterParser.MatcherParameterContext> params = ctx.matcherParameter(); - - if ("any".equals(matcherName)) { - String fqn; - - if (params.size() == 1) { - if (params.get(0).Identifier() != null) { - fqn = params.get(0).Identifier().getText(); - } else { - fqn = params.get(0).FullyQualifiedName().getText(); - } - } else { - fqn = "java.lang.Object"; + if (ctx.typedPattern() == null) { + String paramName = ctx.parameterName().Identifier().getText(); + s = typedPatternByName.get(paramName); + if (s == null) { + throw new IllegalArgumentException("The parameter " + paramName + " must be defined before it is referenced."); } + } else { + TemplateParameterParser.TypedPatternContext typedPattern = ctx.typedPattern(); + s = typedParameter(key, typedPattern); - s = fqn.replace("$", "."); + String name = null; + if (typedPattern.parameterName() != null) { + name = typedPattern.parameterName().Identifier().getText(); + typedPatternByName.put(name, s); + } - Markers markers = Markers.build(Collections.singleton(new TemplateParameter(randomId(), s))); + Markers markers = Markers.build(Collections.singleton(new TemplateParameter(randomId(), s, name))); parameters.add(new J.Empty(randomId(), Space.EMPTY, markers)); - } else { - throw new IllegalArgumentException("Invalid template matcher '" + key + "'"); } } else { throw new IllegalArgumentException("Only typed placeholders are allowed."); @@ -125,10 +120,34 @@ public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, return parameters.toArray(new J[0]); } + private static String typedParameter(String key, TemplateParameterParser.TypedPatternContext typedPattern) { + String matcherName = typedPattern.patternType().matcherName().Identifier().getText(); + List<TemplateParameterParser.MatcherParameterContext> params = typedPattern.patternType().matcherParameter(); + + if ("any".equals(matcherName)) { + String fqn; + + if (params.size() == 1) { + if (params.get(0).Identifier() != null) { + fqn = params.get(0).Identifier().getText(); + } else { + fqn = params.get(0).FullyQualifiedName().getText(); + } + } else { + fqn = "java.lang.Object"; + } + + return fqn.replace("$", "."); + } else { + throw new IllegalArgumentException("Invalid template matcher '" + key + "'"); + } + } + private static TemplateMatchResult matchTemplate(J templateTree, Cursor cursor) { JavaTemplateSemanticallyEqualVisitor semanticallyEqualVisitor = new JavaTemplateSemanticallyEqualVisitor(); semanticallyEqualVisitor.visit(templateTree, cursor.getValue(), cursor.getParentOrThrow()); - return new TemplateMatchResult(semanticallyEqualVisitor.isEqual(), semanticallyEqualVisitor.matchedParameters); + return new TemplateMatchResult(semanticallyEqualVisitor.isEqual(), new ArrayList<>( + semanticallyEqualVisitor.matchedParameters.keySet())); } @Value @@ -136,12 +155,14 @@ private static TemplateMatchResult matchTemplate(J templateTree, Cursor cursor) private static class TemplateParameter implements Marker { UUID id; String typeName; + + @Nullable + String name; } @SuppressWarnings("ConstantConditions") private static class JavaTemplateSemanticallyEqualVisitor extends SemanticallyEqualVisitor { - - final List<J> matchedParameters = new ArrayList<>(); + final Map<J, String> matchedParameters = new LinkedHashMap<>(); public JavaTemplateSemanticallyEqualVisitor() { super(true); @@ -150,16 +171,28 @@ public JavaTemplateSemanticallyEqualVisitor() { private boolean matchTemplateParameterPlaceholder(J.Empty empty, J j) { if (j instanceof TypedTree && !(j instanceof J.Primitive)) { TemplateParameter marker = (TemplateParameter) empty.getMarkers().getMarkers().get(0); - if ("java.lang.Object".equals(marker.typeName) - || TypeUtils.isAssignableTo(marker.typeName, ((TypedTree) j).getType())) { - return registerMatch(j); + + if (marker.name != null) { + for (Map.Entry<J, String> matchedParameter : matchedParameters.entrySet()) { + if (matchedParameter.getValue().equals(marker.name)) { + if(!SemanticallyEqual.areEqual(matchedParameter.getKey(), j)) { + return false; + } + } + } + } + + if ("java.lang.Object".equals(marker.typeName) || + TypeUtils.isAssignableTo(marker.typeName, ((TypedTree) j).getType())) { + registerMatch(j, marker.name); + return true; } } return false; } - private boolean registerMatch(J j) { - return matchedParameters.add(j); + private void registerMatch(J j, @Nullable String name) { + matchedParameters.put(j, name); } @Override diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureLexer.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureLexer.java index d78f78e0922..d8f5167b9ab 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureLexer.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureLexer.java @@ -15,13 +15,14 @@ */ // Generated from java-escape by ANTLR 4.11.1 package org.openrewrite.java.internal.grammar; - +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.LexerATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) public class AnnotationSignatureLexer extends Lexer { @@ -466,4 +467,4 @@ private boolean JavaLetterOrDigit_sempred(RuleContext _localctx, int predIndex) _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); } } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.java index 25c799f6dca..b5509f2d7b3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.java @@ -15,18 +15,14 @@ */ // Generated from java-escape by ANTLR 4.11.1 package org.openrewrite.java.internal.grammar; - -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.ParserATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.tree.ParseTreeListener; -import org.antlr.v4.runtime.tree.ParseTreeVisitor; -import org.antlr.v4.runtime.tree.TerminalNode; - +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.*; +import org.antlr.v4.runtime.tree.*; import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) public class AnnotationSignatureParser extends Parser { @@ -819,4 +815,4 @@ public final LiteralContext literal() throws RecognitionException { _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); } } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserBaseListener.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserBaseListener.java index 42682126bfa..8981fb690ce 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserBaseListener.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserBaseListener.java @@ -172,4 +172,4 @@ public class AnnotationSignatureParserBaseListener implements AnnotationSignatur * <p>The default implementation does nothing.</p> */ @Override public void visitErrorNode(ErrorNode node) { } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserBaseVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserBaseVisitor.java index 72ba029e821..b5a9243d864 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserBaseVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserBaseVisitor.java @@ -97,4 +97,4 @@ public class AnnotationSignatureParserBaseVisitor<T> extends AbstractParseTreeVi * {@link #visitChildren} on {@code ctx}.</p> */ @Override public T visitLiteral(AnnotationSignatureParser.LiteralContext ctx) { return visitChildren(ctx); } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserListener.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserListener.java index fbc14b536b0..2d2ffdec3d1 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserListener.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserListener.java @@ -122,4 +122,4 @@ public interface AnnotationSignatureParserListener extends ParseTreeListener { * @param ctx the parse tree */ void exitLiteral(AnnotationSignatureParser.LiteralContext ctx); -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserVisitor.java index fd48173e827..6c9ac7fef06 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParserVisitor.java @@ -85,4 +85,4 @@ public interface AnnotationSignatureParserVisitor<T> extends ParseTreeVisitor<T> * @return the visitor result */ T visitLiteral(AnnotationSignatureParser.LiteralContext ctx); -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureLexer.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureLexer.java index 19d1ff9dabe..d2c06236a3c 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureLexer.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureLexer.java @@ -15,13 +15,14 @@ */ // Generated from java-escape by ANTLR 4.11.1 package org.openrewrite.java.internal.grammar; - +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.LexerATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) public class MethodSignatureLexer extends Lexer { @@ -225,4 +226,4 @@ private boolean JavaLetterOrDigit_sempred(RuleContext _localctx, int predIndex) _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); } } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParser.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParser.java index 6ee4625daf0..5780287cfc4 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParser.java @@ -15,18 +15,14 @@ */ // Generated from java-escape by ANTLR 4.11.1 package org.openrewrite.java.internal.grammar; - -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.ParserATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.tree.ParseTreeListener; -import org.antlr.v4.runtime.tree.ParseTreeVisitor; -import org.antlr.v4.runtime.tree.TerminalNode; - +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.*; +import org.antlr.v4.runtime.tree.*; import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) public class MethodSignatureParser extends Parser { @@ -1305,4 +1301,4 @@ private boolean formalTypePattern_sempred(FormalTypePatternContext _localctx, in _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); } } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserBaseListener.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserBaseListener.java index ef78ae6eb75..7af9fbca205 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserBaseListener.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserBaseListener.java @@ -172,4 +172,4 @@ public class MethodSignatureParserBaseListener implements MethodSignatureParserL * <p>The default implementation does nothing.</p> */ @Override public void visitErrorNode(ErrorNode node) { } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserBaseVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserBaseVisitor.java index 4a89c2f3200..35d2b2d4881 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserBaseVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserBaseVisitor.java @@ -97,4 +97,4 @@ public class MethodSignatureParserBaseVisitor<T> extends AbstractParseTreeVisito * {@link #visitChildren} on {@code ctx}.</p> */ @Override public T visitSimpleNamePattern(MethodSignatureParser.SimpleNamePatternContext ctx) { return visitChildren(ctx); } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserListener.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserListener.java index e602579696a..3b234f8b3dc 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserListener.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserListener.java @@ -122,4 +122,4 @@ public interface MethodSignatureParserListener extends ParseTreeListener { * @param ctx the parse tree */ void exitSimpleNamePattern(MethodSignatureParser.SimpleNamePatternContext ctx); -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserVisitor.java index 8d1ff019f21..ff0b0e9c790 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/MethodSignatureParserVisitor.java @@ -85,4 +85,4 @@ public interface MethodSignatureParserVisitor<T> extends ParseTreeVisitor<T> { * @return the visitor result */ T visitSimpleNamePattern(MethodSignatureParser.SimpleNamePatternContext ctx); -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.interp b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.interp index 6d8d8734c8c..363033850cf 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.interp +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.interp @@ -2,11 +2,10 @@ token literal names: null '(' ')' -'[' -']' '.' +':' ',' -' ' +null null null null @@ -15,28 +14,26 @@ token symbolic names: null LPAREN RPAREN -LBRACK -RBRACK DOT +COLON COMMA -SPACE FullyQualifiedName Number Identifier +S rule names: LPAREN RPAREN -LBRACK -RBRACK DOT +COLON COMMA -SPACE FullyQualifiedName Number Identifier JavaLetter JavaLetterOrDigit +S channel names: DEFAULT_TOKEN_CHANNEL @@ -46,4 +43,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 10, 127, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 4, 7, 94, 8, 7, 11, 7, 12, 7, 95, 3, 7, 98, 8, 7, 1, 8, 4, 8, 101, 8, 8, 11, 8, 12, 8, 102, 1, 9, 1, 9, 5, 9, 107, 8, 9, 10, 9, 12, 9, 110, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 118, 8, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 126, 8, 11, 0, 0, 12, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 0, 23, 0, 1, 0, 6, 1, 0, 48, 57, 4, 0, 36, 36, 65, 90, 95, 95, 97, 122, 2, 0, 0, 127, 55296, 56319, 1, 0, 55296, 56319, 1, 0, 56320, 57343, 5, 0, 36, 36, 48, 57, 65, 90, 95, 95, 97, 122, 141, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 1, 25, 1, 0, 0, 0, 3, 27, 1, 0, 0, 0, 5, 29, 1, 0, 0, 0, 7, 31, 1, 0, 0, 0, 9, 33, 1, 0, 0, 0, 11, 35, 1, 0, 0, 0, 13, 37, 1, 0, 0, 0, 15, 97, 1, 0, 0, 0, 17, 100, 1, 0, 0, 0, 19, 104, 1, 0, 0, 0, 21, 117, 1, 0, 0, 0, 23, 125, 1, 0, 0, 0, 25, 26, 5, 40, 0, 0, 26, 2, 1, 0, 0, 0, 27, 28, 5, 41, 0, 0, 28, 4, 1, 0, 0, 0, 29, 30, 5, 91, 0, 0, 30, 6, 1, 0, 0, 0, 31, 32, 5, 93, 0, 0, 32, 8, 1, 0, 0, 0, 33, 34, 5, 46, 0, 0, 34, 10, 1, 0, 0, 0, 35, 36, 5, 44, 0, 0, 36, 12, 1, 0, 0, 0, 37, 38, 5, 32, 0, 0, 38, 14, 1, 0, 0, 0, 39, 40, 5, 98, 0, 0, 40, 41, 5, 111, 0, 0, 41, 42, 5, 111, 0, 0, 42, 43, 5, 108, 0, 0, 43, 44, 5, 101, 0, 0, 44, 45, 5, 97, 0, 0, 45, 98, 5, 110, 0, 0, 46, 47, 5, 98, 0, 0, 47, 48, 5, 121, 0, 0, 48, 49, 5, 116, 0, 0, 49, 98, 5, 101, 0, 0, 50, 51, 5, 99, 0, 0, 51, 52, 5, 104, 0, 0, 52, 53, 5, 97, 0, 0, 53, 98, 5, 114, 0, 0, 54, 55, 5, 100, 0, 0, 55, 56, 5, 111, 0, 0, 56, 57, 5, 117, 0, 0, 57, 58, 5, 98, 0, 0, 58, 59, 5, 108, 0, 0, 59, 98, 5, 101, 0, 0, 60, 61, 5, 102, 0, 0, 61, 62, 5, 108, 0, 0, 62, 63, 5, 111, 0, 0, 63, 64, 5, 97, 0, 0, 64, 98, 5, 116, 0, 0, 65, 66, 5, 105, 0, 0, 66, 67, 5, 110, 0, 0, 67, 98, 5, 116, 0, 0, 68, 69, 5, 108, 0, 0, 69, 70, 5, 111, 0, 0, 70, 71, 5, 110, 0, 0, 71, 98, 5, 103, 0, 0, 72, 73, 5, 115, 0, 0, 73, 74, 5, 104, 0, 0, 74, 75, 5, 111, 0, 0, 75, 76, 5, 114, 0, 0, 76, 98, 5, 116, 0, 0, 77, 78, 5, 83, 0, 0, 78, 79, 5, 116, 0, 0, 79, 80, 5, 114, 0, 0, 80, 81, 5, 105, 0, 0, 81, 82, 5, 110, 0, 0, 82, 98, 5, 103, 0, 0, 83, 84, 5, 79, 0, 0, 84, 85, 5, 98, 0, 0, 85, 86, 5, 106, 0, 0, 86, 87, 5, 101, 0, 0, 87, 88, 5, 99, 0, 0, 88, 98, 5, 116, 0, 0, 89, 93, 3, 19, 9, 0, 90, 91, 3, 9, 4, 0, 91, 92, 3, 19, 9, 0, 92, 94, 1, 0, 0, 0, 93, 90, 1, 0, 0, 0, 94, 95, 1, 0, 0, 0, 95, 93, 1, 0, 0, 0, 95, 96, 1, 0, 0, 0, 96, 98, 1, 0, 0, 0, 97, 39, 1, 0, 0, 0, 97, 46, 1, 0, 0, 0, 97, 50, 1, 0, 0, 0, 97, 54, 1, 0, 0, 0, 97, 60, 1, 0, 0, 0, 97, 65, 1, 0, 0, 0, 97, 68, 1, 0, 0, 0, 97, 72, 1, 0, 0, 0, 97, 77, 1, 0, 0, 0, 97, 83, 1, 0, 0, 0, 97, 89, 1, 0, 0, 0, 98, 16, 1, 0, 0, 0, 99, 101, 7, 0, 0, 0, 100, 99, 1, 0, 0, 0, 101, 102, 1, 0, 0, 0, 102, 100, 1, 0, 0, 0, 102, 103, 1, 0, 0, 0, 103, 18, 1, 0, 0, 0, 104, 108, 3, 21, 10, 0, 105, 107, 3, 23, 11, 0, 106, 105, 1, 0, 0, 0, 107, 110, 1, 0, 0, 0, 108, 106, 1, 0, 0, 0, 108, 109, 1, 0, 0, 0, 109, 20, 1, 0, 0, 0, 110, 108, 1, 0, 0, 0, 111, 118, 7, 1, 0, 0, 112, 113, 8, 2, 0, 0, 113, 118, 4, 10, 0, 0, 114, 115, 7, 3, 0, 0, 115, 116, 7, 4, 0, 0, 116, 118, 4, 10, 1, 0, 117, 111, 1, 0, 0, 0, 117, 112, 1, 0, 0, 0, 117, 114, 1, 0, 0, 0, 118, 22, 1, 0, 0, 0, 119, 126, 7, 5, 0, 0, 120, 121, 8, 2, 0, 0, 121, 126, 4, 11, 2, 0, 122, 123, 7, 3, 0, 0, 123, 124, 7, 4, 0, 0, 124, 126, 4, 11, 3, 0, 125, 119, 1, 0, 0, 0, 125, 120, 1, 0, 0, 0, 125, 122, 1, 0, 0, 0, 126, 24, 1, 0, 0, 0, 7, 0, 95, 97, 102, 108, 117, 125, 0] \ No newline at end of file +[4, 0, 9, 125, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 4, 5, 88, 8, 5, 11, 5, 12, 5, 89, 3, 5, 92, 8, 5, 1, 6, 4, 6, 95, 8, 6, 11, 6, 12, 6, 96, 1, 7, 1, 7, 5, 7, 101, 8, 7, 10, 7, 12, 7, 104, 9, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 112, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 120, 8, 9, 1, 10, 1, 10, 1, 10, 1, 10, 0, 0, 11, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 0, 19, 0, 21, 9, 1, 0, 7, 1, 0, 48, 57, 4, 0, 36, 36, 65, 90, 95, 95, 97, 122, 2, 0, 0, 127, 55296, 56319, 1, 0, 55296, 56319, 1, 0, 56320, 57343, 5, 0, 36, 36, 48, 57, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 139, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 1, 23, 1, 0, 0, 0, 3, 25, 1, 0, 0, 0, 5, 27, 1, 0, 0, 0, 7, 29, 1, 0, 0, 0, 9, 31, 1, 0, 0, 0, 11, 91, 1, 0, 0, 0, 13, 94, 1, 0, 0, 0, 15, 98, 1, 0, 0, 0, 17, 111, 1, 0, 0, 0, 19, 119, 1, 0, 0, 0, 21, 121, 1, 0, 0, 0, 23, 24, 5, 40, 0, 0, 24, 2, 1, 0, 0, 0, 25, 26, 5, 41, 0, 0, 26, 4, 1, 0, 0, 0, 27, 28, 5, 46, 0, 0, 28, 6, 1, 0, 0, 0, 29, 30, 5, 58, 0, 0, 30, 8, 1, 0, 0, 0, 31, 32, 5, 44, 0, 0, 32, 10, 1, 0, 0, 0, 33, 34, 5, 98, 0, 0, 34, 35, 5, 111, 0, 0, 35, 36, 5, 111, 0, 0, 36, 37, 5, 108, 0, 0, 37, 38, 5, 101, 0, 0, 38, 39, 5, 97, 0, 0, 39, 92, 5, 110, 0, 0, 40, 41, 5, 98, 0, 0, 41, 42, 5, 121, 0, 0, 42, 43, 5, 116, 0, 0, 43, 92, 5, 101, 0, 0, 44, 45, 5, 99, 0, 0, 45, 46, 5, 104, 0, 0, 46, 47, 5, 97, 0, 0, 47, 92, 5, 114, 0, 0, 48, 49, 5, 100, 0, 0, 49, 50, 5, 111, 0, 0, 50, 51, 5, 117, 0, 0, 51, 52, 5, 98, 0, 0, 52, 53, 5, 108, 0, 0, 53, 92, 5, 101, 0, 0, 54, 55, 5, 102, 0, 0, 55, 56, 5, 108, 0, 0, 56, 57, 5, 111, 0, 0, 57, 58, 5, 97, 0, 0, 58, 92, 5, 116, 0, 0, 59, 60, 5, 105, 0, 0, 60, 61, 5, 110, 0, 0, 61, 92, 5, 116, 0, 0, 62, 63, 5, 108, 0, 0, 63, 64, 5, 111, 0, 0, 64, 65, 5, 110, 0, 0, 65, 92, 5, 103, 0, 0, 66, 67, 5, 115, 0, 0, 67, 68, 5, 104, 0, 0, 68, 69, 5, 111, 0, 0, 69, 70, 5, 114, 0, 0, 70, 92, 5, 116, 0, 0, 71, 72, 5, 83, 0, 0, 72, 73, 5, 116, 0, 0, 73, 74, 5, 114, 0, 0, 74, 75, 5, 105, 0, 0, 75, 76, 5, 110, 0, 0, 76, 92, 5, 103, 0, 0, 77, 78, 5, 79, 0, 0, 78, 79, 5, 98, 0, 0, 79, 80, 5, 106, 0, 0, 80, 81, 5, 101, 0, 0, 81, 82, 5, 99, 0, 0, 82, 92, 5, 116, 0, 0, 83, 87, 3, 15, 7, 0, 84, 85, 3, 5, 2, 0, 85, 86, 3, 15, 7, 0, 86, 88, 1, 0, 0, 0, 87, 84, 1, 0, 0, 0, 88, 89, 1, 0, 0, 0, 89, 87, 1, 0, 0, 0, 89, 90, 1, 0, 0, 0, 90, 92, 1, 0, 0, 0, 91, 33, 1, 0, 0, 0, 91, 40, 1, 0, 0, 0, 91, 44, 1, 0, 0, 0, 91, 48, 1, 0, 0, 0, 91, 54, 1, 0, 0, 0, 91, 59, 1, 0, 0, 0, 91, 62, 1, 0, 0, 0, 91, 66, 1, 0, 0, 0, 91, 71, 1, 0, 0, 0, 91, 77, 1, 0, 0, 0, 91, 83, 1, 0, 0, 0, 92, 12, 1, 0, 0, 0, 93, 95, 7, 0, 0, 0, 94, 93, 1, 0, 0, 0, 95, 96, 1, 0, 0, 0, 96, 94, 1, 0, 0, 0, 96, 97, 1, 0, 0, 0, 97, 14, 1, 0, 0, 0, 98, 102, 3, 17, 8, 0, 99, 101, 3, 19, 9, 0, 100, 99, 1, 0, 0, 0, 101, 104, 1, 0, 0, 0, 102, 100, 1, 0, 0, 0, 102, 103, 1, 0, 0, 0, 103, 16, 1, 0, 0, 0, 104, 102, 1, 0, 0, 0, 105, 112, 7, 1, 0, 0, 106, 107, 8, 2, 0, 0, 107, 112, 4, 8, 0, 0, 108, 109, 7, 3, 0, 0, 109, 110, 7, 4, 0, 0, 110, 112, 4, 8, 1, 0, 111, 105, 1, 0, 0, 0, 111, 106, 1, 0, 0, 0, 111, 108, 1, 0, 0, 0, 112, 18, 1, 0, 0, 0, 113, 120, 7, 5, 0, 0, 114, 115, 8, 2, 0, 0, 115, 120, 4, 9, 2, 0, 116, 117, 7, 3, 0, 0, 117, 118, 7, 4, 0, 0, 118, 120, 4, 9, 3, 0, 119, 113, 1, 0, 0, 0, 119, 114, 1, 0, 0, 0, 119, 116, 1, 0, 0, 0, 120, 20, 1, 0, 0, 0, 121, 122, 7, 6, 0, 0, 122, 123, 1, 0, 0, 0, 123, 124, 6, 10, 0, 0, 124, 22, 1, 0, 0, 0, 7, 0, 89, 91, 96, 102, 111, 119, 1, 6, 0, 0] \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.java index 6ce2ae45fed..6b321cb9924 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.java @@ -15,13 +15,14 @@ */ // Generated from java-escape by ANTLR 4.11.1 package org.openrewrite.java.internal.grammar; - +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.LexerATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) public class TemplateParameterLexer extends Lexer { @@ -31,8 +32,8 @@ public class TemplateParameterLexer extends Lexer { protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); public static final int - LPAREN=1, RPAREN=2, LBRACK=3, RBRACK=4, DOT=5, COMMA=6, SPACE=7, FullyQualifiedName=8, - Number=9, Identifier=10; + LPAREN=1, RPAREN=2, DOT=3, COLON=4, COMMA=5, FullyQualifiedName=6, Number=7, + Identifier=8, S=9; public static String[] channelNames = { "DEFAULT_TOKEN_CHANNEL", "HIDDEN" }; @@ -43,22 +44,22 @@ public class TemplateParameterLexer extends Lexer { private static String[] makeRuleNames() { return new String[] { - "LPAREN", "RPAREN", "LBRACK", "RBRACK", "DOT", "COMMA", "SPACE", "FullyQualifiedName", - "Number", "Identifier", "JavaLetter", "JavaLetterOrDigit" + "LPAREN", "RPAREN", "DOT", "COLON", "COMMA", "FullyQualifiedName", "Number", + "Identifier", "JavaLetter", "JavaLetterOrDigit", "S" }; } public static final String[] ruleNames = makeRuleNames(); private static String[] makeLiteralNames() { return new String[] { - null, "'('", "')'", "'['", "']'", "'.'", "','", "' '" + null, "'('", "')'", "'.'", "':'", "','" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { return new String[] { - null, "LPAREN", "RPAREN", "LBRACK", "RBRACK", "DOT", "COMMA", "SPACE", - "FullyQualifiedName", "Number", "Identifier" + null, "LPAREN", "RPAREN", "DOT", "COLON", "COMMA", "FullyQualifiedName", + "Number", "Identifier", "S" }; } private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); @@ -122,9 +123,9 @@ public TemplateParameterLexer(CharStream input) { @Override public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) { switch (ruleIndex) { - case 10: + case 8: return JavaLetter_sempred((RuleContext)_localctx, predIndex); - case 11: + case 9: return JavaLetterOrDigit_sempred((RuleContext)_localctx, predIndex); } return true; @@ -149,82 +150,81 @@ private boolean JavaLetterOrDigit_sempred(RuleContext _localctx, int predIndex) } public static final String _serializedATN = - "\u0004\u0000\n\u007f\u0006\uffff\uffff\u0002\u0000\u0007\u0000\u0002\u0001"+ + "\u0004\u0000\t}\u0006\uffff\uffff\u0002\u0000\u0007\u0000\u0002\u0001"+ "\u0007\u0001\u0002\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004"+ "\u0007\u0004\u0002\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007"+ - "\u0007\u0007\u0002\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b"+ - "\u0007\u000b\u0001\u0000\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0002"+ - "\u0001\u0002\u0001\u0003\u0001\u0003\u0001\u0004\u0001\u0004\u0001\u0005"+ - "\u0001\u0005\u0001\u0006\u0001\u0006\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0004\u0007^\b\u0007\u000b\u0007"+ - "\f\u0007_\u0003\u0007b\b\u0007\u0001\b\u0004\be\b\b\u000b\b\f\bf\u0001"+ - "\t\u0001\t\u0005\tk\b\t\n\t\f\tn\t\t\u0001\n\u0001\n\u0001\n\u0001\n\u0001"+ - "\n\u0001\n\u0003\nv\b\n\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b"+ - "\u0001\u000b\u0001\u000b\u0003\u000b~\b\u000b\u0000\u0000\f\u0001\u0001"+ - "\u0003\u0002\u0005\u0003\u0007\u0004\t\u0005\u000b\u0006\r\u0007\u000f"+ - "\b\u0011\t\u0013\n\u0015\u0000\u0017\u0000\u0001\u0000\u0006\u0001\u0000"+ - "09\u0004\u0000$$AZ__az\u0002\u0000\u0000\u007f\u8000\ud800\u8000\udbff"+ - "\u0001\u0000\u8000\ud800\u8000\udbff\u0001\u0000\u8000\udc00\u8000\udfff"+ - "\u0005\u0000$$09AZ__az\u008d\u0000\u0001\u0001\u0000\u0000\u0000\u0000"+ - "\u0003\u0001\u0000\u0000\u0000\u0000\u0005\u0001\u0000\u0000\u0000\u0000"+ - "\u0007\u0001\u0000\u0000\u0000\u0000\t\u0001\u0000\u0000\u0000\u0000\u000b"+ - "\u0001\u0000\u0000\u0000\u0000\r\u0001\u0000\u0000\u0000\u0000\u000f\u0001"+ - "\u0000\u0000\u0000\u0000\u0011\u0001\u0000\u0000\u0000\u0000\u0013\u0001"+ - "\u0000\u0000\u0000\u0001\u0019\u0001\u0000\u0000\u0000\u0003\u001b\u0001"+ - "\u0000\u0000\u0000\u0005\u001d\u0001\u0000\u0000\u0000\u0007\u001f\u0001"+ - "\u0000\u0000\u0000\t!\u0001\u0000\u0000\u0000\u000b#\u0001\u0000\u0000"+ - "\u0000\r%\u0001\u0000\u0000\u0000\u000fa\u0001\u0000\u0000\u0000\u0011"+ - "d\u0001\u0000\u0000\u0000\u0013h\u0001\u0000\u0000\u0000\u0015u\u0001"+ - "\u0000\u0000\u0000\u0017}\u0001\u0000\u0000\u0000\u0019\u001a\u0005(\u0000"+ - "\u0000\u001a\u0002\u0001\u0000\u0000\u0000\u001b\u001c\u0005)\u0000\u0000"+ - "\u001c\u0004\u0001\u0000\u0000\u0000\u001d\u001e\u0005[\u0000\u0000\u001e"+ - "\u0006\u0001\u0000\u0000\u0000\u001f \u0005]\u0000\u0000 \b\u0001\u0000"+ - "\u0000\u0000!\"\u0005.\u0000\u0000\"\n\u0001\u0000\u0000\u0000#$\u0005"+ - ",\u0000\u0000$\f\u0001\u0000\u0000\u0000%&\u0005 \u0000\u0000&\u000e\u0001"+ - "\u0000\u0000\u0000\'(\u0005b\u0000\u0000()\u0005o\u0000\u0000)*\u0005"+ - "o\u0000\u0000*+\u0005l\u0000\u0000+,\u0005e\u0000\u0000,-\u0005a\u0000"+ - "\u0000-b\u0005n\u0000\u0000./\u0005b\u0000\u0000/0\u0005y\u0000\u0000"+ - "01\u0005t\u0000\u00001b\u0005e\u0000\u000023\u0005c\u0000\u000034\u0005"+ - "h\u0000\u000045\u0005a\u0000\u00005b\u0005r\u0000\u000067\u0005d\u0000"+ - "\u000078\u0005o\u0000\u000089\u0005u\u0000\u00009:\u0005b\u0000\u0000"+ - ":;\u0005l\u0000\u0000;b\u0005e\u0000\u0000<=\u0005f\u0000\u0000=>\u0005"+ - "l\u0000\u0000>?\u0005o\u0000\u0000?@\u0005a\u0000\u0000@b\u0005t\u0000"+ - "\u0000AB\u0005i\u0000\u0000BC\u0005n\u0000\u0000Cb\u0005t\u0000\u0000"+ - "DE\u0005l\u0000\u0000EF\u0005o\u0000\u0000FG\u0005n\u0000\u0000Gb\u0005"+ - "g\u0000\u0000HI\u0005s\u0000\u0000IJ\u0005h\u0000\u0000JK\u0005o\u0000"+ - "\u0000KL\u0005r\u0000\u0000Lb\u0005t\u0000\u0000MN\u0005S\u0000\u0000"+ - "NO\u0005t\u0000\u0000OP\u0005r\u0000\u0000PQ\u0005i\u0000\u0000QR\u0005"+ - "n\u0000\u0000Rb\u0005g\u0000\u0000ST\u0005O\u0000\u0000TU\u0005b\u0000"+ - "\u0000UV\u0005j\u0000\u0000VW\u0005e\u0000\u0000WX\u0005c\u0000\u0000"+ - "Xb\u0005t\u0000\u0000Y]\u0003\u0013\t\u0000Z[\u0003\t\u0004\u0000[\\\u0003"+ - "\u0013\t\u0000\\^\u0001\u0000\u0000\u0000]Z\u0001\u0000\u0000\u0000^_"+ - "\u0001\u0000\u0000\u0000_]\u0001\u0000\u0000\u0000_`\u0001\u0000\u0000"+ - "\u0000`b\u0001\u0000\u0000\u0000a\'\u0001\u0000\u0000\u0000a.\u0001\u0000"+ - "\u0000\u0000a2\u0001\u0000\u0000\u0000a6\u0001\u0000\u0000\u0000a<\u0001"+ - "\u0000\u0000\u0000aA\u0001\u0000\u0000\u0000aD\u0001\u0000\u0000\u0000"+ - "aH\u0001\u0000\u0000\u0000aM\u0001\u0000\u0000\u0000aS\u0001\u0000\u0000"+ - "\u0000aY\u0001\u0000\u0000\u0000b\u0010\u0001\u0000\u0000\u0000ce\u0007"+ - "\u0000\u0000\u0000dc\u0001\u0000\u0000\u0000ef\u0001\u0000\u0000\u0000"+ - "fd\u0001\u0000\u0000\u0000fg\u0001\u0000\u0000\u0000g\u0012\u0001\u0000"+ - "\u0000\u0000hl\u0003\u0015\n\u0000ik\u0003\u0017\u000b\u0000ji\u0001\u0000"+ - "\u0000\u0000kn\u0001\u0000\u0000\u0000lj\u0001\u0000\u0000\u0000lm\u0001"+ - "\u0000\u0000\u0000m\u0014\u0001\u0000\u0000\u0000nl\u0001\u0000\u0000"+ - "\u0000ov\u0007\u0001\u0000\u0000pq\b\u0002\u0000\u0000qv\u0004\n\u0000"+ - "\u0000rs\u0007\u0003\u0000\u0000st\u0007\u0004\u0000\u0000tv\u0004\n\u0001"+ - "\u0000uo\u0001\u0000\u0000\u0000up\u0001\u0000\u0000\u0000ur\u0001\u0000"+ - "\u0000\u0000v\u0016\u0001\u0000\u0000\u0000w~\u0007\u0005\u0000\u0000"+ - "xy\b\u0002\u0000\u0000y~\u0004\u000b\u0002\u0000z{\u0007\u0003\u0000\u0000"+ - "{|\u0007\u0004\u0000\u0000|~\u0004\u000b\u0003\u0000}w\u0001\u0000\u0000"+ - "\u0000}x\u0001\u0000\u0000\u0000}z\u0001\u0000\u0000\u0000~\u0018\u0001"+ - "\u0000\u0000\u0000\u0007\u0000_aflu}\u0000"; + "\u0007\u0007\u0002\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0001\u0000"+ + "\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0002\u0001\u0002\u0001\u0003"+ + "\u0001\u0003\u0001\u0004\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0004\u0005X\b\u0005\u000b\u0005"+ + "\f\u0005Y\u0003\u0005\\\b\u0005\u0001\u0006\u0004\u0006_\b\u0006\u000b"+ + "\u0006\f\u0006`\u0001\u0007\u0001\u0007\u0005\u0007e\b\u0007\n\u0007\f"+ + "\u0007h\t\u0007\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0003"+ + "\bp\b\b\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0003\tx\b\t\u0001"+ + "\n\u0001\n\u0001\n\u0001\n\u0000\u0000\u000b\u0001\u0001\u0003\u0002\u0005"+ + "\u0003\u0007\u0004\t\u0005\u000b\u0006\r\u0007\u000f\b\u0011\u0000\u0013"+ + "\u0000\u0015\t\u0001\u0000\u0007\u0001\u000009\u0004\u0000$$AZ__az\u0002"+ + "\u0000\u0000\u007f\u8000\ud800\u8000\udbff\u0001\u0000\u8000\ud800\u8000"+ + "\udbff\u0001\u0000\u8000\udc00\u8000\udfff\u0005\u0000$$09AZ__az\u0003"+ + "\u0000\t\n\r\r \u008b\u0000\u0001\u0001\u0000\u0000\u0000\u0000\u0003"+ + "\u0001\u0000\u0000\u0000\u0000\u0005\u0001\u0000\u0000\u0000\u0000\u0007"+ + "\u0001\u0000\u0000\u0000\u0000\t\u0001\u0000\u0000\u0000\u0000\u000b\u0001"+ + "\u0000\u0000\u0000\u0000\r\u0001\u0000\u0000\u0000\u0000\u000f\u0001\u0000"+ + "\u0000\u0000\u0000\u0015\u0001\u0000\u0000\u0000\u0001\u0017\u0001\u0000"+ + "\u0000\u0000\u0003\u0019\u0001\u0000\u0000\u0000\u0005\u001b\u0001\u0000"+ + "\u0000\u0000\u0007\u001d\u0001\u0000\u0000\u0000\t\u001f\u0001\u0000\u0000"+ + "\u0000\u000b[\u0001\u0000\u0000\u0000\r^\u0001\u0000\u0000\u0000\u000f"+ + "b\u0001\u0000\u0000\u0000\u0011o\u0001\u0000\u0000\u0000\u0013w\u0001"+ + "\u0000\u0000\u0000\u0015y\u0001\u0000\u0000\u0000\u0017\u0018\u0005(\u0000"+ + "\u0000\u0018\u0002\u0001\u0000\u0000\u0000\u0019\u001a\u0005)\u0000\u0000"+ + "\u001a\u0004\u0001\u0000\u0000\u0000\u001b\u001c\u0005.\u0000\u0000\u001c"+ + "\u0006\u0001\u0000\u0000\u0000\u001d\u001e\u0005:\u0000\u0000\u001e\b"+ + "\u0001\u0000\u0000\u0000\u001f \u0005,\u0000\u0000 \n\u0001\u0000\u0000"+ + "\u0000!\"\u0005b\u0000\u0000\"#\u0005o\u0000\u0000#$\u0005o\u0000\u0000"+ + "$%\u0005l\u0000\u0000%&\u0005e\u0000\u0000&\'\u0005a\u0000\u0000\'\\\u0005"+ + "n\u0000\u0000()\u0005b\u0000\u0000)*\u0005y\u0000\u0000*+\u0005t\u0000"+ + "\u0000+\\\u0005e\u0000\u0000,-\u0005c\u0000\u0000-.\u0005h\u0000\u0000"+ + "./\u0005a\u0000\u0000/\\\u0005r\u0000\u000001\u0005d\u0000\u000012\u0005"+ + "o\u0000\u000023\u0005u\u0000\u000034\u0005b\u0000\u000045\u0005l\u0000"+ + "\u00005\\\u0005e\u0000\u000067\u0005f\u0000\u000078\u0005l\u0000\u0000"+ + "89\u0005o\u0000\u00009:\u0005a\u0000\u0000:\\\u0005t\u0000\u0000;<\u0005"+ + "i\u0000\u0000<=\u0005n\u0000\u0000=\\\u0005t\u0000\u0000>?\u0005l\u0000"+ + "\u0000?@\u0005o\u0000\u0000@A\u0005n\u0000\u0000A\\\u0005g\u0000\u0000"+ + "BC\u0005s\u0000\u0000CD\u0005h\u0000\u0000DE\u0005o\u0000\u0000EF\u0005"+ + "r\u0000\u0000F\\\u0005t\u0000\u0000GH\u0005S\u0000\u0000HI\u0005t\u0000"+ + "\u0000IJ\u0005r\u0000\u0000JK\u0005i\u0000\u0000KL\u0005n\u0000\u0000"+ + "L\\\u0005g\u0000\u0000MN\u0005O\u0000\u0000NO\u0005b\u0000\u0000OP\u0005"+ + "j\u0000\u0000PQ\u0005e\u0000\u0000QR\u0005c\u0000\u0000R\\\u0005t\u0000"+ + "\u0000SW\u0003\u000f\u0007\u0000TU\u0003\u0005\u0002\u0000UV\u0003\u000f"+ + "\u0007\u0000VX\u0001\u0000\u0000\u0000WT\u0001\u0000\u0000\u0000XY\u0001"+ + "\u0000\u0000\u0000YW\u0001\u0000\u0000\u0000YZ\u0001\u0000\u0000\u0000"+ + "Z\\\u0001\u0000\u0000\u0000[!\u0001\u0000\u0000\u0000[(\u0001\u0000\u0000"+ + "\u0000[,\u0001\u0000\u0000\u0000[0\u0001\u0000\u0000\u0000[6\u0001\u0000"+ + "\u0000\u0000[;\u0001\u0000\u0000\u0000[>\u0001\u0000\u0000\u0000[B\u0001"+ + "\u0000\u0000\u0000[G\u0001\u0000\u0000\u0000[M\u0001\u0000\u0000\u0000"+ + "[S\u0001\u0000\u0000\u0000\\\f\u0001\u0000\u0000\u0000]_\u0007\u0000\u0000"+ + "\u0000^]\u0001\u0000\u0000\u0000_`\u0001\u0000\u0000\u0000`^\u0001\u0000"+ + "\u0000\u0000`a\u0001\u0000\u0000\u0000a\u000e\u0001\u0000\u0000\u0000"+ + "bf\u0003\u0011\b\u0000ce\u0003\u0013\t\u0000dc\u0001\u0000\u0000\u0000"+ + "eh\u0001\u0000\u0000\u0000fd\u0001\u0000\u0000\u0000fg\u0001\u0000\u0000"+ + "\u0000g\u0010\u0001\u0000\u0000\u0000hf\u0001\u0000\u0000\u0000ip\u0007"+ + "\u0001\u0000\u0000jk\b\u0002\u0000\u0000kp\u0004\b\u0000\u0000lm\u0007"+ + "\u0003\u0000\u0000mn\u0007\u0004\u0000\u0000np\u0004\b\u0001\u0000oi\u0001"+ + "\u0000\u0000\u0000oj\u0001\u0000\u0000\u0000ol\u0001\u0000\u0000\u0000"+ + "p\u0012\u0001\u0000\u0000\u0000qx\u0007\u0005\u0000\u0000rs\b\u0002\u0000"+ + "\u0000sx\u0004\t\u0002\u0000tu\u0007\u0003\u0000\u0000uv\u0007\u0004\u0000"+ + "\u0000vx\u0004\t\u0003\u0000wq\u0001\u0000\u0000\u0000wr\u0001\u0000\u0000"+ + "\u0000wt\u0001\u0000\u0000\u0000x\u0014\u0001\u0000\u0000\u0000yz\u0007"+ + "\u0006\u0000\u0000z{\u0001\u0000\u0000\u0000{|\u0006\n\u0000\u0000|\u0016"+ + "\u0001\u0000\u0000\u0000\u0007\u0000Y[`fow\u0001\u0006\u0000\u0000"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { @@ -233,4 +233,4 @@ private boolean JavaLetterOrDigit_sempred(RuleContext _localctx, int predIndex) _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); } } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.tokens b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.tokens index c24e72bc9b4..aad1837fbc6 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.tokens +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterLexer.tokens @@ -1,17 +1,14 @@ LPAREN=1 RPAREN=2 -LBRACK=3 -RBRACK=4 -DOT=5 -COMMA=6 -SPACE=7 -FullyQualifiedName=8 -Number=9 -Identifier=10 +DOT=3 +COLON=4 +COMMA=5 +FullyQualifiedName=6 +Number=7 +Identifier=8 +S=9 '('=1 ')'=2 -'['=3 -']'=4 -'.'=5 -','=6 -' '=7 +'.'=3 +':'=4 +','=5 diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.interp b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.interp index a930f78304e..9196ed7bcea 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.interp +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.interp @@ -2,11 +2,10 @@ token literal names: null '(' ')' -'[' -']' '.' +':' ',' -' ' +null null null null @@ -15,20 +14,22 @@ token symbolic names: null LPAREN RPAREN -LBRACK -RBRACK DOT +COLON COMMA -SPACE FullyQualifiedName Number Identifier +S rule names: matcherPattern +typedPattern +patternType matcherParameter +parameterName matcherName atn: -[4, 1, 10, 21, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 1, 0, 1, 0, 1, 0, 5, 0, 10, 8, 0, 10, 0, 12, 0, 13, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 0, 0, 3, 0, 2, 4, 0, 1, 1, 0, 8, 10, 18, 0, 6, 1, 0, 0, 0, 2, 16, 1, 0, 0, 0, 4, 18, 1, 0, 0, 0, 6, 7, 3, 4, 2, 0, 7, 11, 5, 1, 0, 0, 8, 10, 3, 2, 1, 0, 9, 8, 1, 0, 0, 0, 10, 13, 1, 0, 0, 0, 11, 9, 1, 0, 0, 0, 11, 12, 1, 0, 0, 0, 12, 14, 1, 0, 0, 0, 13, 11, 1, 0, 0, 0, 14, 15, 5, 2, 0, 0, 15, 1, 1, 0, 0, 0, 16, 17, 7, 0, 0, 0, 17, 3, 1, 0, 0, 0, 18, 19, 5, 10, 0, 0, 19, 5, 1, 0, 0, 0, 1, 11] \ No newline at end of file +[4, 1, 9, 45, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 1, 0, 1, 0, 3, 0, 15, 8, 0, 1, 1, 1, 1, 1, 1, 3, 1, 20, 8, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 29, 8, 2, 10, 2, 12, 2, 32, 9, 2, 1, 2, 3, 2, 35, 8, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 0, 0, 6, 0, 2, 4, 6, 8, 10, 0, 1, 1, 0, 6, 8, 42, 0, 14, 1, 0, 0, 0, 2, 19, 1, 0, 0, 0, 4, 23, 1, 0, 0, 0, 6, 38, 1, 0, 0, 0, 8, 40, 1, 0, 0, 0, 10, 42, 1, 0, 0, 0, 12, 15, 3, 2, 1, 0, 13, 15, 3, 8, 4, 0, 14, 12, 1, 0, 0, 0, 14, 13, 1, 0, 0, 0, 15, 1, 1, 0, 0, 0, 16, 17, 3, 8, 4, 0, 17, 18, 5, 4, 0, 0, 18, 20, 1, 0, 0, 0, 19, 16, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 21, 1, 0, 0, 0, 21, 22, 3, 4, 2, 0, 22, 3, 1, 0, 0, 0, 23, 24, 3, 10, 5, 0, 24, 34, 5, 1, 0, 0, 25, 26, 3, 6, 3, 0, 26, 27, 5, 5, 0, 0, 27, 29, 1, 0, 0, 0, 28, 25, 1, 0, 0, 0, 29, 32, 1, 0, 0, 0, 30, 28, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 33, 1, 0, 0, 0, 32, 30, 1, 0, 0, 0, 33, 35, 3, 6, 3, 0, 34, 30, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0, 35, 36, 1, 0, 0, 0, 36, 37, 5, 2, 0, 0, 37, 5, 1, 0, 0, 0, 38, 39, 7, 0, 0, 0, 39, 7, 1, 0, 0, 0, 40, 41, 5, 8, 0, 0, 41, 9, 1, 0, 0, 0, 42, 43, 5, 8, 0, 0, 43, 11, 1, 0, 0, 0, 4, 14, 19, 30, 34] \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.java index a5dd3ffa9bc..170cb98ddaa 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.java @@ -15,18 +15,14 @@ */ // Generated from java-escape by ANTLR 4.11.1 package org.openrewrite.java.internal.grammar; - -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.ATN; -import org.antlr.v4.runtime.atn.ATNDeserializer; -import org.antlr.v4.runtime.atn.ParserATNSimulator; -import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.tree.ParseTreeListener; -import org.antlr.v4.runtime.tree.ParseTreeVisitor; -import org.antlr.v4.runtime.tree.TerminalNode; - +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.*; +import org.antlr.v4.runtime.tree.*; import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) public class TemplateParameterParser extends Parser { @@ -36,27 +32,29 @@ public class TemplateParameterParser extends Parser { protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache(); public static final int - LPAREN=1, RPAREN=2, LBRACK=3, RBRACK=4, DOT=5, COMMA=6, SPACE=7, FullyQualifiedName=8, - Number=9, Identifier=10; + LPAREN=1, RPAREN=2, DOT=3, COLON=4, COMMA=5, FullyQualifiedName=6, Number=7, + Identifier=8, S=9; public static final int - RULE_matcherPattern = 0, RULE_matcherParameter = 1, RULE_matcherName = 2; + RULE_matcherPattern = 0, RULE_typedPattern = 1, RULE_patternType = 2, + RULE_matcherParameter = 3, RULE_parameterName = 4, RULE_matcherName = 5; private static String[] makeRuleNames() { return new String[] { - "matcherPattern", "matcherParameter", "matcherName" + "matcherPattern", "typedPattern", "patternType", "matcherParameter", + "parameterName", "matcherName" }; } public static final String[] ruleNames = makeRuleNames(); private static String[] makeLiteralNames() { return new String[] { - null, "'('", "')'", "'['", "']'", "'.'", "','", "' '" + null, "'('", "')'", "'.'", "':'", "','" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); private static String[] makeSymbolicNames() { return new String[] { - null, "LPAREN", "RPAREN", "LBRACK", "RBRACK", "DOT", "COMMA", "SPACE", - "FullyQualifiedName", "Number", "Identifier" + null, "LPAREN", "RPAREN", "DOT", "COLON", "COMMA", "FullyQualifiedName", + "Number", "Identifier", "S" }; } private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); @@ -112,6 +110,128 @@ public TemplateParameterParser(TokenStream input) { @SuppressWarnings("CheckReturnValue") public static class MatcherPatternContext extends ParserRuleContext { + public TypedPatternContext typedPattern() { + return getRuleContext(TypedPatternContext.class,0); + } + public ParameterNameContext parameterName() { + return getRuleContext(ParameterNameContext.class,0); + } + public MatcherPatternContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_matcherPattern; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TemplateParameterParserListener ) ((TemplateParameterParserListener)listener).enterMatcherPattern(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TemplateParameterParserListener ) ((TemplateParameterParserListener)listener).exitMatcherPattern(this); + } + @Override + public <T> T accept(ParseTreeVisitor<? extends T> visitor) { + if ( visitor instanceof TemplateParameterParserVisitor ) return ((TemplateParameterParserVisitor<? extends T>)visitor).visitMatcherPattern(this); + else return visitor.visitChildren(this); + } + } + + public final MatcherPatternContext matcherPattern() throws RecognitionException { + MatcherPatternContext _localctx = new MatcherPatternContext(_ctx, getState()); + enterRule(_localctx, 0, RULE_matcherPattern); + try { + setState(14); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,0,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(12); + typedPattern(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(13); + parameterName(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class TypedPatternContext extends ParserRuleContext { + public PatternTypeContext patternType() { + return getRuleContext(PatternTypeContext.class,0); + } + public ParameterNameContext parameterName() { + return getRuleContext(ParameterNameContext.class,0); + } + public TerminalNode COLON() { return getToken(TemplateParameterParser.COLON, 0); } + public TypedPatternContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_typedPattern; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TemplateParameterParserListener ) ((TemplateParameterParserListener)listener).enterTypedPattern(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TemplateParameterParserListener ) ((TemplateParameterParserListener)listener).exitTypedPattern(this); + } + @Override + public <T> T accept(ParseTreeVisitor<? extends T> visitor) { + if ( visitor instanceof TemplateParameterParserVisitor ) return ((TemplateParameterParserVisitor<? extends T>)visitor).visitTypedPattern(this); + else return visitor.visitChildren(this); + } + } + + public final TypedPatternContext typedPattern() throws RecognitionException { + TypedPatternContext _localctx = new TypedPatternContext(_ctx, getState()); + enterRule(_localctx, 2, RULE_typedPattern); + try { + enterOuterAlt(_localctx, 1); + { + setState(19); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,1,_ctx) ) { + case 1: + { + setState(16); + parameterName(); + setState(17); + match(COLON); + } + break; + } + setState(21); + patternType(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + @SuppressWarnings("CheckReturnValue") + public static class PatternTypeContext extends ParserRuleContext { public MatcherNameContext matcherName() { return getRuleContext(MatcherNameContext.class,0); } @@ -123,51 +243,70 @@ public List<MatcherParameterContext> matcherParameter() { public MatcherParameterContext matcherParameter(int i) { return getRuleContext(MatcherParameterContext.class,i); } - public MatcherPatternContext(ParserRuleContext parent, int invokingState) { + public List<TerminalNode> COMMA() { return getTokens(TemplateParameterParser.COMMA); } + public TerminalNode COMMA(int i) { + return getToken(TemplateParameterParser.COMMA, i); + } + public PatternTypeContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } - @Override public int getRuleIndex() { return RULE_matcherPattern; } + @Override public int getRuleIndex() { return RULE_patternType; } @Override public void enterRule(ParseTreeListener listener) { - if ( listener instanceof TemplateParameterParserListener ) ((TemplateParameterParserListener)listener).enterMatcherPattern(this); + if ( listener instanceof TemplateParameterParserListener ) ((TemplateParameterParserListener)listener).enterPatternType(this); } @Override public void exitRule(ParseTreeListener listener) { - if ( listener instanceof TemplateParameterParserListener ) ((TemplateParameterParserListener)listener).exitMatcherPattern(this); + if ( listener instanceof TemplateParameterParserListener ) ((TemplateParameterParserListener)listener).exitPatternType(this); } @Override public <T> T accept(ParseTreeVisitor<? extends T> visitor) { - if ( visitor instanceof TemplateParameterParserVisitor ) return ((TemplateParameterParserVisitor<? extends T>)visitor).visitMatcherPattern(this); + if ( visitor instanceof TemplateParameterParserVisitor ) return ((TemplateParameterParserVisitor<? extends T>)visitor).visitPatternType(this); else return visitor.visitChildren(this); } } - public final MatcherPatternContext matcherPattern() throws RecognitionException { - MatcherPatternContext _localctx = new MatcherPatternContext(_ctx, getState()); - enterRule(_localctx, 0, RULE_matcherPattern); + public final PatternTypeContext patternType() throws RecognitionException { + PatternTypeContext _localctx = new PatternTypeContext(_ctx, getState()); + enterRule(_localctx, 4, RULE_patternType); int _la; try { + int _alt; enterOuterAlt(_localctx, 1); { - setState(6); + setState(23); matcherName(); - setState(7); + setState(24); match(LPAREN); - setState(11); + setState(34); _errHandler.sync(this); _la = _input.LA(1); - while (((_la) & ~0x3f) == 0 && ((1L << _la) & 1792L) != 0) { - { + if (((_la) & ~0x3f) == 0 && ((1L << _la) & 448L) != 0) { { - setState(8); - matcherParameter(); + setState(30); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,2,_ctx); + while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { + if ( _alt==1 ) { + { + { + setState(25); + matcherParameter(); + setState(26); + match(COMMA); + } + } + } + setState(32); + _errHandler.sync(this); + _alt = getInterpreter().adaptivePredict(_input,2,_ctx); } + setState(33); + matcherParameter(); } - setState(13); - _errHandler.sync(this); - _la = _input.LA(1); } - setState(14); + + setState(36); match(RPAREN); } } @@ -208,14 +347,14 @@ public <T> T accept(ParseTreeVisitor<? extends T> visitor) { public final MatcherParameterContext matcherParameter() throws RecognitionException { MatcherParameterContext _localctx = new MatcherParameterContext(_ctx, getState()); - enterRule(_localctx, 2, RULE_matcherParameter); + enterRule(_localctx, 6, RULE_matcherParameter); int _la; try { enterOuterAlt(_localctx, 1); { - setState(16); + setState(38); _la = _input.LA(1); - if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 1792L) != 0) ) { + if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 448L) != 0) ) { _errHandler.recoverInline(this); } else { @@ -236,6 +375,49 @@ public final MatcherParameterContext matcherParameter() throws RecognitionExcept return _localctx; } + @SuppressWarnings("CheckReturnValue") + public static class ParameterNameContext extends ParserRuleContext { + public TerminalNode Identifier() { return getToken(TemplateParameterParser.Identifier, 0); } + public ParameterNameContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_parameterName; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof TemplateParameterParserListener ) ((TemplateParameterParserListener)listener).enterParameterName(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof TemplateParameterParserListener ) ((TemplateParameterParserListener)listener).exitParameterName(this); + } + @Override + public <T> T accept(ParseTreeVisitor<? extends T> visitor) { + if ( visitor instanceof TemplateParameterParserVisitor ) return ((TemplateParameterParserVisitor<? extends T>)visitor).visitParameterName(this); + else return visitor.visitChildren(this); + } + } + + public final ParameterNameContext parameterName() throws RecognitionException { + ParameterNameContext _localctx = new ParameterNameContext(_ctx, getState()); + enterRule(_localctx, 8, RULE_parameterName); + try { + enterOuterAlt(_localctx, 1); + { + setState(40); + match(Identifier); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + @SuppressWarnings("CheckReturnValue") public static class MatcherNameContext extends ParserRuleContext { public TerminalNode Identifier() { return getToken(TemplateParameterParser.Identifier, 0); } @@ -260,11 +442,11 @@ public <T> T accept(ParseTreeVisitor<? extends T> visitor) { public final MatcherNameContext matcherName() throws RecognitionException { MatcherNameContext _localctx = new MatcherNameContext(_ctx, getState()); - enterRule(_localctx, 4, RULE_matcherName); + enterRule(_localctx, 10, RULE_matcherName); try { enterOuterAlt(_localctx, 1); { - setState(18); + setState(42); match(Identifier); } } @@ -280,20 +462,34 @@ public final MatcherNameContext matcherName() throws RecognitionException { } public static final String _serializedATN = - "\u0004\u0001\n\u0015\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ - "\u0002\u0007\u0002\u0001\u0000\u0001\u0000\u0001\u0000\u0005\u0000\n\b"+ - "\u0000\n\u0000\f\u0000\r\t\u0000\u0001\u0000\u0001\u0000\u0001\u0001\u0001"+ - "\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0000\u0000\u0003\u0000\u0002"+ - "\u0004\u0000\u0001\u0001\u0000\b\n\u0012\u0000\u0006\u0001\u0000\u0000"+ - "\u0000\u0002\u0010\u0001\u0000\u0000\u0000\u0004\u0012\u0001\u0000\u0000"+ - "\u0000\u0006\u0007\u0003\u0004\u0002\u0000\u0007\u000b\u0005\u0001\u0000"+ - "\u0000\b\n\u0003\u0002\u0001\u0000\t\b\u0001\u0000\u0000\u0000\n\r\u0001"+ - "\u0000\u0000\u0000\u000b\t\u0001\u0000\u0000\u0000\u000b\f\u0001\u0000"+ - "\u0000\u0000\f\u000e\u0001\u0000\u0000\u0000\r\u000b\u0001\u0000\u0000"+ - "\u0000\u000e\u000f\u0005\u0002\u0000\u0000\u000f\u0001\u0001\u0000\u0000"+ - "\u0000\u0010\u0011\u0007\u0000\u0000\u0000\u0011\u0003\u0001\u0000\u0000"+ - "\u0000\u0012\u0013\u0005\n\u0000\u0000\u0013\u0005\u0001\u0000\u0000\u0000"+ - "\u0001\u000b"; + "\u0004\u0001\t-\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ + "\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004\u0002"+ + "\u0005\u0007\u0005\u0001\u0000\u0001\u0000\u0003\u0000\u000f\b\u0000\u0001"+ + "\u0001\u0001\u0001\u0001\u0001\u0003\u0001\u0014\b\u0001\u0001\u0001\u0001"+ + "\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0005"+ + "\u0002\u001d\b\u0002\n\u0002\f\u0002 \t\u0002\u0001\u0002\u0003\u0002"+ + "#\b\u0002\u0001\u0002\u0001\u0002\u0001\u0003\u0001\u0003\u0001\u0004"+ + "\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0005\u0000\u0000\u0006\u0000"+ + "\u0002\u0004\u0006\b\n\u0000\u0001\u0001\u0000\u0006\b*\u0000\u000e\u0001"+ + "\u0000\u0000\u0000\u0002\u0013\u0001\u0000\u0000\u0000\u0004\u0017\u0001"+ + "\u0000\u0000\u0000\u0006&\u0001\u0000\u0000\u0000\b(\u0001\u0000\u0000"+ + "\u0000\n*\u0001\u0000\u0000\u0000\f\u000f\u0003\u0002\u0001\u0000\r\u000f"+ + "\u0003\b\u0004\u0000\u000e\f\u0001\u0000\u0000\u0000\u000e\r\u0001\u0000"+ + "\u0000\u0000\u000f\u0001\u0001\u0000\u0000\u0000\u0010\u0011\u0003\b\u0004"+ + "\u0000\u0011\u0012\u0005\u0004\u0000\u0000\u0012\u0014\u0001\u0000\u0000"+ + "\u0000\u0013\u0010\u0001\u0000\u0000\u0000\u0013\u0014\u0001\u0000\u0000"+ + "\u0000\u0014\u0015\u0001\u0000\u0000\u0000\u0015\u0016\u0003\u0004\u0002"+ + "\u0000\u0016\u0003\u0001\u0000\u0000\u0000\u0017\u0018\u0003\n\u0005\u0000"+ + "\u0018\"\u0005\u0001\u0000\u0000\u0019\u001a\u0003\u0006\u0003\u0000\u001a"+ + "\u001b\u0005\u0005\u0000\u0000\u001b\u001d\u0001\u0000\u0000\u0000\u001c"+ + "\u0019\u0001\u0000\u0000\u0000\u001d \u0001\u0000\u0000\u0000\u001e\u001c"+ + "\u0001\u0000\u0000\u0000\u001e\u001f\u0001\u0000\u0000\u0000\u001f!\u0001"+ + "\u0000\u0000\u0000 \u001e\u0001\u0000\u0000\u0000!#\u0003\u0006\u0003"+ + "\u0000\"\u001e\u0001\u0000\u0000\u0000\"#\u0001\u0000\u0000\u0000#$\u0001"+ + "\u0000\u0000\u0000$%\u0005\u0002\u0000\u0000%\u0005\u0001\u0000\u0000"+ + "\u0000&\'\u0007\u0000\u0000\u0000\'\u0007\u0001\u0000\u0000\u0000()\u0005"+ + "\b\u0000\u0000)\t\u0001\u0000\u0000\u0000*+\u0005\b\u0000\u0000+\u000b"+ + "\u0001\u0000\u0000\u0000\u0004\u000e\u0013\u001e\""; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { @@ -302,4 +498,4 @@ public final MatcherNameContext matcherName() throws RecognitionException { _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); } } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.tokens b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.tokens index c24e72bc9b4..aad1837fbc6 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.tokens +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParser.tokens @@ -1,17 +1,14 @@ LPAREN=1 RPAREN=2 -LBRACK=3 -RBRACK=4 -DOT=5 -COMMA=6 -SPACE=7 -FullyQualifiedName=8 -Number=9 -Identifier=10 +DOT=3 +COLON=4 +COMMA=5 +FullyQualifiedName=6 +Number=7 +Identifier=8 +S=9 '('=1 ')'=2 -'['=3 -']'=4 -'.'=5 -','=6 -' '=7 +'.'=3 +':'=4 +','=5 diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserBaseListener.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserBaseListener.java index 2a8d6f843e6..7deb52093f2 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserBaseListener.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserBaseListener.java @@ -39,6 +39,30 @@ public class TemplateParameterParserBaseListener implements TemplateParameterPar * <p>The default implementation does nothing.</p> */ @Override public void exitMatcherPattern(TemplateParameterParser.MatcherPatternContext ctx) { } + /** + * {@inheritDoc} + * + * <p>The default implementation does nothing.</p> + */ + @Override public void enterTypedPattern(TemplateParameterParser.TypedPatternContext ctx) { } + /** + * {@inheritDoc} + * + * <p>The default implementation does nothing.</p> + */ + @Override public void exitTypedPattern(TemplateParameterParser.TypedPatternContext ctx) { } + /** + * {@inheritDoc} + * + * <p>The default implementation does nothing.</p> + */ + @Override public void enterPatternType(TemplateParameterParser.PatternTypeContext ctx) { } + /** + * {@inheritDoc} + * + * <p>The default implementation does nothing.</p> + */ + @Override public void exitPatternType(TemplateParameterParser.PatternTypeContext ctx) { } /** * {@inheritDoc} * @@ -51,6 +75,18 @@ public class TemplateParameterParserBaseListener implements TemplateParameterPar * <p>The default implementation does nothing.</p> */ @Override public void exitMatcherParameter(TemplateParameterParser.MatcherParameterContext ctx) { } + /** + * {@inheritDoc} + * + * <p>The default implementation does nothing.</p> + */ + @Override public void enterParameterName(TemplateParameterParser.ParameterNameContext ctx) { } + /** + * {@inheritDoc} + * + * <p>The default implementation does nothing.</p> + */ + @Override public void exitParameterName(TemplateParameterParser.ParameterNameContext ctx) { } /** * {@inheritDoc} * @@ -88,4 +124,4 @@ public class TemplateParameterParserBaseListener implements TemplateParameterPar * <p>The default implementation does nothing.</p> */ @Override public void visitErrorNode(ErrorNode node) { } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserBaseVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserBaseVisitor.java index 9c7bf807d9a..930f92cc2fa 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserBaseVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserBaseVisitor.java @@ -34,6 +34,20 @@ public class TemplateParameterParserBaseVisitor<T> extends AbstractParseTreeVisi * {@link #visitChildren} on {@code ctx}.</p> */ @Override public T visitMatcherPattern(TemplateParameterParser.MatcherPatternContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + * <p>The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.</p> + */ + @Override public T visitTypedPattern(TemplateParameterParser.TypedPatternContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + * <p>The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.</p> + */ + @Override public T visitPatternType(TemplateParameterParser.PatternTypeContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} * @@ -41,6 +55,13 @@ public class TemplateParameterParserBaseVisitor<T> extends AbstractParseTreeVisi * {@link #visitChildren} on {@code ctx}.</p> */ @Override public T visitMatcherParameter(TemplateParameterParser.MatcherParameterContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + * <p>The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.</p> + */ + @Override public T visitParameterName(TemplateParameterParser.ParameterNameContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} * @@ -48,4 +69,4 @@ public class TemplateParameterParserBaseVisitor<T> extends AbstractParseTreeVisi * {@link #visitChildren} on {@code ctx}.</p> */ @Override public T visitMatcherName(TemplateParameterParser.MatcherNameContext ctx) { return visitChildren(ctx); } -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserListener.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserListener.java index 7648fc217ee..4838ede63ab 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserListener.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserListener.java @@ -32,6 +32,26 @@ public interface TemplateParameterParserListener extends ParseTreeListener { * @param ctx the parse tree */ void exitMatcherPattern(TemplateParameterParser.MatcherPatternContext ctx); + /** + * Enter a parse tree produced by {@link TemplateParameterParser#typedPattern}. + * @param ctx the parse tree + */ + void enterTypedPattern(TemplateParameterParser.TypedPatternContext ctx); + /** + * Exit a parse tree produced by {@link TemplateParameterParser#typedPattern}. + * @param ctx the parse tree + */ + void exitTypedPattern(TemplateParameterParser.TypedPatternContext ctx); + /** + * Enter a parse tree produced by {@link TemplateParameterParser#patternType}. + * @param ctx the parse tree + */ + void enterPatternType(TemplateParameterParser.PatternTypeContext ctx); + /** + * Exit a parse tree produced by {@link TemplateParameterParser#patternType}. + * @param ctx the parse tree + */ + void exitPatternType(TemplateParameterParser.PatternTypeContext ctx); /** * Enter a parse tree produced by {@link TemplateParameterParser#matcherParameter}. * @param ctx the parse tree @@ -42,6 +62,16 @@ public interface TemplateParameterParserListener extends ParseTreeListener { * @param ctx the parse tree */ void exitMatcherParameter(TemplateParameterParser.MatcherParameterContext ctx); + /** + * Enter a parse tree produced by {@link TemplateParameterParser#parameterName}. + * @param ctx the parse tree + */ + void enterParameterName(TemplateParameterParser.ParameterNameContext ctx); + /** + * Exit a parse tree produced by {@link TemplateParameterParser#parameterName}. + * @param ctx the parse tree + */ + void exitParameterName(TemplateParameterParser.ParameterNameContext ctx); /** * Enter a parse tree produced by {@link TemplateParameterParser#matcherName}. * @param ctx the parse tree @@ -52,4 +82,4 @@ public interface TemplateParameterParserListener extends ParseTreeListener { * @param ctx the parse tree */ void exitMatcherName(TemplateParameterParser.MatcherNameContext ctx); -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserVisitor.java index e676ac3f93f..d74e66d76af 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/TemplateParameterParserVisitor.java @@ -31,16 +31,34 @@ public interface TemplateParameterParserVisitor<T> extends ParseTreeVisitor<T> { * @return the visitor result */ T visitMatcherPattern(TemplateParameterParser.MatcherPatternContext ctx); + /** + * Visit a parse tree produced by {@link TemplateParameterParser#typedPattern}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitTypedPattern(TemplateParameterParser.TypedPatternContext ctx); + /** + * Visit a parse tree produced by {@link TemplateParameterParser#patternType}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPatternType(TemplateParameterParser.PatternTypeContext ctx); /** * Visit a parse tree produced by {@link TemplateParameterParser#matcherParameter}. * @param ctx the parse tree * @return the visitor result */ T visitMatcherParameter(TemplateParameterParser.MatcherParameterContext ctx); + /** + * Visit a parse tree produced by {@link TemplateParameterParser#parameterName}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitParameterName(TemplateParameterParser.ParameterNameContext ctx); /** * Visit a parse tree produced by {@link TemplateParameterParser#matcherName}. * @param ctx the parse tree * @return the visitor result */ T visitMatcherName(TemplateParameterParser.MatcherNameContext ctx); -} +} \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java index 1c685dfa682..5ab5d1b2822 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java @@ -25,7 +25,9 @@ import org.openrewrite.java.internal.grammar.TemplateParameterParser; import org.openrewrite.java.tree.*; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; @@ -41,13 +43,14 @@ public class Substitutions { "#{", "}", null); public String substitute() { + AtomicInteger requiredParameters = new AtomicInteger(0); AtomicInteger index = new AtomicInteger(0); String substituted = code; while (true) { + Map<String, String> typedPatternByName = new HashMap<>(); String previous = substituted; substituted = propertyPlaceholderHelper.replacePlaceholders(substituted, key -> { int i = index.getAndIncrement(); - Object parameter = parameters[i]; String s; if (!key.isEmpty()) { @@ -58,73 +61,23 @@ public String substitute() { parser.addErrorListener(new ThrowingErrorListener()); TemplateParameterParser.MatcherPatternContext ctx = parser.matcherPattern(); - String matcherName = ctx.matcherName().Identifier().getText(); - List<TemplateParameterParser.MatcherParameterContext> params = ctx.matcherParameter(); - - if ("anyArray".equals(matcherName)) { - if (!(parameter instanceof TypedTree)) { - throw new IllegalArgumentException("anyArray can only be used on TypedTree parameters"); - } - - JavaType type = ((TypedTree) parameter).getType(); - JavaType.Array arrayType = TypeUtils.asArray(type); - if (arrayType == null) { - arrayType = TypeUtils.asArray(type); - if (arrayType == null) { - throw new IllegalArgumentException("anyArray can only be used on parameters containing JavaType.Array type attribution"); - } - } - - s = "(/*__p" + i + "__*/new "; - - StringBuilder extraDim = new StringBuilder(); - for (; arrayType.getElemType() instanceof JavaType.Array; arrayType = (JavaType.Array) arrayType.getElemType()) { - extraDim.append("[0]"); + TemplateParameterParser.TypedPatternContext typedPattern = ctx.typedPattern(); + if (typedPattern == null) { + String paramName = ctx.parameterName().Identifier().getText(); + s = typedPatternByName.get(paramName); + if (s == null) { + throw new IllegalArgumentException("The parameter " + paramName + " must be defined before it is referenced."); } - - if (arrayType.getElemType() instanceof JavaType.Primitive) { - s += ((JavaType.Primitive) arrayType.getElemType()).getKeyword(); - } else if (arrayType.getElemType() instanceof JavaType.FullyQualified) { - s += ((JavaType.FullyQualified) arrayType.getElemType()).getFullyQualifiedName().replace("$", "."); - } - - s += "[0]" + extraDim + ")"; - } else if ("any".equals(matcherName)) { - String fqn; - - if (params.size() == 1) { - if(params.get(0).Identifier() != null) { - fqn = params.get(0).Identifier().getText(); - } else { - fqn = params.get(0).FullyQualifiedName().getText(); - } - } else { - if (parameter instanceof J.NewClass && ((J.NewClass) parameter).getBody() != null - && ((J.NewClass) parameter).getClazz() != null) { - // for anonymous classes get the type from the supertype - fqn = getTypeName(((J.NewClass) parameter).getClazz().getType()); - } else if (!(parameter instanceof TypedTree)) { - // any should only be used on TypedTree parameters, but will give it best effort - fqn = "java.lang.Object"; - } else { - fqn = getTypeName(((TypedTree) parameter).getType()); - } - } - - fqn = fqn.replace("$", "."); - - JavaType.Primitive primitive = JavaType.Primitive.fromKeyword(fqn); - s = "__P__." + (primitive == null || primitive.equals(JavaType.Primitive.String) ? - "<" + fqn + ">/*__p" + i + "__*/p()" : - "/*__p" + i + "__*/" + fqn + "p()" - ); - - parameters[i] = ((J) parameter).withPrefix(Space.EMPTY); } else { - throw new IllegalArgumentException("Invalid template matcher '" + key + "'"); + s = substituteTypedPattern(key, i, typedPattern); + if (ctx.typedPattern().parameterName() != null) { + typedPatternByName.put(ctx.typedPattern().parameterName().Identifier().getText(), s); + } + requiredParameters.incrementAndGet(); } } else { - s = substituteUntyped(parameter, i); + s = substituteUntyped(parameters[i], i); + requiredParameters.incrementAndGet(); } return s; @@ -135,9 +88,84 @@ public String substitute() { } } + if (parameters.length != requiredParameters.get()) { + throw new IllegalArgumentException("This template requires " + requiredParameters.get() + " parameters."); + } + return substituted; } + private String substituteTypedPattern(String key, int index, TemplateParameterParser.TypedPatternContext typedPattern) { + Object parameter = parameters[index]; + String s; + String matcherName = typedPattern.patternType().matcherName().Identifier().getText(); + List<TemplateParameterParser.MatcherParameterContext> params = typedPattern.patternType().matcherParameter(); + + if ("anyArray".equals(matcherName)) { + if (!(parameter instanceof TypedTree)) { + throw new IllegalArgumentException("anyArray can only be used on TypedTree parameters"); + } + + JavaType type = ((TypedTree) parameter).getType(); + JavaType.Array arrayType = TypeUtils.asArray(type); + if (arrayType == null) { + arrayType = TypeUtils.asArray(type); + if (arrayType == null) { + throw new IllegalArgumentException("anyArray can only be used on parameters containing JavaType.Array type attribution"); + } + } + + s = "(/*__p" + index + "__*/new "; + + StringBuilder extraDim = new StringBuilder(); + for (; arrayType.getElemType() instanceof JavaType.Array; arrayType = (JavaType.Array) arrayType.getElemType()) { + extraDim.append("[0]"); + } + + if (arrayType.getElemType() instanceof JavaType.Primitive) { + s += ((JavaType.Primitive) arrayType.getElemType()).getKeyword(); + } else if (arrayType.getElemType() instanceof JavaType.FullyQualified) { + s += ((JavaType.FullyQualified) arrayType.getElemType()).getFullyQualifiedName().replace("$", "."); + } + + s += "[0]" + extraDim + ")"; + } else if ("any".equals(matcherName)) { + String fqn; + + if (params.size() == 1) { + if (params.get(0).Identifier() != null) { + fqn = params.get(0).Identifier().getText(); + } else { + fqn = params.get(0).FullyQualifiedName().getText(); + } + } else { + if (parameter instanceof J.NewClass && ((J.NewClass) parameter).getBody() != null + && ((J.NewClass) parameter).getClazz() != null) { + // for anonymous classes get the type from the supertype + fqn = getTypeName(((J.NewClass) parameter).getClazz().getType()); + } else if (!(parameter instanceof TypedTree)) { + // any should only be used on TypedTree parameters, but will give it best effort + fqn = "java.lang.Object"; + } else { + fqn = getTypeName(((TypedTree) parameter).getType()); + } + } + + fqn = fqn.replace("$", "."); + + JavaType.Primitive primitive = JavaType.Primitive.fromKeyword(fqn); + s = "__P__." + (primitive == null || primitive.equals(JavaType.Primitive.String) ? + "<" + fqn + ">/*__p" + index + "__*/p()" : + "/*__p" + index + "__*/" + fqn + "p()" + ); + + parameters[index] = ((J) parameter).withPrefix(Space.EMPTY); + } else { + throw new IllegalArgumentException("Invalid template matcher '" + key + "'"); + } + return s; + } + private String getTypeName(@Nullable JavaType type) { if (type instanceof JavaType.Parameterized) { StringJoiner joiner = new StringJoiner(",", "<", ">"); @@ -169,7 +197,7 @@ private String substituteUntyped(Object parameter, int index) { return ((J) parameter).printTrimmed(); } else { throw new IllegalArgumentException("Template parameter " + index + " cannot be used in an untyped template substitution. " + - "Instead of \"#{}\", indicate the template parameter's type with \"#{any(" + typeHintFor(parameter) + ")}\"."); + "Instead of \"#{}\", indicate the template parameter's type with \"#{any(" + typeHintFor(parameter) + ")}\"."); } } else if (parameter instanceof JRightPadded) { return substituteUntyped(((JRightPadded<?>) parameter).getElement(), index); @@ -180,7 +208,7 @@ private String substituteUntyped(Object parameter, int index) { } private static String typeHintFor(Object j) { - if(j instanceof TypedTree) { + if (j instanceof TypedTree) { return typeHintFor(((TypedTree) j).getType()); } return ""; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/template/Matcher.java b/rewrite-java/src/main/java/org/openrewrite/java/template/Matcher.java deleted file mode 100644 index c2c377fc8b8..00000000000 --- a/rewrite-java/src/main/java/org/openrewrite/java/template/Matcher.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.template; - -import org.openrewrite.Incubating; -import org.openrewrite.java.tree.J; - -@Incubating(since = "8.3.0") -@FunctionalInterface -public interface Matcher<T extends J> { - boolean matches(T t); -} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/template/Matches.java b/rewrite-java/src/main/java/org/openrewrite/java/template/Matches.java deleted file mode 100644 index 348333353a5..00000000000 --- a/rewrite-java/src/main/java/org/openrewrite/java/template/Matches.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.template; - -import org.openrewrite.Incubating; -import org.openrewrite.java.tree.Expression; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - -@Incubating(since = "8.3.0") -@Target(ElementType.PARAMETER) -public @interface Matches { - Class<? extends Matcher<? super Expression>> value(); -} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/template/NotMatches.java b/rewrite-java/src/main/java/org/openrewrite/java/template/NotMatches.java deleted file mode 100644 index 17935203b7c..00000000000 --- a/rewrite-java/src/main/java/org/openrewrite/java/template/NotMatches.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.template; - -import org.openrewrite.Incubating; -import org.openrewrite.java.tree.Expression; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - -@Incubating(since = "8.3.0") -@Target(ElementType.PARAMETER) -public @interface NotMatches { - Class<? extends Matcher<? super Expression>> value(); -} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/template/Primitive.java b/rewrite-java/src/main/java/org/openrewrite/java/template/Primitive.java deleted file mode 100644 index c8df4f20556..00000000000 --- a/rewrite-java/src/main/java/org/openrewrite/java/template/Primitive.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.template; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.SOURCE) -public @interface Primitive { -} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/template/package-info.java b/rewrite-java/src/main/java/org/openrewrite/java/template/package-info.java deleted file mode 100644 index 1d435666527..00000000000 --- a/rewrite-java/src/main/java/org/openrewrite/java/template/package-info.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2020 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * 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. - */ -@NonNullApi -package org.openrewrite.java.template; - -import org.openrewrite.internal.lang.NonNullApi; From 9493760bd57292f18157683429dc483c768cecfe Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Sat, 30 Sep 2023 15:42:27 +0200 Subject: [PATCH 280/447] Maven parent POM insight through data table (#3583) --- .../maven/search/ParentPomInsight.java | 88 ++++++++ .../maven/table/ParentPomsInUse.java | 64 ++++++ .../maven/search/ParentPomInsightTest.java | 205 ++++++++++++++++++ 3 files changed, 357 insertions(+) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/table/ParentPomsInUse.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java new file mode 100644 index 00000000000..ac90b23414b --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java @@ -0,0 +1,88 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.search; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.marker.JavaProject; +import org.openrewrite.marker.SearchResult; +import org.openrewrite.maven.MavenIsoVisitor; +import org.openrewrite.maven.table.ParentPomsInUse; +import org.openrewrite.maven.tree.ResolvedPom; +import org.openrewrite.xml.tree.Xml; + +import java.util.UUID; + +import static org.openrewrite.Tree.randomId; +import static org.openrewrite.internal.StringUtils.matchesGlob; + +@EqualsAndHashCode(callSuper = true) +@Value +public class ParentPomInsight extends Recipe { + transient ParentPomsInUse inUse = new ParentPomsInUse(this); + + @Option(displayName = "Group pattern", + description = "Group glob pattern used to match dependencies.", + example = "org.springframework.boot") + String groupIdPattern; + + @Option(displayName = "Artifact pattern", + description = "Artifact glob pattern used to match dependencies.", + example = "spring-boot-starter-*") + String artifactIdPattern; + + UUID searchId = randomId(); + + @Override + public String getDisplayName() { + return "Maven parent insight"; + } + + @Override + public String getDescription() { + return "Find Maven parents matching a `groupId` and `artifactId`."; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new MavenIsoVisitor<ExecutionContext>() { + @Override + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + if (isParentTag()) { + ResolvedPom resolvedPom = getResolutionResult().getPom(); + String groupId = resolvedPom.getValue(tag.getChildValue("groupId").orElse(null)); + String artifactId = resolvedPom.getValue(tag.getChildValue("artifactId").orElse(null)); + if (matchesGlob(groupId, groupIdPattern) && matchesGlob(artifactId, artifactIdPattern)) { + String version = resolvedPom.getValue(tag.getChildValue("version").orElse(null)); + String projectName = getCursor().firstEnclosingOrThrow(Xml.Document.class) + .getMarkers().findFirst(JavaProject.class) + .map(JavaProject::getProjectName).orElse(""); + String relativePath = tag.getChildValue("relativePath").orElse(null); + inUse.insertRow(ctx, new ParentPomsInUse.Row( + projectName, groupId, artifactId, version, resolvedPom.getDatedSnapshotVersion(), relativePath)); + return SearchResult.found(t); + } + } + return t; + } + }; + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/table/ParentPomsInUse.java b/rewrite-maven/src/main/java/org/openrewrite/maven/table/ParentPomsInUse.java new file mode 100644 index 00000000000..29e9bc450d8 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/table/ParentPomsInUse.java @@ -0,0 +1,64 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.table; + +import com.fasterxml.jackson.annotation.JsonIgnoreType; +import lombok.Value; +import org.openrewrite.Column; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; +import org.openrewrite.internal.lang.Nullable; + +@JsonIgnoreType +public class ParentPomsInUse extends DataTable<ParentPomsInUse.Row> { + + public ParentPomsInUse(Recipe recipe) { + super(recipe, Row.class, + ParentPomsInUse.class.getName(), + "Maven parent POMs in use", "Projects, GAVs and relativePaths for Maven parent POMs in use."); + } + + @Value + public static class Row { + @Column(displayName = "Project name", + description = "The name of the project that contains the parent.") + String projectName; + + @Column(displayName = "Group", + description = "The first part of a parent coordinate `org.springframework.boot`.") + String groupId; + + @Column(displayName = "Artifact", + description = "The second part of a parent coordinate `spring-boot-starter-*`.") + String artifactId; + + @Column(displayName = "Version", + description = "The resolved version.") + @Nullable + String version; + + @Column(displayName = "Dated snapshot version", + description = "The resolved dated snapshot version or `null` if this parent is not a snapshot.") + @Nullable + String datedSnapshotVersion; + + @Column(displayName = "Relative path", + description = "The relative path to the parent.") + @Nullable + String relativePath; + + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java new file mode 100644 index 00000000000..7f7f735224c --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java @@ -0,0 +1,205 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.maven.table.ParentPomsInUse; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.java.Assertions.mavenProject; +import static org.openrewrite.maven.Assertions.pomXml; + +class ParentPomInsightTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ParentPomInsight("org.springframework.boot", "spring-boot-starter-parent")); + } + + @DocumentExample + @Test + void findParent() { + rewriteRun( + spec -> spec.dataTable(ParentPomsInUse.Row.class, rows -> { + assertThat(rows).singleElement().satisfies(row -> { + assertThat(row.getProjectName()).isEqualTo("demo"); + assertThat(row.getGroupId()).isEqualTo("org.springframework.boot"); + assertThat(row.getArtifactId()).isEqualTo("spring-boot-starter-parent"); + assertThat(row.getVersion()).isEqualTo("3.1.4"); + assertThat(row.getDatedSnapshotVersion()).isNull(); + assertThat(row.getRelativePath()).isNull(); + }); + }), + mavenProject("demo", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>3.1.4</version> + <relativePath/> <!-- lookup parent from repository --> + </parent> + <groupId>com.example</groupId> + <artifactId>demo</artifactId> + <version>0.0.1-SNAPSHOT</version> + </project> + """, + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <!--~~>--><parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>3.1.4</version> + <relativePath/> <!-- lookup parent from repository --> + </parent> + <groupId>com.example</groupId> + <artifactId>demo</artifactId> + <version>0.0.1-SNAPSHOT</version> + </project> + """ + ) + ) + ); + } + + @Test + void multiModuleOnlyRoot() { + rewriteRun( + spec -> spec + .recipe(new ParentPomInsight("*", "*")) + .dataTableAsCsv(ParentPomsInUse.class.getName(), """ + projectName,groupId,artifactId,version,datedSnapshotVersion,relativePath + sample,org.springframework.boot,"spring-boot-starter-parent",2.5.0,, + module1,org.sample,sample,1.0.0,,../ + module2,org.sample,sample,1.0.0,,../ + """), + mavenProject("sample", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.5.0</version> + </parent> + + <modules> + <module>module1</module> + <module>module2</module> + </modules> + </project> + """, + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + + <!--~~>--><parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.5.0</version> + </parent> + + <modules> + <module>module1</module> + <module>module2</module> + </modules> + </project> + """ + ), + mavenProject("module1", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + <relativePath>../</relativePath> + </parent> + <artifactId>module1</artifactId> + </project> + """, + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <!--~~>--><parent> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + <relativePath>../</relativePath> + </parent> + <artifactId>module1</artifactId> + </project> + """, + spec -> spec.path("module1/pom.xml") + )), + mavenProject("module2", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + <relativePath>../</relativePath> + </parent> + <artifactId>module2</artifactId> + </project> + """, + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <!--~~>--><parent> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + <relativePath>../</relativePath> + </parent> + <artifactId>module2</artifactId> + </project> + """, + spec -> spec.path("module2/pom.xml") + ) + ) + ) + ); + } +} From 7ef3ffcb9d659eb57437ad47dce3ed474b719117 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Sat, 30 Sep 2023 07:07:52 -0700 Subject: [PATCH 281/447] Delete AbstractRefasterJavaVisitor --- .../template/AbstractRefasterJavaVisitor.java | 84 ------------------- 1 file changed, 84 deletions(-) delete mode 100644 rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java deleted file mode 100644 index d8197183168..00000000000 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AbstractRefasterJavaVisitor.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.java.internal.template; - -import org.openrewrite.Cursor; -import org.openrewrite.ExecutionContext; -import org.openrewrite.TreeVisitor; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.ShortenFullyQualifiedTypeReferences; -import org.openrewrite.java.cleanup.SimplifyBooleanExpressionVisitor; -import org.openrewrite.java.cleanup.UnnecessaryParenthesesVisitor; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaCoordinates; - -import java.util.Arrays; -import java.util.EnumSet; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -@SuppressWarnings("unused") -public abstract class AbstractRefasterJavaVisitor extends JavaVisitor<ExecutionContext> { - - protected final <T> Supplier<T> memoize(Supplier<T> delegate) { - AtomicReference<T> value = new AtomicReference<>(); - return () -> { - T val = value.get(); - if (val == null) { - val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); - } - return val; - }; - } - - protected final JavaTemplate.Matcher matcher(Supplier<JavaTemplate> template, Cursor cursor) { - return template.get().matcher(cursor); - } - - protected final J apply(Supplier<JavaTemplate> template, Cursor cursor, JavaCoordinates coordinates, Object... parameters) { - return template.get().apply(cursor, coordinates, parameters); - } - - @Deprecated - // to be removed as soon as annotation processor generates required options - protected J embed(J j, Cursor cursor, ExecutionContext ctx) { - return embed(j, cursor, ctx, EmbeddingOption.values()); - } - - @SuppressWarnings({"DataFlowIssue", "SameParameterValue"}) - protected J embed(J j, Cursor cursor, ExecutionContext ctx, EmbeddingOption... options) { - EnumSet<EmbeddingOption> optionsSet = options.length > 0 ? EnumSet.copyOf(Arrays.asList(options)) : - EnumSet.noneOf(EmbeddingOption.class); - - TreeVisitor<?, ExecutionContext> visitor; - if (optionsSet.contains(EmbeddingOption.REMOVE_PARENS) && !getAfterVisit().contains(visitor = new UnnecessaryParenthesesVisitor())) { - doAfterVisit(visitor); - } - if (optionsSet.contains(EmbeddingOption.SHORTEN_NAMES)) { - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(j)); - } - if (optionsSet.contains(EmbeddingOption.SIMPLIFY_BOOLEANS)) { - j = new SimplifyBooleanExpressionVisitor().visitNonNull(j, ctx, cursor.getParent()); - } - return j; - } - - protected enum EmbeddingOption { - SHORTEN_NAMES, SIMPLIFY_BOOLEANS, REMOVE_PARENS; - } -} From 0d805907606b06b28bbbaa14e4251abd9118d41e Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Sat, 30 Sep 2023 21:27:17 +0200 Subject: [PATCH 282/447] Drop datedSnapshotVersion and adopt parentArtifactId --- .../maven/search/ParentPomInsight.java | 6 +----- .../maven/table/ParentPomsInUse.java | 12 +++-------- .../maven/search/ParentPomInsightTest.java | 21 ++++++++++--------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java index ac90b23414b..d54b992279c 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java @@ -21,7 +21,6 @@ import org.openrewrite.Option; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.java.marker.JavaProject; import org.openrewrite.marker.SearchResult; import org.openrewrite.maven.MavenIsoVisitor; import org.openrewrite.maven.table.ParentPomsInUse; @@ -72,12 +71,9 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { String artifactId = resolvedPom.getValue(tag.getChildValue("artifactId").orElse(null)); if (matchesGlob(groupId, groupIdPattern) && matchesGlob(artifactId, artifactIdPattern)) { String version = resolvedPom.getValue(tag.getChildValue("version").orElse(null)); - String projectName = getCursor().firstEnclosingOrThrow(Xml.Document.class) - .getMarkers().findFirst(JavaProject.class) - .map(JavaProject::getProjectName).orElse(""); String relativePath = tag.getChildValue("relativePath").orElse(null); inUse.insertRow(ctx, new ParentPomsInUse.Row( - projectName, groupId, artifactId, version, resolvedPom.getDatedSnapshotVersion(), relativePath)); + resolvedPom.getArtifactId(), groupId, artifactId, version, relativePath)); return SearchResult.found(t); } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/table/ParentPomsInUse.java b/rewrite-maven/src/main/java/org/openrewrite/maven/table/ParentPomsInUse.java index 29e9bc450d8..46a66413756 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/table/ParentPomsInUse.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/table/ParentPomsInUse.java @@ -33,9 +33,9 @@ public ParentPomsInUse(Recipe recipe) { @Value public static class Row { - @Column(displayName = "Project name", - description = "The name of the project that contains the parent.") - String projectName; + @Column(displayName = "Project artifactId", + description = "The artifactId of the project that contains the parent.") + String projectArtifactId; @Column(displayName = "Group", description = "The first part of a parent coordinate `org.springframework.boot`.") @@ -50,15 +50,9 @@ public static class Row { @Nullable String version; - @Column(displayName = "Dated snapshot version", - description = "The resolved dated snapshot version or `null` if this parent is not a snapshot.") - @Nullable - String datedSnapshotVersion; - @Column(displayName = "Relative path", description = "The relative path to the parent.") @Nullable String relativePath; - } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java index 7f7f735224c..21a9bf29424 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java @@ -35,16 +35,17 @@ public void defaults(RecipeSpec spec) { @Test void findParent() { rewriteRun( - spec -> spec.dataTable(ParentPomsInUse.Row.class, rows -> { - assertThat(rows).singleElement().satisfies(row -> { - assertThat(row.getProjectName()).isEqualTo("demo"); + spec -> spec.dataTable(ParentPomsInUse.Row.class, rows -> assertThat(rows) + .singleElement() + .satisfies(row -> { + assertThat(row.getProjectArtifactId()).isEqualTo("demo"); assertThat(row.getGroupId()).isEqualTo("org.springframework.boot"); assertThat(row.getArtifactId()).isEqualTo("spring-boot-starter-parent"); assertThat(row.getVersion()).isEqualTo("3.1.4"); - assertThat(row.getDatedSnapshotVersion()).isNull(); assertThat(row.getRelativePath()).isNull(); - }); - }), + } + ) + ), mavenProject("demo", pomXml( """ @@ -90,10 +91,10 @@ void multiModuleOnlyRoot() { spec -> spec .recipe(new ParentPomInsight("*", "*")) .dataTableAsCsv(ParentPomsInUse.class.getName(), """ - projectName,groupId,artifactId,version,datedSnapshotVersion,relativePath - sample,org.springframework.boot,"spring-boot-starter-parent",2.5.0,, - module1,org.sample,sample,1.0.0,,../ - module2,org.sample,sample,1.0.0,,../ + projectArtifactId,groupId,artifactId,version,relativePath + sample,org.springframework.boot,"spring-boot-starter-parent",2.5.0, + module1,org.sample,sample,1.0.0,../ + module2,org.sample,sample,1.0.0,../ """), mavenProject("sample", pomXml( From b87bc10f3e967f186e2e7f70da70949240e980d7 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Sun, 1 Oct 2023 01:24:11 +0200 Subject: [PATCH 283/447] Move PrefixlessExpressionsTest to match implementation --- .../maven/{maven4 => cleanup}/PrefixlessExpressionsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename rewrite-maven/src/test/java/org/openrewrite/maven/{maven4 => cleanup}/PrefixlessExpressionsTest.java (99%) diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/maven4/PrefixlessExpressionsTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/PrefixlessExpressionsTest.java similarity index 99% rename from rewrite-maven/src/test/java/org/openrewrite/maven/maven4/PrefixlessExpressionsTest.java rename to rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/PrefixlessExpressionsTest.java index a77401b8c1a..b0e62903640 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/maven4/PrefixlessExpressionsTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/PrefixlessExpressionsTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openrewrite.maven.maven4; +package org.openrewrite.maven.cleanup; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; From 4a6a157e8cdbc0798449ae896da33c609213394a Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Sun, 1 Oct 2023 09:09:00 +0200 Subject: [PATCH 284/447] Add explicit Maven plugin groupId for clarity (#3591) * Add explicit Maven plugin groupId for clarity Fixes https://github.com/openrewrite/rewrite/issues/3529 * Rename to ExplicitPluginGroupId --- .../maven/cleanup/ExplicitPluginGroupId.java | 57 ++++++++ .../main/resources/META-INF/rewrite/maven.yml | 1 + .../cleanup/ExplicitPluginGroupIdTest.java | 123 ++++++++++++++++++ .../ManagedDependencyRequiresVersionTest.java | 1 - 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100755 rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupId.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupIdTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupId.java new file mode 100755 index 00000000000..e9bb432c38f --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupId.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.cleanup; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.maven.MavenTagInsertionComparator; +import org.openrewrite.maven.MavenVisitor; +import org.openrewrite.xml.AddToTagVisitor; +import org.openrewrite.xml.tree.Xml; + +@Value +@EqualsAndHashCode(callSuper = true) +public class ExplicitPluginGroupId extends Recipe { + @Override + public String getDisplayName() { + return "Add explicit `groupId` to Maven plugins"; + } + + @Override + public String getDescription() { + return "Add the default `<groupId>org.apache.maven.plugins</groupId>` to plugins for clarity."; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new MavenVisitor<ExecutionContext>() { + @Override + public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = (Xml.Tag) super.visitTag(tag, ctx); + if (isPluginTag() && !t.getChild("groupId").isPresent()) { + Xml.Tag groupIdTag = Xml.Tag.build("<groupId>org.apache.maven.plugins</groupId>"); + t = (Xml.Tag) new AddToTagVisitor<>(t, groupIdTag, new MavenTagInsertionComparator(t.getChildren())) + .visitNonNull(t, ctx, getCursor().getParentOrThrow()); + maybeUpdateModel(); + } + return t; + } + }; + } +} diff --git a/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml b/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml index 1a8a106553b..351033dad40 100644 --- a/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml +++ b/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml @@ -19,6 +19,7 @@ name: org.openrewrite.maven.BestPractices displayName: Apache Maven best practices description: Applies best practices to Maven POMs. recipeList: + - org.openrewrite.maven.cleanup.ExplicitPluginGroupId - org.openrewrite.maven.cleanup.PrefixlessExpressions --- diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupIdTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupIdTest.java new file mode 100644 index 00000000000..a62c4b90d4d --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ExplicitPluginGroupIdTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.cleanup; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.maven.Assertions.pomXml; + +class ExplicitPluginGroupIdTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ExplicitPluginGroupId()); + } + + private static final String BEFORE = """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.0</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + </project> + """; + + private static final String AFTER = """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.0</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + </project> + """; + + @Test + @DocumentExample + @Issue("https://github.com/openrewrite/rewrite/issues/3529") + void addGroupId() { + rewriteRun(pomXml(BEFORE, AFTER)); + } + + @Test + void noopIfPresent() { + rewriteRun(pomXml(AFTER)); + } + + @Test + void reportingPlugin() { + rewriteRun( + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <reporting> + <plugins> + <plugin> + <artifactId>maven-project-info-reports-plugin</artifactId> + <version>2.6</version> + </plugin> + </plugins> + </reporting> + </project> + """, + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <reporting> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-project-info-reports-plugin</artifactId> + <version>2.6</version> + </plugin> + </plugins> + </reporting> + </project> + """ + ) + ); + } +} \ No newline at end of file diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ManagedDependencyRequiresVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ManagedDependencyRequiresVersionTest.java index 6be2d846e00..ec129a9c9c5 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ManagedDependencyRequiresVersionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ManagedDependencyRequiresVersionTest.java @@ -22,7 +22,6 @@ import static org.openrewrite.maven.Assertions.pomXml; class ManagedDependencyRequiresVersionTest implements RewriteTest { - @Issue("https://github.com/openrewrite/rewrite/issues/1084") @Test void dependencyManagementDependencyRequiresVersion() { From 5e4742d25c52b2b07316bd1a59a0356e0d84f7ba Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sun, 1 Oct 2023 09:44:24 +0200 Subject: [PATCH 285/447] Fix bug in `Substitutions` related to named parameters --- .../java/JavaTemplateMatchTest.java | 29 +++++++++++++++++++ .../java/internal/template/Substitutions.java | 7 +++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateMatchTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateMatchTest.java index 35dff7db112..d3e8c82a19e 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateMatchTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateMatchTest.java @@ -85,6 +85,35 @@ class Test { )); } + @Test + void matchNamedParameterMultipleReferences() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new JavaVisitor<>() { + @Override + public J visitBinary(J.Binary binary, ExecutionContext ctx) { + return JavaTemplate.matches("#{i:any(int)} == 1 && #{i} == #{j:any(int)}", getCursor()) + ? SearchResult.found(binary) + : super.visitBinary(binary, ctx); + } + })), + java( + """ + class Test { + boolean foo(int i, int j) { + return i == 1 && i == j; + } + } + """, + """ + class Test { + boolean foo(int i, int j) { + return /*~~>*/i == 1 && i == j; + } + } + """ + )); + } + @SuppressWarnings({"ConstantValue", "ConstantConditions"}) @Test void extractParameterUsingMatcher() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java index 5ab5d1b2822..0db385f9eb5 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/Substitutions.java @@ -50,8 +50,6 @@ public String substitute() { Map<String, String> typedPatternByName = new HashMap<>(); String previous = substituted; substituted = propertyPlaceholderHelper.replacePlaceholders(substituted, key -> { - int i = index.getAndIncrement(); - String s; if (!key.isEmpty()) { TemplateParameterParser parser = new TemplateParameterParser(new CommonTokenStream(new TemplateParameterLexer( @@ -69,13 +67,16 @@ public String substitute() { throw new IllegalArgumentException("The parameter " + paramName + " must be defined before it is referenced."); } } else { + int i = index.getAndIncrement(); s = substituteTypedPattern(key, i, typedPattern); if (ctx.typedPattern().parameterName() != null) { - typedPatternByName.put(ctx.typedPattern().parameterName().Identifier().getText(), s); + String paramName = ctx.typedPattern().parameterName().Identifier().getText(); + typedPatternByName.put(paramName, s); } requiredParameters.incrementAndGet(); } } else { + int i = index.getAndIncrement(); s = substituteUntyped(parameters[i], i); requiredParameters.incrementAndGet(); } From 8e76a4328325255cc4b269d78b9a7be11f4f9ee9 Mon Sep 17 00:00:00 2001 From: Michael Keppler <bananeweizen@gmx.de> Date: Sun, 1 Oct 2023 10:56:33 +0200 Subject: [PATCH 286/447] Avoid NPE in SemanticallyEqual (#3586) Fixes #3585. --- .../java/org/openrewrite/java/search/SemanticallyEqual.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java b/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java index 2b513f0fe88..30adccf6651 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/SemanticallyEqual.java @@ -875,7 +875,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, J j) J.MethodInvocation compareTo = (J.MethodInvocation) j; if (!method.getSimpleName().equals(compareTo.getSimpleName()) || !TypeUtils.isOfType(method.getMethodType(), compareTo.getMethodType()) || - !(static_ == compareTo.getMethodType().hasFlags(Flag.Static) || + !(static_ == (compareTo.getMethodType() != null && compareTo.getMethodType().hasFlags(Flag.Static)) || !nullMissMatch(method.getSelect(), compareTo.getSelect())) || method.getArguments().size() != compareTo.getArguments().size() || nullListSizeMissMatch(method.getTypeParameters(), compareTo.getTypeParameters())) { From e900928537f48f25352bdcbbb59d16d780f1a7fd Mon Sep 17 00:00:00 2001 From: Michael Keppler <bananeweizen@gmx.de> Date: Sun, 1 Oct 2023 12:56:27 +0200 Subject: [PATCH 287/447] Avoid trailing blanks in SingleLineComment (#3589) Fixes #3588. --- .../java/format/SingleLineCommentsTest.java | 13 +++++++++++++ .../openrewrite/java/format/SingleLineComments.java | 5 +++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/SingleLineCommentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/SingleLineCommentsTest.java index 0d2e1209d78..06178656c13 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/SingleLineCommentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/SingleLineCommentsTest.java @@ -47,4 +47,17 @@ class Test { ) ); } + + @Test + void emptyCommentLineDoesNotGetTrailingBlank() { + rewriteRun( + java( + """ + // Copyright + // + // Some long license text + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/SingleLineComments.java b/rewrite-java/src/main/java/org/openrewrite/java/format/SingleLineComments.java index 0acf6205ecf..5ba900abc17 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/SingleLineComments.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/SingleLineComments.java @@ -42,8 +42,9 @@ public Space visitSpace(Space space, Space.Location loc, ExecutionContext ctx) { return space.withComments(ListUtils.map(space.getComments(), c -> { if (!c.isMultiline()) { TextComment tc = (TextComment) c; - if (!tc.getText().startsWith(" ")) { - return tc.withText(" " + tc.getText()); + String commentText = tc.getText(); + if (!commentText.isEmpty() && !commentText.startsWith(" ")) { + return tc.withText(" " + commentText); } } return c; From 164d356dbbe8bf0beece40239370a6074faab624 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 2 Oct 2023 06:49:25 -0700 Subject: [PATCH 288/447] Allow loading recipes from inner classes --- .../config/ClasspathScanningLoader.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java index 1884cd3d377..3a565577b4c 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java @@ -72,13 +72,13 @@ public ClasspathScanningLoader(Properties properties, String[] acceptPackages) { */ public ClasspathScanningLoader(Properties properties, ClassLoader classLoader) { scanClasses(new ClassGraph() - .ignoreParentClassLoaders() - .overrideClassLoaders(classLoader), classLoader); + .ignoreParentClassLoaders() + .overrideClassLoaders(classLoader), classLoader); scanYaml(new ClassGraph() - .ignoreParentClassLoaders() - .overrideClassLoaders(classLoader) - .acceptPaths("META-INF/rewrite"), + .ignoreParentClassLoaders() + .overrideClassLoaders(classLoader) + .acceptPaths("META-INF/rewrite"), properties, emptyList(), classLoader); @@ -163,10 +163,9 @@ private void scanClasses(ClassGraph classGraph, ClassLoader classLoader) { private void configureRecipes(ScanResult result, String className) { for (ClassInfo classInfo : result.getSubclasses(className)) { Class<?> recipeClass = classInfo.loadClass(); - if (recipeClass.getName().equals(DeclarativeRecipe.class.getName()) - || recipeClass.getEnclosingClass() != null + if (recipeClass.getName().equals(DeclarativeRecipe.class.getName()) || // `ScanningRecipe` is an example of an abstract `Recipe` subtype - || (recipeClass.getModifiers() & Modifier.ABSTRACT) != 0) { + (recipeClass.getModifiers() & Modifier.ABSTRACT) != 0) { continue; } Timer.Builder builder = Timer.builder("rewrite.scan.configure.recipe"); From e68262ba67214baebdb734b22ce9df66cc7cb6e1 Mon Sep 17 00:00:00 2001 From: Kun Li <122563761+kunli2@users.noreply.github.com> Date: Mon, 2 Oct 2023 16:51:13 -0700 Subject: [PATCH 289/447] Update json parser to support multiple-bytes-unicodes (#3596) * Update json parser to support multiple-bytes-unicodes --- .../json/internal/JsonParserVisitor.java | 51 ++++++++++++++++--- .../org/openrewrite/json/JsonParserTest.java | 14 +++++ 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java b/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java index 37f6c8717cf..033a07b3ced 100644 --- a/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java @@ -49,12 +49,49 @@ public class JsonParserVisitor extends JSON5BaseVisitor<Json> { private int cursor = 0; - public JsonParserVisitor(Path path, @Nullable FileAttributes fileAttributes, EncodingDetectingInputStream source) { + // Whether the source has multi bytes (> 2 bytes) unicode characters + private final boolean hasMultiBytesUnicode; + // Antlr index to source index mapping + private final int[] indexes; + + public JsonParserVisitor(Path path, + @Nullable FileAttributes fileAttributes, + EncodingDetectingInputStream sourceInput + ) { this.path = path; this.fileAttributes = fileAttributes; - this.source = source.readFully(); - this.charset = source.getCharset(); - this.charsetBomMarked = source.isCharsetBomMarked(); + this.source = sourceInput.readFully(); + this.charset = sourceInput.getCharset(); + this.charsetBomMarked = sourceInput.isCharsetBomMarked(); + + boolean hasMultiBytesUnicode = false; + int[] pos = new int[source.length() + 1]; + int cursor = 0; + int i = 1; + pos[0] = 0; + + while (cursor < source.length()) { + int newCursor = source.offsetByCodePoints(cursor, 1); + if (newCursor > cursor + 1) { + hasMultiBytesUnicode = true; + } + pos[i++] = newCursor; + cursor = newCursor; + } + + this.hasMultiBytesUnicode = hasMultiBytesUnicode; + this.indexes = hasMultiBytesUnicode ? pos : null; + } + + /** + * Characters index to source index mapping, valid only when `hasMultiBytesUnicode` is true. + * Antlr index is based on characters index and reader is based on source index. + * If there are any >2 bytes unicode characters in source code, it will make the index mismatch. + * @param index index from Antlr + * @return corrected cursor index + */ + private int getCursorIndex(int index) { + return hasMultiBytesUnicode ? indexes[index] : index; } @Override @@ -264,7 +301,7 @@ private Space prefix(@Nullable TerminalNode terminalNode) { } private Space prefix(Token token) { - int start = token.getStartIndex(); + int start = getCursorIndex(token.getStartIndex()); if (start < cursor) { return Space.EMPTY; } @@ -281,7 +318,7 @@ private <C extends ParserRuleContext, T> T convert(C ctx, BiFunction<C, Space, T T t = conversion.apply(ctx, prefix(ctx)); if (ctx.getStop() != null) { - cursor = ctx.getStop().getStopIndex() + (Character.isWhitespace(source.charAt(ctx.getStop().getStopIndex())) ? 0 : 1); + cursor = getCursorIndex(ctx.getStop().getStopIndex()) + (Character.isWhitespace(source.charAt(getCursorIndex(ctx.getStop().getStopIndex()))) ? 0 : 1); } return t; @@ -289,7 +326,7 @@ private <C extends ParserRuleContext, T> T convert(C ctx, BiFunction<C, Space, T private <T> T convert(TerminalNode node, BiFunction<TerminalNode, Space, T> conversion) { T t = conversion.apply(node, prefix(node)); - cursor = node.getSymbol().getStopIndex() + 1; + cursor = getCursorIndex(node.getSymbol().getStopIndex()) + 1; return t; } diff --git a/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java b/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java index 854a74974e7..5eb7c1703da 100644 --- a/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java +++ b/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java @@ -146,4 +146,18 @@ void empty() { json("") ); } + + @Issue("https://github.com/openrewrite/rewrite/issues/3582") + @Test + void multiBytesUnicode() { + rewriteRun( + json( + """ + { + "🤖" : "robot" + } + """ + ) + ); + } } From 0893a6fcb3bcf315148cb2567039e745ccdc3428 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Tue, 3 Oct 2023 02:56:46 -0700 Subject: [PATCH 290/447] Recipes must be public to be loaded by ClasspathScanningLoader --- .../java/org/openrewrite/config/ClasspathScanningLoader.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java index 3a565577b4c..5d314289b98 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/ClasspathScanningLoader.java @@ -164,6 +164,7 @@ private void configureRecipes(ScanResult result, String className) { for (ClassInfo classInfo : result.getSubclasses(className)) { Class<?> recipeClass = classInfo.loadClass(); if (recipeClass.getName().equals(DeclarativeRecipe.class.getName()) || + (recipeClass.getModifiers() & Modifier.PUBLIC) == 0 || // `ScanningRecipe` is an example of an abstract `Recipe` subtype (recipeClass.getModifiers() & Modifier.ABSTRACT) != 0) { continue; From 78eb336a44dcde35af98fa3dc779b5c11875c202 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Tue, 3 Oct 2023 03:06:47 -0700 Subject: [PATCH 291/447] Polish --- .../main/java/org/openrewrite/java/JavaParser.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index 158ef9e77a1..54a5d89354a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -89,10 +89,11 @@ static List<Path> dependenciesFromClasspath(String... artifactNames) { boolean lacking = true; for (URI cpEntry : runtimeClasspath) { String cpEntryString = cpEntry.toString(); - if (jarPattern.matcher(cpEntryString).find() - || (explodedPattern.matcher(cpEntryString).find() - && Paths.get(cpEntry).toFile().isDirectory())) { - artifacts.add(Paths.get(cpEntry)); + Path cpEntryPath = Paths.get(cpEntry); + if (jarPattern.matcher(cpEntryString).find() || + (explodedPattern.matcher(cpEntryString).find() && + cpEntryPath.toFile().isDirectory())) { + artifacts.add(cpEntryPath); lacking = false; // Do not break because jarPattern matches "foo-bar-1.0.jar" and "foo-1.0.jar" to "foo" } @@ -199,7 +200,7 @@ static List<Path> dependenciesFromResources(ExecutionContext ctx, String... arti //noinspection ConstantValue throw new IllegalArgumentException("Unable to find classpath resource dependencies beginning with: " + missingArtifactNames.stream().map(a -> "'" + a + "'").sorted().collect(joining(", ", "", ".\n")) + - "The caller is of type " + (caller == null ? "NO CALLER IDENTIFIED" : caller.getName()) + ".\n" + + "The caller is of type " + caller.getName() + ".\n" + "The resources resolvable from the caller's classpath are: " + resources.stream().map(Resource::getPath).sorted().collect(joining(", ")) ); From b0eddd06010bfc98056cc6636e87a2773504449b Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 3 Oct 2023 12:47:16 +0200 Subject: [PATCH 292/447] Add missing `visitArrayType()` to Javadoc parsers for Java 8 and 11 Issue: #3575 --- .../ReloadableJava11JavadocVisitor.java | 68 +++++++++++++----- .../java/ReloadableJava8JavadocVisitor.java | 72 ++++++++++++++----- 2 files changed, 105 insertions(+), 35 deletions(-) diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java index d2abe9cb439..ef5603b8b2c 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java @@ -16,7 +16,7 @@ package org.openrewrite.java.isolated; import com.sun.source.doctree.*; -import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.ArrayTypeTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.PrimitiveTypeTree; @@ -87,8 +87,6 @@ public ReloadableJava11JavadocVisitor(Context context, TreePath scope, Reloadabl } private void init() { - char[] sourceArr = source.toCharArray(); - StringBuilder firstPrefixBuilder = new StringBuilder(); StringBuilder javadocContent = new StringBuilder(); StringBuilder marginBuilder = null; @@ -97,12 +95,12 @@ private void init() { // skip past the opening '/**' int i = 3; - for (; i < sourceArr.length; i++) { - char c = sourceArr[i]; + for (; i < source.length(); i++) { + char c = source.charAt(i); if (inFirstPrefix) { // '*' characters are considered a part of the margin until a non '*' is parsed. if (Character.isWhitespace(c) || c == '*' && isPrefixAsterisk) { - if (isPrefixAsterisk && i + 1 <= sourceArr.length - 1 && sourceArr[i + 1] != '*') { + if (isPrefixAsterisk && i + 1 <= source.length() - 1 && source.charAt(i + 1) != '*') { isPrefixAsterisk = false; } firstPrefixBuilder.append(c); @@ -117,30 +115,31 @@ private void init() { } if (c == '\n') { + char prev = source.charAt(i - 1); if (inFirstPrefix) { firstPrefix = firstPrefixBuilder.toString(); inFirstPrefix = false; } else { // Handle consecutive new lines. - if ((sourceArr[i - 1] == '\n' || - sourceArr[i - 1] == '\r' && sourceArr[i - 2] == '\n')) { - String prevLineLine = sourceArr[i - 1] == '\n' ? "\n" : "\r\n"; + if ((prev == '\n' || + prev == '\r' && source.charAt(i - 2) == '\n')) { + String prevLineLine = prev == '\n' ? "\n" : "\r\n"; lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), prevLineLine, Markers.EMPTY)); } else if (marginBuilder != null) { // A new line with no '*' that only contains whitespace. - String newLine = sourceArr[i - 1] == '\r' ? "\r\n" : "\n"; + String newLine = prev == '\r' ? "\r\n" : "\n"; lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), newLine, Markers.EMPTY)); javadocContent.append(marginBuilder.substring(marginBuilder.indexOf("\n") + 1)); } javadocContent.append(c); } - String newLine = sourceArr[i - 1] == '\r' ? "\r\n" : "\n"; + String newLine = prev == '\r' ? "\r\n" : "\n"; marginBuilder = new StringBuilder(newLine); } else if (marginBuilder != null) { if (!Character.isWhitespace(c)) { if (c == '*') { // '*' characters are considered a part of the margin until a non '*' is parsed. marginBuilder.append(c); - if (i + 1 <= sourceArr.length - 1 && sourceArr[i + 1] != '*') { + if (i + 1 <= source.length() - 1 && source.charAt(i + 1) != '*') { lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), marginBuilder.toString(), Markers.EMPTY)); marginBuilder = null; @@ -151,7 +150,7 @@ private void init() { marginBuilder.toString(), Markers.EMPTY)); javadocContent.append(c); } else { - String newLine = marginBuilder.toString().startsWith("\r") ? "\r\n" : "\n"; + String newLine = marginBuilder.charAt(0) == '\r' ? "\r\n" : "\n"; lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), newLine, Markers.EMPTY)); String margin = marginBuilder.toString(); @@ -863,9 +862,9 @@ public List<Javadoc> visitText(String node) { node = node.stripLeading(); } - char[] textArr = node.toCharArray(); StringBuilder text = new StringBuilder(); - for (char c : textArr) { + for (int i = 0; i < node.length(); i++) { + char c = node.charAt(i); cursor++; if (c == '\n') { if (text.length() > 0) { @@ -1116,7 +1115,7 @@ public J visitMemberSelect(MemberSelectTree node, Space fmt) { } @Override - public J visitIdentifier(IdentifierTree node, Space fmt) { + public J visitIdentifier(com.sun.source.tree.IdentifierTree node, Space fmt) { String name = node.getName().toString(); cursor += name.length(); JavaType type = typeMapping.type(node); @@ -1131,6 +1130,43 @@ public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, typeMapping.primitive(primitiveType.typetag), null); } + @Override + public J visitArrayType(ArrayTypeTree node, Space fmt) { + com.sun.source.tree.Tree typeIdent = node.getType(); + int dimCount = 1; + + while (typeIdent instanceof ArrayTypeTree) { + dimCount++; + typeIdent = ((ArrayTypeTree) typeIdent).getType(); + } + + TypeTree elemType = (TypeTree) scan(typeIdent, fmt); + + List<JRightPadded<Space>> dimensions = emptyList(); + if (dimCount > 0) { + dimensions = new ArrayList<>(dimCount); + for (int n = 0; n < dimCount; n++) { + if (!source.substring(cursor).startsWith("...")) { + dimensions.add(padRight( + Space.build(sourceBeforeAsString("["), emptyList()), + Space.build(sourceBeforeAsString("]"), emptyList()))); + } + } + } + + return new J.ArrayType( + randomId(), + Space.EMPTY, + Markers.EMPTY, + elemType, + dimensions + ); + } + + private <T> JRightPadded<T> padRight(T tree, Space right) { + return new JRightPadded<>(tree, right, Markers.EMPTY); + } + @Override public J visitParameterizedType(ParameterizedTypeTree node, Space fmt) { NameTree id = (NameTree) javaVisitor.scan(node.getType(), Space.EMPTY); diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java index 536ce2151a7..4ff85ff6bc7 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java @@ -16,7 +16,7 @@ package org.openrewrite.java; import com.sun.source.doctree.*; -import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.ArrayTypeTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.PrimitiveTypeTree; @@ -87,8 +87,6 @@ public ReloadableJava8JavadocVisitor(Context context, TreePath scope, Reloadable } private void init() { - char[] sourceArr = source.toCharArray(); - StringBuilder firstPrefixBuilder = new StringBuilder(); StringBuilder javadocContent = new StringBuilder(); StringBuilder marginBuilder = null; @@ -97,12 +95,12 @@ private void init() { // skip past the opening '/**' int i = 3; - for (; i < sourceArr.length; i++) { - char c = sourceArr[i]; + for (; i < source.length(); i++) { + char c = source.charAt(i); if (inFirstPrefix) { // '*' characters are considered a part of the margin until a non '*' is parsed. if (Character.isWhitespace(c) || c == '*' && isPrefixAsterisk) { - if (isPrefixAsterisk && i + 1 <= sourceArr.length - 1 && sourceArr[i + 1] != '*') { + if (isPrefixAsterisk && i + 1 <= source.length() - 1 && source.charAt(i + 1) != '*') { isPrefixAsterisk = false; } firstPrefixBuilder.append(c); @@ -117,30 +115,31 @@ private void init() { } if (c == '\n') { + char prev = source.charAt(i - 1); if (inFirstPrefix) { firstPrefix = firstPrefixBuilder.toString(); inFirstPrefix = false; } else { // Handle consecutive new lines. - if ((sourceArr[i - 1] == '\n' || - sourceArr[i - 1] == '\r' && sourceArr[i -2] == '\n')) { - String prevLineLine = sourceArr[i - 1] == '\n' ? "\n" : "\r\n"; + if ((prev == '\n' || + prev == '\r' && source.charAt(i - 2) == '\n')) { + String prevLineLine = prev == '\n' ? "\n" : "\r\n"; lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), prevLineLine, Markers.EMPTY)); } else if (marginBuilder != null) { // A new line with no '*' that only contains whitespace. - String newLine = sourceArr[i - 1] == '\r' ? "\r\n" : "\n"; + String newLine = prev == '\r' ? "\r\n" : "\n"; lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), newLine, Markers.EMPTY)); javadocContent.append(marginBuilder.substring(marginBuilder.indexOf("\n") + 1)); } javadocContent.append(c); } - String newLine = sourceArr[i - 1] == '\r' ? "\r\n" : "\n"; + String newLine = prev == '\r' ? "\r\n" : "\n"; marginBuilder = new StringBuilder(newLine); } else if (marginBuilder != null) { if (!Character.isWhitespace(c)) { if (c == '*') { // '*' characters are considered a part of the margin until a non '*' is parsed. marginBuilder.append(c); - if (i + 1 <= sourceArr.length - 1 && sourceArr[i + 1] != '*') { + if (i + 1 <= source.length() - 1 && source.charAt(i + 1) != '*') { lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), marginBuilder.toString(), Markers.EMPTY)); marginBuilder = null; @@ -151,7 +150,7 @@ private void init() { marginBuilder.toString(), Markers.EMPTY)); javadocContent.append(c); } else { - String newLine = marginBuilder.toString().startsWith("\r") ? "\r\n" : "\n"; + String newLine = marginBuilder.charAt(0) == '\r' ? "\r\n" : "\n"; lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), newLine, Markers.EMPTY)); String margin = marginBuilder.toString(); @@ -788,18 +787,16 @@ public List<Javadoc> visitText(String node) { if (!node.isEmpty() && Character.isWhitespace(node.charAt(0)) && !Character.isWhitespace(source.charAt(cursor))) { - char[] charArray = node.toCharArray(); - int i = 0; - for (; i < charArray.length && Character.isWhitespace(charArray[i]); i++) { + for (; i < node.length() && Character.isWhitespace(node.charAt(i)); i++) { } node = node.substring(i); } - char[] textArr = node.toCharArray(); StringBuilder text = new StringBuilder(); - for (char c : textArr) { + for (int i = 0; i < node.length(); i++) { + char c = node.charAt(i); cursor++; if (c == '\n') { if (text.length() > 0) { @@ -1039,7 +1036,7 @@ public J visitMemberSelect(MemberSelectTree node, Space fmt) { } @Override - public J visitIdentifier(IdentifierTree node, Space fmt) { + public J visitIdentifier(com.sun.source.tree.IdentifierTree node, Space fmt) { String name = node.getName().toString(); cursor += name.length(); JavaType type = typeMapping.type(node); @@ -1054,6 +1051,43 @@ public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, typeMapping.primitive(primitiveType.typetag), null); } + @Override + public J visitArrayType(ArrayTypeTree node, Space fmt) { + com.sun.source.tree.Tree typeIdent = node.getType(); + int dimCount = 1; + + while (typeIdent instanceof ArrayTypeTree) { + dimCount++; + typeIdent = ((ArrayTypeTree) typeIdent).getType(); + } + + TypeTree elemType = (TypeTree) scan(typeIdent, fmt); + + List<JRightPadded<Space>> dimensions = emptyList(); + if (dimCount > 0) { + dimensions = new ArrayList<>(dimCount); + for (int n = 0; n < dimCount; n++) { + if (!source.substring(cursor).startsWith("...")) { + dimensions.add(padRight( + Space.build(sourceBeforeAsString("["), emptyList()), + Space.build(sourceBeforeAsString("]"), emptyList()))); + } + } + } + + return new J.ArrayType( + randomId(), + Space.EMPTY, + Markers.EMPTY, + elemType, + dimensions + ); + } + + private <T> JRightPadded<T> padRight(T tree, Space right) { + return new JRightPadded<>(tree, right, Markers.EMPTY); + } + @Override public J visitParameterizedType(ParameterizedTypeTree node, Space fmt) { NameTree id = (NameTree) javaVisitor.scan(node.getType(), Space.EMPTY); From 5675dd665ce68d2f61b3359213a3a729786fc95a Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Tue, 3 Oct 2023 15:03:44 +0200 Subject: [PATCH 293/447] Merge NormalizeMavenVariables into PrefixlessExpressions (#3600) Due to overlap, and the latter replacing more instances. --- .../maven/NormalizeMavenVariables.java | 66 ------------------- .../openrewrite/maven/RenamePropertyKey.java | 10 +-- .../main/resources/META-INF/rewrite/maven.yml | 12 ++++ .../maven/NormalizeMavenVariablesTest.java | 63 ------------------ 4 files changed, 14 insertions(+), 137 deletions(-) delete mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/NormalizeMavenVariables.java delete mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/NormalizeMavenVariablesTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/NormalizeMavenVariables.java b/rewrite-maven/src/main/java/org/openrewrite/maven/NormalizeMavenVariables.java deleted file mode 100644 index a0ecfb20a00..00000000000 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/NormalizeMavenVariables.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.maven; - -import org.openrewrite.ExecutionContext; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; -import org.openrewrite.xml.tree.Xml; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -public class NormalizeMavenVariables extends Recipe { - - @Override - public String getDisplayName() { - return "Normalize Maven variables"; - } - - @Override - public String getDescription() { - return "Variables are all referenced by the prefix `project.`. You may also see references with `pom.` as the " + - "prefix, or the prefix omitted entirely - these forms are now deprecated and should not be used."; - } - - @Override - public TreeVisitor<?, ExecutionContext> getVisitor() { - return new MavenIsoVisitor<ExecutionContext>() { - private final List<String> properties = Arrays.asList( - "basedir", - "groupId", - "artifactId", - "version", - "build.timestamp" - ); - - @Override - public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext executionContext) { - Xml.Tag t = super.visitTag(tag, executionContext); - Optional<String> value = t.getValue(); - if (value.isPresent()) { - String newValue = value - .filter(v -> properties.stream().anyMatch(prop -> v.equals("${" + prop + "}"))) - .map(v -> "${project." + v.substring(2)) - .orElse(value.get()); - return value.get().equals(newValue) ? t : t.withValue(newValue); - } - return t; - } - }; - } -} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/RenamePropertyKey.java b/rewrite-maven/src/main/java/org/openrewrite/maven/RenamePropertyKey.java index f924d94bdf2..c13065278a7 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/RenamePropertyKey.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/RenamePropertyKey.java @@ -50,13 +50,7 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - return Preconditions.check(new MavenVisitor<ExecutionContext>() { - @Override - public Xml visitDocument(Xml.Document document, ExecutionContext executionContext) { - // Scanning every tag's value is not an efficient applicable test, so just accept all maven files - return SearchResult.found(document); - } - }, new MavenIsoVisitor<ExecutionContext>() { + return new MavenIsoVisitor<ExecutionContext>() { final String oldKeyAsProperty = "${" + oldKey + "}"; final String newKeyAsProperty = "${" + newKey + "}"; @@ -76,6 +70,6 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { } return t; } - }); + }; } } diff --git a/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml b/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml index 351033dad40..3021cda4ef5 100644 --- a/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml +++ b/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml @@ -28,6 +28,18 @@ name: org.openrewrite.maven.cleanup.PrefixlessExpressions displayName: Drop prefixless expressions in POM description: MNG-7404 drops support for prefixless in POMs. This recipe will add the `project.` prefix where missing. recipeList: + - org.openrewrite.maven.RenamePropertyKey: + oldKey: basedir + newKey: project.basedir + - org.openrewrite.maven.RenamePropertyKey: + oldKey: build.timestamp + newKey: project.build.timestamp + - org.openrewrite.maven.RenamePropertyKey: + oldKey: groupId + newKey: project.groupId + - org.openrewrite.maven.RenamePropertyKey: + oldKey: artifactId + newKey: project.artifactId - org.openrewrite.maven.RenamePropertyKey: oldKey: version newKey: project.version diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/NormalizeMavenVariablesTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/NormalizeMavenVariablesTest.java deleted file mode 100644 index 4f4eb973965..00000000000 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/NormalizeMavenVariablesTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * <p> - * 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 - * <p> - * https://www.apache.org/licenses/LICENSE-2.0 - * <p> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.openrewrite.maven; - -import org.junit.jupiter.api.Test; -import org.openrewrite.DocumentExample; -import org.openrewrite.test.RecipeSpec; -import org.openrewrite.test.RewriteTest; - -import static org.openrewrite.maven.Assertions.pomXml; - -public class NormalizeMavenVariablesTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - spec.recipe(new NormalizeMavenVariables()); - } - - @DocumentExample - @Test - void prefixProject() { - rewriteRun( - //language=xml - pomXml( - """ - <project> - <modelVersion>4.0.0</modelVersion> - <groupId>com.mycompany.app</groupId> - <artifactId>my-app</artifactId> - <version>1</version> - <properties> - <name>${artifactId}</name> - </properties> - </project> - """, - """ - <project> - <modelVersion>4.0.0</modelVersion> - <groupId>com.mycompany.app</groupId> - <artifactId>my-app</artifactId> - <version>1</version> - <properties> - <name>${project.artifactId}</name> - </properties> - </project> - """ - ) - ); - } -} From 05f79813ebdab75a5c27cf821bb8d968a905c12e Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 3 Oct 2023 20:50:52 +0200 Subject: [PATCH 294/447] Remove `JsonParserVisitor#indexes` (#3597) The `JsonParserVisitor#indexes` array requires an additional 4 bytes of memory per code point in the source code (only in case the source has at least one surrogate). Rather than doing that, the parser can have two cursors which are kept in sync when advancing through the source code: One code point cursor and one code unit cursor. The former is to align with the indexes of the ANTLR tokens and the latter is to be able to read from the underlying source string. --- .../json/internal/JsonParserVisitor.java | 68 ++++++------------- .../org/openrewrite/json/JsonParserTest.java | 4 +- 2 files changed, 22 insertions(+), 50 deletions(-) diff --git a/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java b/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java index 033a07b3ced..a29ed2f7e9c 100644 --- a/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java +++ b/rewrite-json/src/main/java/org/openrewrite/json/internal/JsonParserVisitor.java @@ -48,50 +48,14 @@ public class JsonParserVisitor extends JSON5BaseVisitor<Json> { private final FileAttributes fileAttributes; private int cursor = 0; + private int codePointCursor = 0; - // Whether the source has multi bytes (> 2 bytes) unicode characters - private final boolean hasMultiBytesUnicode; - // Antlr index to source index mapping - private final int[] indexes; - - public JsonParserVisitor(Path path, - @Nullable FileAttributes fileAttributes, - EncodingDetectingInputStream sourceInput - ) { + public JsonParserVisitor(Path path, @Nullable FileAttributes fileAttributes, EncodingDetectingInputStream source) { this.path = path; this.fileAttributes = fileAttributes; - this.source = sourceInput.readFully(); - this.charset = sourceInput.getCharset(); - this.charsetBomMarked = sourceInput.isCharsetBomMarked(); - - boolean hasMultiBytesUnicode = false; - int[] pos = new int[source.length() + 1]; - int cursor = 0; - int i = 1; - pos[0] = 0; - - while (cursor < source.length()) { - int newCursor = source.offsetByCodePoints(cursor, 1); - if (newCursor > cursor + 1) { - hasMultiBytesUnicode = true; - } - pos[i++] = newCursor; - cursor = newCursor; - } - - this.hasMultiBytesUnicode = hasMultiBytesUnicode; - this.indexes = hasMultiBytesUnicode ? pos : null; - } - - /** - * Characters index to source index mapping, valid only when `hasMultiBytesUnicode` is true. - * Antlr index is based on characters index and reader is based on source index. - * If there are any >2 bytes unicode characters in source code, it will make the index mismatch. - * @param index index from Antlr - * @return corrected cursor index - */ - private int getCursorIndex(int index) { - return hasMultiBytesUnicode ? indexes[index] : index; + this.source = source.readFully(); + this.charset = source.getCharset(); + this.charsetBomMarked = source.isCharsetBomMarked(); } @Override @@ -301,15 +265,21 @@ private Space prefix(@Nullable TerminalNode terminalNode) { } private Space prefix(Token token) { - int start = getCursorIndex(token.getStartIndex()); - if (start < cursor) { + int start = token.getStartIndex(); + if (start < codePointCursor) { return Space.EMPTY; } - String prefix = source.substring(cursor, start); - cursor = start; + String prefix = source.substring(cursor, advanceCursor(start)); return Space.format(prefix); } + public int advanceCursor(int newCodePointIndex) { + for (; codePointCursor < newCodePointIndex; codePointCursor++) { + cursor = source.offsetByCodePoints(cursor, 1); + } + return cursor; + } + @Nullable private <C extends ParserRuleContext, T> T convert(C ctx, BiFunction<C, Space, T> conversion) { if (ctx == null) { @@ -318,7 +288,7 @@ private <C extends ParserRuleContext, T> T convert(C ctx, BiFunction<C, Space, T T t = conversion.apply(ctx, prefix(ctx)); if (ctx.getStop() != null) { - cursor = getCursorIndex(ctx.getStop().getStopIndex()) + (Character.isWhitespace(source.charAt(getCursorIndex(ctx.getStop().getStopIndex()))) ? 0 : 1); + advanceCursor(ctx.getStop().getStopIndex() + 1); } return t; @@ -326,12 +296,12 @@ private <C extends ParserRuleContext, T> T convert(C ctx, BiFunction<C, Space, T private <T> T convert(TerminalNode node, BiFunction<TerminalNode, Space, T> conversion) { T t = conversion.apply(node, prefix(node)); - cursor = getCursorIndex(node.getSymbol().getStopIndex()) + 1; + advanceCursor(node.getSymbol().getStopIndex() + 1); return t; } private void skip(TerminalNode node) { - cursor = node.getSymbol().getStopIndex() + 1; + advanceCursor(node.getSymbol().getStopIndex() + 1); } /** @@ -346,7 +316,7 @@ private Space sourceBefore(String untilDelim) { } String prefix = source.substring(cursor, delimIndex); - cursor += prefix.length() + untilDelim.length(); // advance past the delimiter + advanceCursor(codePointCursor + Character.codePointCount(prefix, 0, prefix.length()) + untilDelim.length()); return Space.format(prefix); } diff --git a/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java b/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java index 5eb7c1703da..0c355456338 100644 --- a/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java +++ b/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java @@ -154,7 +154,9 @@ void multiBytesUnicode() { json( """ { - "🤖" : "robot" + "🤖" : "robot", + "robot" : "🤖", + "நடித்த" : 3 /* 🇩🇪 */ } """ ) From 7b431f233e7316a7abbce0e6a940e553d6d99ac5 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 4 Oct 2023 14:16:23 +0200 Subject: [PATCH 295/447] Unroll `List.stream()` call for performance --- .../java/org/openrewrite/java/tree/TypeUtils.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 70d5f04097c..a827fcffb4e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -235,9 +235,16 @@ public static boolean isAssignableTo(String to, @Nullable JavaType from) { } } JavaType.FullyQualified classFrom = (JavaType.FullyQualified) from; - return fullyQualifiedNamesAreEqual(to, classFrom.getFullyQualifiedName()) || - isAssignableTo(to, classFrom.getSupertype()) || - classFrom.getInterfaces().stream().anyMatch(i -> isAssignableTo(to, i)); + if (fullyQualifiedNamesAreEqual(to, classFrom.getFullyQualifiedName()) || + isAssignableTo(to, classFrom.getSupertype())) { + return true; + } + for (JavaType.FullyQualified i : classFrom.getInterfaces()) { + if (isAssignableTo(to, i)) { + return true; + } + } + return false; } else if (from instanceof JavaType.GenericTypeVariable) { JavaType.GenericTypeVariable genericFrom = (JavaType.GenericTypeVariable) from; for (JavaType bound : genericFrom.getBounds()) { From f36792d368dd33c4cac0ebe16f6d1ed6bf122158 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 4 Oct 2023 15:06:07 +0200 Subject: [PATCH 296/447] Fix failing `EnvironmentTest` --- .../org/openrewrite/java/EnvironmentTest.java | 21 ---------- .../java/MixedConstructorRecipe.java | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 21 deletions(-) create mode 100644 rewrite-java-test/src/test/java/org/openrewrite/java/MixedConstructorRecipe.java diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/EnvironmentTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/EnvironmentTest.java index 4bd163ca5c2..dedc2349534 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/EnvironmentTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/EnvironmentTest.java @@ -15,10 +15,8 @@ */ package org.openrewrite.java; -import com.fasterxml.jackson.annotation.JsonCreator; import org.junit.jupiter.api.Test; import org.openrewrite.Issue; -import org.openrewrite.Recipe; import org.openrewrite.config.Environment; import org.openrewrite.java.style.IntelliJ; @@ -55,22 +53,3 @@ void listStyles() { } } -class MixedConstructorRecipe extends Recipe { - public MixedConstructorRecipe() { - this(true); - } - - @JsonCreator - public MixedConstructorRecipe(boolean opt) { - } - - @Override - public String getDisplayName() { - return "Mixed constructor"; - } - - @Override - public String getDescription() { - return "A recipe with more than one constructor, with one marked as the primary."; - } -} diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/MixedConstructorRecipe.java b/rewrite-java-test/src/test/java/org/openrewrite/java/MixedConstructorRecipe.java new file mode 100644 index 00000000000..0b8e462058b --- /dev/null +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/MixedConstructorRecipe.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java; + +import com.fasterxml.jackson.annotation.JsonCreator; +import org.openrewrite.Recipe; + +public class MixedConstructorRecipe extends Recipe { + public MixedConstructorRecipe() { + this(true); + } + + @JsonCreator + public MixedConstructorRecipe(boolean opt) { + } + + @Override + public String getDisplayName() { + return "Mixed constructor"; + } + + @Override + public String getDescription() { + return "A recipe with more than one constructor, with one marked as the primary."; + } +} From 1670c0144964c419f6ff42b72d5f278eac5a87db Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 4 Oct 2023 21:10:42 +0200 Subject: [PATCH 297/447] Fix printing of false Javadoc attributes This to ensure parse-to-print idempotency. --- .../openrewrite/java/tree/JavadocTest.java | 19 +++++++++++++++++++ .../org/openrewrite/java/JavadocPrinter.java | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java index 1e24d2b586e..15c510c2a38 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java @@ -1707,4 +1707,23 @@ public static void main(String[] args) { ) ); } + + @Test + void emptyAttributes() { + rewriteRun( + java( + """ + /** + * DEFINE TENANCY TenantB AS <TenantB OCID> + * ENDORSE GROUP <TenantA user group name> TO {OBJECTSTORAGE_NAMESPACE_READ} IN TENANCY TenantB + * + * DEFINE TENANCY TenantA AS <TenantA OCID> + * DEFINE GROUP TenantAGroup AS <TenantA user group OCID> + * ADMIT GROUP TenantAGroup OF TENANCY TenantA TO {OBJECTSTORAGE_NAMESPACE_READ} IN TENANCY + */ + class Test {} + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java index 242f4485648..c36750c55fb 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavadocPrinter.java @@ -35,7 +35,7 @@ public JavadocPrinter() { public Javadoc visitAttribute(Javadoc.Attribute attribute, PrintOutputCapture<P> p) { beforeSyntax(attribute, p); p.append(attribute.getName()); - if (attribute.getSpaceBeforeEqual() != null) { + if (attribute.getSpaceBeforeEqual() != null && !attribute.getSpaceBeforeEqual().isEmpty()) { visit(attribute.getSpaceBeforeEqual(), p); if (attribute.getValue() != null) { p.append('='); From 1a3565e63ed007f3f0a04cfc28749a3bba9af2db Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 5 Oct 2023 12:39:23 +0200 Subject: [PATCH 298/447] Allow for trailing tabs characters in Javadoc --- .../ReloadableJava11JavadocVisitor.java | 2 +- .../ReloadableJava17JavadocVisitor.java | 20 ++++++++++--------- .../java/ReloadableJava8JavadocVisitor.java | 2 +- .../openrewrite/java/tree/JavadocTest.java | 15 ++++++++++++++ 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java index ef5603b8b2c..c0b59decf81 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java @@ -336,7 +336,7 @@ public Tree visitDocComment(DocCommentTree node, List<Javadoc> body) { } // The javadoc ends with trailing whitespace. - if (cursor < source.length() && source.substring(cursor).contains(" ")) { + if (cursor < source.length()) { String trailingWhitespace = source.substring(cursor); if (trailingWhitespace.contains("\n")) { // 1 or more newlines. diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java index f238083d420..4db1407edb8 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java @@ -15,12 +15,14 @@ */ package org.openrewrite.java.isolated; +import com.sun.source.doctree.ErroneousTree; +import com.sun.source.doctree.LiteralTree; +import com.sun.source.doctree.ProvidesTree; +import com.sun.source.doctree.ReturnTree; +import com.sun.source.doctree.UsesTree; import com.sun.source.doctree.*; import com.sun.source.tree.IdentifierTree; -import com.sun.source.tree.ArrayTypeTree; -import com.sun.source.tree.MemberSelectTree; -import com.sun.source.tree.ParameterizedTypeTree; -import com.sun.source.tree.PrimitiveTypeTree; +import com.sun.source.tree.*; import com.sun.source.util.DocTreeScanner; import com.sun.source.util.TreePath; import com.sun.source.util.TreeScanner; @@ -337,7 +339,7 @@ public Tree visitDocComment(DocCommentTree node, List<Javadoc> body) { } // The javadoc ends with trailing whitespace. - if (cursor < source.length() && source.substring(cursor).contains(" ")) { + if (cursor < source.length()) { String trailingWhitespace = source.substring(cursor); if (trailingWhitespace.contains("\n")) { // 1 or more newlines. @@ -578,14 +580,14 @@ public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { if (ref.qualifierExpression != null) { try { attr.attribType(ref.qualifierExpression, symbol); - } catch(NullPointerException ignored) { + } catch (NullPointerException ignored) { // best effort, can result in: // java.lang.NullPointerException: Cannot read field "info" because "env" is null // at com.sun.tools.javac.comp.Attr.attribType(Attr.java:404) } } - if(ref.qualifierExpression != null) { + if (ref.qualifierExpression != null) { qualifier = (TypedTree) javaVisitor.scan(ref.qualifierExpression, Space.EMPTY); qualifierType = qualifier.getType(); if (ref.memberName != null) { @@ -677,7 +679,7 @@ public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { @Nullable private JavaType.Method methodReferenceType(DCTree.DCReference ref, @Nullable JavaType type) { - if (type instanceof JavaType.Class) { + if (type instanceof JavaType.Class) { JavaType.Class classType = (JavaType.Class) type; nextMethod: @@ -1077,7 +1079,7 @@ private List<Javadoc> convertMultiline(List<? extends DocTree> dts) { /** * A {@link J} may contain new lines in each {@link Space} and each new line will have a corresponding * {@link org.openrewrite.java.tree.Javadoc.LineBreak}. - * + * <p> * This method collects the linebreaks associated to new lines in a Space, and removes the applicable linebreaks * from the map. */ diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java index 4ff85ff6bc7..6c393d58a9d 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java @@ -349,7 +349,7 @@ public Tree visitDocComment(DocCommentTree node, List<Javadoc> body) { } // The javadoc ends with trailing whitespace. - if (cursor < source.length() && source.substring(cursor).contains(" ")) { + if (cursor < source.length()) { String trailingWhitespace = source.substring(cursor); if (trailingWhitespace.contains("\n")) { // 1 or more newlines. diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java index 15c510c2a38..a233cc6c989 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java @@ -1726,4 +1726,19 @@ class Test {} ) ); } + + @Test + void trailingTab() { + rewriteRun( + java( + """ + /** + * See <a href="">here</a>\t + */ + class Test { + } + """ + ) + ); + } } From f47cf48dbcdb09d9f92047c4ea3b540d17356dc7 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 5 Oct 2023 15:23:06 +0200 Subject: [PATCH 299/447] Fix type attribution of `ReplaceConstantWithAnotherConstant` --- ...eplaceConstantWithAnotherConstantTest.java | 43 ++++++-------- .../ReplaceConstantWithAnotherConstant.java | 57 ++++++++++++------- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java index 3360803b120..7385e3b9d6d 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ReplaceConstantWithAnotherConstantTest.java @@ -17,40 +17,31 @@ import org.junit.jupiter.api.Test; import org.openrewrite.Issue; -import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; public class ReplaceConstantWithAnotherConstantTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec - .parser(JavaParser.fromJavaVersion() - .logCompilationWarningsAndErrors(true) - .classpath("guava")); - } - @Test void replaceConstantInAnnotation() { rewriteRun( - spec -> spec.recipe(new ReplaceConstantWithAnotherConstant("com.google.common.base.Charsets.UTF_8", "com.constant.B.UTF_8")), + spec -> spec.recipe(new ReplaceConstantWithAnotherConstant("java.io.File.pathSeparator", "com.constant.B.PATH_SEPARATOR")), java( """ package com.constant; public class B { - public static final String UTF_8 = "UTF_8"; + public static final String PATH_SEPARATOR = ":"; } """ ), java( """ - import com.google.common.base.Charsets; - - @SuppressWarnings(Charsets.UTF_8) + import java.io.File; + + @SuppressWarnings(File.pathSeparator) class Test { - @SuppressWarnings(value = Charsets.UTF_8) + @SuppressWarnings(value = File.pathSeparator) void foo() { System.out.println("Annotation"); } @@ -58,10 +49,10 @@ void foo() { """, """ import com.constant.B; - - @SuppressWarnings(B.UTF_8) + + @SuppressWarnings(B.PATH_SEPARATOR) class Test { - @SuppressWarnings(value = B.UTF_8) + @SuppressWarnings(value = B.PATH_SEPARATOR) void foo() { System.out.println("Annotation"); } @@ -74,17 +65,18 @@ void foo() { @Test void replaceConstant() { rewriteRun( - spec -> spec.recipe(new ReplaceConstantWithAnotherConstant("com.google.common.base.Charsets.UTF_8", "java.io.File.separator")), + spec -> spec.recipe(new ReplaceConstantWithAnotherConstant("java.io.File.pathSeparator", "java.io.File.separator")), java( """ - import com.google.common.base.Charsets; + import java.io.File; - import static com.google.common.base.Charsets.UTF_8; + import static java.io.File.pathSeparator; class Test { - Object o = Charsets.UTF_8; + Object o = File.pathSeparator; void foo() { - System.out.println(UTF_8); + System.out.println(pathSeparator); + System.out.println(java.io.File.pathSeparator); } } """, @@ -97,6 +89,7 @@ class Test { Object o = File.separator; void foo() { System.out.println(separator); + System.out.println(File.separator); } } """ @@ -113,8 +106,8 @@ void removeTopLevelClassImport() { package foo; public class Bar { - public enum Baz { - QUX + public static class Baz { + public static final String QUX = "QUX"; } } """ diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ReplaceConstantWithAnotherConstant.java b/rewrite-java/src/main/java/org/openrewrite/java/ReplaceConstantWithAnotherConstant.java index 884aabdf079..52e0f7c3143 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ReplaceConstantWithAnotherConstant.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ReplaceConstantWithAnotherConstant.java @@ -57,60 +57,73 @@ private static class ReplaceConstantWithAnotherConstantVisitor extends JavaVisit private final String existingOwningType; private final String constantName; - private final String owningType; + private final JavaType.FullyQualified newOwningType; private final String newConstantName; public ReplaceConstantWithAnotherConstantVisitor(String existingFullyQualifiedConstantName, String fullyQualifiedConstantName) { this.existingOwningType = existingFullyQualifiedConstantName.substring(0, existingFullyQualifiedConstantName.lastIndexOf('.')); this.constantName = existingFullyQualifiedConstantName.substring(existingFullyQualifiedConstantName.lastIndexOf('.') + 1); - this.owningType = fullyQualifiedConstantName.substring(0, fullyQualifiedConstantName.lastIndexOf('.')); + this.newOwningType = JavaType.ShallowClass.build(fullyQualifiedConstantName.substring(0, fullyQualifiedConstantName.lastIndexOf('.'))); this.newConstantName = fullyQualifiedConstantName.substring(fullyQualifiedConstantName.lastIndexOf('.') + 1); } @Override - public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext executionContext) { + public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) { JavaType.Variable fieldType = fieldAccess.getName().getFieldType(); if (isConstant(fieldType)) { return replaceFieldAccess(fieldAccess, fieldType); } - return super.visitFieldAccess(fieldAccess, executionContext); + return super.visitFieldAccess(fieldAccess, ctx); } @Override - public J visitIdentifier(J.Identifier ident, ExecutionContext executionContext) { + public J visitIdentifier(J.Identifier ident, ExecutionContext ctx) { JavaType.Variable fieldType = ident.getFieldType(); if (isConstant(fieldType) && !isVariableDeclaration()) { return replaceFieldAccess(ident, fieldType); } - return super.visitIdentifier(ident, executionContext); + return super.visitIdentifier(ident, ctx); } - private J replaceFieldAccess(Expression fieldAccess, JavaType.Variable fieldType) { + private J replaceFieldAccess(Expression expression, JavaType.Variable fieldType) { JavaType owner = fieldType.getOwner(); while (owner instanceof JavaType.FullyQualified) { maybeRemoveImport(((JavaType.FullyQualified) owner).getFullyQualifiedName()); owner = ((JavaType.FullyQualified) owner).getOwningClass(); } - JavaTemplate.Builder templateBuilder; - if (fieldAccess instanceof J.Identifier) { - maybeAddImport(owningType, newConstantName, false); - templateBuilder = JavaTemplate.builder(newConstantName) - .staticImports(owningType + '.' + newConstantName); - } else { - maybeAddImport(owningType, false); - templateBuilder = JavaTemplate.builder(owningType.substring(owningType.lastIndexOf('.') + 1) + '.' + newConstantName) - .imports(owningType); + if (expression instanceof J.Identifier) { + maybeAddImport(newOwningType.getFullyQualifiedName(), newConstantName, false); + J.Identifier identifier = (J.Identifier) expression; + return identifier + .withSimpleName(newConstantName) + .withFieldType(fieldType.withOwner(newOwningType).withName(newConstantName)); + } else if (expression instanceof J.FieldAccess) { + maybeAddImport(newOwningType.getFullyQualifiedName(), false); + J.FieldAccess fieldAccess = (J.FieldAccess) expression; + Expression target = fieldAccess.getTarget(); + J.Identifier name = fieldAccess.getName(); + if (target instanceof J.Identifier) { + target = ((J.Identifier) target).withType(newOwningType).withSimpleName(newOwningType.getClassName()); + name = name + .withFieldType(fieldType.withOwner(newOwningType).withName(newConstantName)) + .withSimpleName(newConstantName); + } else { + target = (((J.FieldAccess) target).getName()).withType(newOwningType).withSimpleName(newOwningType.getClassName()); + name = name + .withFieldType(fieldType.withOwner(newOwningType).withName(newConstantName)) + .withSimpleName(newConstantName); + } + return fieldAccess + .withTarget(target) + .withName(name); } - - return templateBuilder.contextSensitive().build() - .apply(getCursor(), fieldAccess.getCoordinates().replace()) - .withPrefix(fieldAccess.getPrefix()); + return expression; } private boolean isConstant(@Nullable JavaType.Variable varType) { return varType != null && TypeUtils.isOfClassType(varType.getOwner(), existingOwningType) && - varType.getName().equals(constantName); + varType.getName().equals(constantName); } private boolean isVariableDeclaration() { @@ -129,7 +142,7 @@ private boolean isVariableDeclaration() { } return constantName.equals(((J.VariableDeclarations) maybeVariable.getValue()).getVariables().get(0).getSimpleName()) && - existingOwningType.equals(ownerFqn.getFullyQualifiedName()); + existingOwningType.equals(ownerFqn.getFullyQualifiedName()); } } } From 02df3870395b0267156dd30cba935307f8068131 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 5 Oct 2023 16:44:57 +0200 Subject: [PATCH 300/447] Fix parsing of unicode escape sequences in YAML (#3604) * Fix parsing of unicode escape sequences in YAML A YAML scalar value can contain unicode escape sequences like `\u0051`. Currently, this throws off the parser. * Fix `MappingTest#escapeSequences()` * Polish test --- .../org/openrewrite/json/JsonParserTest.java | 14 +++++ .../yaml/FormatPreservingReader.java | 3 +- .../java/org/openrewrite/yaml/YamlParser.java | 51 +++++++++++++------ .../yaml/internal/YamlPrinter.java | 18 +------ .../org/openrewrite/yaml/YamlParserTest.java | 13 +++++ .../openrewrite/yaml/tree/MappingTest.java | 10 ++-- 6 files changed, 67 insertions(+), 42 deletions(-) diff --git a/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java b/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java index 0c355456338..2829a33cf93 100644 --- a/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java +++ b/rewrite-json/src/test/java/org/openrewrite/json/JsonParserTest.java @@ -162,4 +162,18 @@ void multiBytesUnicode() { ) ); } + + @Test + void unicodeEscapes() { + rewriteRun( + json( + """ + { + "nul": "\\u0000", + "reverse-solidus": "\\u005c", + } + """ + ) + ); + } } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java index 14c6dfa7621..187c914d58b 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/FormatPreservingReader.java @@ -16,7 +16,6 @@ package org.openrewrite.yaml; import lombok.Getter; -import org.openrewrite.internal.lang.NonNull; import org.yaml.snakeyaml.events.Event; import java.io.IOException; @@ -115,7 +114,7 @@ public String readStringFromBuffer(int start, int end) { } @Override - public int read(@NonNull char[] cbuf, int off, int len) throws IOException { + public int read(char[] cbuf, int off, int len) throws IOException { int read = delegate.read(cbuf, off, len); if (read > 0) { buffer.ensureCapacity(buffer.size() + read); diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index 75a3ec1eec2..6245fcf1799 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -17,7 +17,10 @@ import lombok.Getter; import org.intellij.lang.annotations.Language; -import org.openrewrite.*; +import org.openrewrite.ExecutionContext; +import org.openrewrite.FileAttributes; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.SourceFile; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; @@ -36,7 +39,6 @@ import org.yaml.snakeyaml.scanner.ScannerImpl; import java.io.IOException; -import java.io.StringReader; import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.*; @@ -186,15 +188,40 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr newLine = ""; ScalarEvent scalar = (ScalarEvent) event; - String scalarValue = scalar.getValue(); - if (variableByUuid.containsKey(scalarValue)) { - scalarValue = variableByUuid.get(scalarValue); - } Yaml.Anchor anchor = null; + int valueStart; if (scalar.getAnchor() != null) { anchor = buildYamlAnchor(reader, lastEnd, fmt, scalar.getAnchor(), event.getEndMark().getIndex(), true); anchors.put(scalar.getAnchor(), anchor); + valueStart = lastEnd + fmt.length() + scalar.getAnchor().length() + 1 + anchor.getPostfix().length(); + } else { + valueStart = lastEnd + fmt.length(); + } + + String scalarValue; + switch (scalar.getScalarStyle()) { + case DOUBLE_QUOTED: + case SINGLE_QUOTED: + scalarValue = reader.readStringFromBuffer(valueStart + 1, event.getEndMark().getIndex() - 2); + break; + case PLAIN: + scalarValue = reader.readStringFromBuffer(valueStart, event.getEndMark().getIndex() - 1); + break; + case LITERAL: + scalarValue = reader.readStringFromBuffer(valueStart + 1, event.getEndMark().getIndex() - 1); + if (scalarValue.endsWith("\n")) { + newLine = "\n"; + scalarValue = scalarValue.substring(0, scalarValue.length() - 1); + } + break; + case FOLDED: + default: + scalarValue = reader.readStringFromBuffer(valueStart + 1, event.getEndMark().getIndex() - 1); + break; + } + if (variableByUuid.containsKey(scalarValue)) { + scalarValue = variableByUuid.get(scalarValue); } Yaml.Scalar.Style style; @@ -207,22 +234,13 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr break; case LITERAL: style = Yaml.Scalar.Style.LITERAL; - scalarValue = reader.readStringFromBuffer(event.getStartMark().getIndex() + 1, event.getEndMark().getIndex() - 1); - if (scalarValue.endsWith("\n")) { - newLine = "\n"; - scalarValue = scalarValue.substring(0, scalarValue.length() - 1); - } break; case FOLDED: style = Yaml.Scalar.Style.FOLDED; - scalarValue = reader.readStringFromBuffer(event.getStartMark().getIndex() + 1, event.getEndMark().getIndex() - 1); break; case PLAIN: default: style = Yaml.Scalar.Style.PLAIN; - if (!scalarValue.startsWith("@") && event.getStartMark().getIndex() >= reader.getBufferIndex()) { - scalarValue = reader.readStringFromBuffer(event.getStartMark().getIndex(), event.getEndMark().getIndex() - 1); - } break; } BlockBuilder builder = blockStack.isEmpty() ? null : blockStack.peek(); @@ -347,7 +365,8 @@ private Yaml.Anchor buildYamlAnchor(FormatPreservingReader reader, int lastEnd, lastEnd + eventPrefix.length() + anchorLength, eventEndIndex); StringBuilder postFix = new StringBuilder(); - for (char c : whitespaceAndScalar.toCharArray()) { + for (int i = 0; i < whitespaceAndScalar.length(); i++) { + char c = whitespaceAndScalar.charAt(i); if (c != ' ' && c != '\t') { break; } diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java index 9a0426a9a2b..8c56c1752cd 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java @@ -118,23 +118,7 @@ public Yaml visitScalar(Yaml.Scalar scalar, PrintOutputCapture<P> p) { switch (scalar.getStyle()) { case DOUBLE_QUOTED: p.append('"') - .append(scalar.getValue() - .replace("\\", "\\\\") - .replace("\0", "\\0") - .replace("\u0007", "\\a") - .replace("\b", "\\b") - .replace("\t", "\\t") - .replace("\n", "\\n") - .replace("\u000B", "\\v") - .replace("\f", "\\f") - .replace("\r", "\\r") - .replace("\u001B", "\\e") - .replace("\"", "\\\"") - .replace("\u0085", "\\N") - .replace("\u00A0", "\\_") - .replace("\u2028", "\\L") - .replace("\u2029", "\\P") - ) + .append(scalar.getValue()) .append('"'); break; case SINGLE_QUOTED: diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java index 5a42250bd0b..1daf691cbc0 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/YamlParserTest.java @@ -116,4 +116,17 @@ void newlinesCombinedWithUnniCode() { ) ); } + + @Test + void unicodeEscapes() { + rewriteRun( + yaml( + """ + root: + "nul": "\\u0000" + "reverse-solidus": "\\u005c" + """ + ) + ); + } } diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java index 03709c13f24..972e3fd2a36 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java @@ -400,34 +400,30 @@ void mappingAnchor() { " '\\n' ", " '\n' ", " \n ", - " \"\\.\" ", " \"\\0\" ", " \"\\0\" ", " \"\\a\" ", " \"\\a\" ", " \"\\b\" ", - " \"\b\" ", " \"\\t\" ", " \"\t\" ", " \"\\n\" ", " \"\n\" ", " \"\\v\" ", " \"\\f\" ", - " \"\f\" ", " \"\\r\" ", " \"\r\" ", " \"\\e\" ", " \"\\\\\" ", - " \"\\\" ", " \"\\\"\" ", - " \"\"\" ", " \"\\N\" ", " \"\\_\" ", " \"\\L\" ", " \"\\P\" ", }) - void escapeSequences() { + void escapeSequences(String str) { rewriteRun( - yaml("escaped-value: $string")); + yaml("escaped-value: " + str) + ); } } From a8b1aca9754790537790a3159a3dcbe4959cc670 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 5 Oct 2023 17:07:38 +0200 Subject: [PATCH 301/447] Improve performance of `UsesType` (#3605) `UsesType` is extensively used as a predicate by recipes and it is thus important to be efficient. Typically, `UsesType` is used in one of three ways: 1. To do an exact match on a type 2. To match all types in a given package 3. To match all types in a given package or subpackages For 1 the code was already optimized in the past (see https://github.com/openrewrite/rewrite/commit/071185a5b10a207bff46c69d16c18db695be0f0d). For 2 and 3 the code currently creates a regex pattern and uses that for the matching. Both the pattern creation and matching can be expensive. Thus, this PR optimizes these two cases by doing a simple prefix match. --- .../org/openrewrite/java/search/UsesType.java | 45 +++++++++++++++++-- .../org/openrewrite/java/tree/TypeUtils.java | 24 +++++++--- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java b/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java index 7503bed0831..610504ccc7b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/UsesType.java @@ -22,6 +22,7 @@ import org.openrewrite.java.tree.*; import org.openrewrite.marker.SearchResult; +import java.util.function.Predicate; import java.util.regex.Pattern; import static java.util.Objects.requireNonNull; @@ -31,7 +32,7 @@ public class UsesType<P> extends JavaIsoVisitor<P> { @Nullable private final String fullyQualifiedType; @Nullable - private final Pattern typePattern; + private final Predicate<JavaType> typePattern; @Nullable private final Boolean includeImplicit; @@ -39,7 +40,18 @@ public class UsesType<P> extends JavaIsoVisitor<P> { public UsesType(String fullyQualifiedType, @Nullable Boolean includeImplicit) { if (fullyQualifiedType.contains("*")) { this.fullyQualifiedType = null; - this.typePattern = Pattern.compile(StringUtils.aspectjNameToPattern(fullyQualifiedType)); + if (fullyQualifiedType.indexOf('*') == fullyQualifiedType.length() - 1) { + int dotdot = fullyQualifiedType.indexOf(".."); + if (dotdot == -1 && fullyQualifiedType.charAt(fullyQualifiedType.length() - 2) == '.') { + this.typePattern = packagePattern(fullyQualifiedType.substring(0, fullyQualifiedType.length() - 2)); + } else if (dotdot == fullyQualifiedType.length() - 3) { + this.typePattern = packagePrefixPattern(fullyQualifiedType.substring(0, dotdot)); + } else { + this.typePattern = genericPattern(Pattern.compile(StringUtils.aspectjNameToPattern(fullyQualifiedType))); + } + } else { + this.typePattern = genericPattern(Pattern.compile(StringUtils.aspectjNameToPattern(fullyQualifiedType))); + } } else { this.fullyQualifiedType = fullyQualifiedType; this.typePattern = null; @@ -98,10 +110,37 @@ private JavaSourceFile maybeMark(JavaSourceFile c, @Nullable JavaType type) { } if (typePattern != null && TypeUtils.isAssignableTo(typePattern, type) - || fullyQualifiedType != null && TypeUtils.isAssignableTo(fullyQualifiedType, type)) { + || fullyQualifiedType != null && TypeUtils.isAssignableTo(fullyQualifiedType, type)) { return SearchResult.found(c); } return c; } + + private static Predicate<JavaType> genericPattern(Pattern pattern) { + return type -> { + if (type instanceof JavaType.FullyQualified) { + return pattern.matcher(((JavaType.FullyQualified) type).getFullyQualifiedName()).matches(); + } else if (type instanceof JavaType.Primitive) { + return pattern.matcher(((JavaType.Primitive) type).getKeyword()).matches(); + } + return false; + }; + } + + private static Predicate<JavaType> packagePattern(String name) { + return type -> type instanceof JavaType.FullyQualified && ((JavaType.FullyQualified) type).getPackageName().equals(name); + } + + private static Predicate<JavaType> packagePrefixPattern(String prefix) { + String subPackagePrefix = prefix + "."; + return type -> { + if (type instanceof JavaType.FullyQualified) { + String packageName = ((JavaType.FullyQualified) type).getPackageName(); + return packageName.equals(prefix) || packageName.startsWith(subPackagePrefix); + } + return false; + }; + } + } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index a827fcffb4e..90f93cd3c7f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -309,16 +309,26 @@ public static boolean isAssignableTo(String to, @Nullable JavaType from) { } public static boolean isAssignableTo(Pattern to, @Nullable JavaType from) { + return isAssignableTo(type -> { + if (type instanceof JavaType.FullyQualified) { + return to.matcher(((JavaType.FullyQualified) type).getFullyQualifiedName()).matches(); + } else if (type instanceof JavaType.Primitive) { + return to.matcher(((JavaType.Primitive) type).getKeyword()).matches(); + } + return false; + }, from); + } + + public static boolean isAssignableTo(Predicate<JavaType> predicate, @Nullable JavaType from) { try { if (from instanceof JavaType.FullyQualified) { JavaType.FullyQualified classFrom = (JavaType.FullyQualified) from; - if (to.matcher(classFrom.getFullyQualifiedName()).matches() || - isAssignableTo(to, classFrom.getSupertype())) { + if (predicate.test(classFrom) || isAssignableTo(predicate, classFrom.getSupertype())) { return true; } for (JavaType.FullyQualified anInterface : classFrom.getInterfaces()) { - if (isAssignableTo(to, anInterface)) { + if (isAssignableTo(predicate, anInterface)) { return true; } } @@ -326,17 +336,17 @@ public static boolean isAssignableTo(Pattern to, @Nullable JavaType from) { } else if (from instanceof JavaType.GenericTypeVariable) { JavaType.GenericTypeVariable genericFrom = (JavaType.GenericTypeVariable) from; for (JavaType bound : genericFrom.getBounds()) { - if (isAssignableTo(to, bound)) { + if (isAssignableTo(predicate, bound)) { return true; } } } else if (from instanceof JavaType.Variable) { - return isAssignableTo(to, ((JavaType.Variable) from).getType()); + return isAssignableTo(predicate, ((JavaType.Variable) from).getType()); } else if (from instanceof JavaType.Method) { - return isAssignableTo(to, ((JavaType.Method) from).getReturnType()); + return isAssignableTo(predicate, ((JavaType.Method) from).getReturnType()); } else if (from instanceof JavaType.Primitive) { JavaType.Primitive primitive = (JavaType.Primitive) from; - return to.toString().equals(primitive.getKeyword()); + return predicate.test(primitive); } } catch (Exception e) { return false; From 39b1a1eeb6efb2615438b60b78814b50e78b3585 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 9 Oct 2023 13:51:57 -0400 Subject: [PATCH 302/447] MavenArtifactDownloader download from local repositories --- .../utilities/MavenArtifactDownloader.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java index 9e37909c368..646c21213c1 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java @@ -31,6 +31,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.SocketTimeoutException; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -93,21 +94,23 @@ public Path downloadArtifact(ResolvedDependency dependency) { try { String uri = requireNonNull(dependency.getRepository(), String.format("Repository for dependency '%s' was null.", dependency)).getUri() + "/" + - dependency.getGroupId().replace('.', '/') + '/' + - dependency.getArtifactId() + '/' + - dependency.getVersion() + '/' + - dependency.getArtifactId() + '-' + - (dependency.getDatedSnapshotVersion() == null ? dependency.getVersion() : dependency.getDatedSnapshotVersion()) + - ".jar"; + dependency.getGroupId().replace('.', '/') + '/' + + dependency.getArtifactId() + '/' + + dependency.getVersion() + '/' + + dependency.getArtifactId() + '-' + + (dependency.getDatedSnapshotVersion() == null ? dependency.getVersion() : dependency.getDatedSnapshotVersion()) + + ".jar"; InputStream bodyStream; if (uri.startsWith("~")) { bodyStream = Files.newInputStream(Paths.get(System.getProperty("user.home") + uri.substring(1))); + } else if ("file".equals(URI.create(uri).getScheme())) { + bodyStream = Files.newInputStream(Paths.get(URI.create(uri))); } else { HttpSender.Request.Builder request = applyAuthentication(dependency.getRepository(), httpSender.get(uri)); - try(HttpSender.Response response = sendRequest.apply(request.build()); - InputStream body = response.getBody()) { + try (HttpSender.Response response = sendRequest.apply(request.build()); + InputStream body = response.getBody()) { if (!response.isSuccessful() || body == null) { onError.accept(new MavenDownloadingException(String.format("Unable to download dependency %s:%s:%s. Response was %d", dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), response.getCode()), null, @@ -119,10 +122,8 @@ public Path downloadArtifact(ResolvedDependency dependency) { throw new MavenDownloadingException("Unable to download dependency", t, dependency.getRequested().getGav()); } - } return bodyStream; - } catch (Throwable t) { onError.accept(t); } From bdccec625a2100070810d0e3c900714e3876e21a Mon Sep 17 00:00:00 2001 From: Tracey Yoshima <tracey.yoshima@gmail.com> Date: Tue, 10 Oct 2023 01:43:46 -0600 Subject: [PATCH 303/447] Added recipe options as JSON to FindRecipes. (#3610) * Added recipe options as JSON to FindRecipes. * Use Jackson to construct JSON to deal with escaping --------- Co-authored-by: Knut Wannheden <knut@moderne.io> --- .../table/RewriteRecipeSource.java | 3 + .../java/recipes/FindRecipesTest.java | 29 +++++++ .../openrewrite/java/recipes/FindRecipes.java | 77 ++++++++++++++++++- 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/table/RewriteRecipeSource.java b/rewrite-core/src/main/java/org/openrewrite/table/RewriteRecipeSource.java index 39061cbe572..201881218b9 100644 --- a/rewrite-core/src/main/java/org/openrewrite/table/RewriteRecipeSource.java +++ b/rewrite-core/src/main/java/org/openrewrite/table/RewriteRecipeSource.java @@ -46,6 +46,9 @@ public static class Row { @Column(displayName = "Recipe source code", description = "The full source code of the recipe.") String sourceCode; + + @Column(displayName = "Recipe options", description = "JSON format of recipe options.") + String options; } public enum RecipeType { diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java index 3e19e573262..927a2744bde 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java @@ -41,14 +41,29 @@ void findRecipes() { RewriteRecipeSource.Row row = rows.get(0); assertThat(row.getDisplayName()).isEqualTo("My recipe"); assertThat(row.getDescription()).isEqualTo("This is my recipe."); + assertThat(row.getOptions()).isEqualTo("[{\"name\":\"methodPattern\",\"displayName\":\"Method pattern\",\"description\":\"A method pattern that is used to find matching method declarations/invocations.\",\"example\":\"org.mockito.Matchers anyVararg()\"},{\"name\":\"newAccessLevel\",\"displayName\":\"New access level\",\"description\":\"New method access level to apply to the method, like \\\"public\\\".\",\"example\":\"public\",\"valid\":[\"private\",\"protected\",\"package\",\"public\"],\"required\":false}]"); }), java( """ + import org.openrewrite.Option; import org.openrewrite.internal.lang.NonNullApi; import org.openrewrite.Recipe; + import org.openrewrite.internal.lang.Nullable; @NonNullApi class MyRecipe extends Recipe { + @Option(displayName = "Method pattern", + description = "A method pattern that is used to find matching method declarations/invocations.", + example = "org.mockito.Matchers anyVararg()") + String methodPattern; + + @Option(displayName = "New access level", + description = "New method access level to apply to the method, like \\"public\\".", + example = "public", + valid = {"private", "protected", "package", "public"}, + required = false) + String newAccessLevel; + @Override public String getDisplayName() { return "My recipe"; @@ -61,11 +76,25 @@ public String getDescription() { } """, """ + import org.openrewrite.Option; import org.openrewrite.internal.lang.NonNullApi; import org.openrewrite.Recipe; + import org.openrewrite.internal.lang.Nullable; @NonNullApi class /*~~>*/MyRecipe extends Recipe { + @Option(displayName = "Method pattern", + description = "A method pattern that is used to find matching method declarations/invocations.", + example = "org.mockito.Matchers anyVararg()") + String methodPattern; + + @Option(displayName = "New access level", + description = "New method access level to apply to the method, like \\"public\\".", + example = "public", + valid = {"private", "protected", "package", "public"}, + required = false) + String newAccessLevel; + @Override public String getDisplayName() { return "My recipe"; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java b/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java index c3ccba95506..24675af790f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java @@ -15,18 +15,29 @@ */ package org.openrewrite.java.recipes; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.ValueNode; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.AnnotationMatcher; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.marker.SearchResult; import org.openrewrite.table.RewriteRecipeSource; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + import static java.util.Objects.requireNonNull; public class FindRecipes extends Recipe { @@ -47,7 +58,10 @@ public String getDescription() { public TreeVisitor<?, ExecutionContext> getVisitor() { MethodMatcher getDisplayName = new MethodMatcher("org.openrewrite.Recipe getDisplayName()", true); MethodMatcher getDescription = new MethodMatcher("org.openrewrite.Recipe getDescription()", true); + AnnotationMatcher optionAnnotation = new AnnotationMatcher("@org.openrewrite.Option"); return Preconditions.check(new UsesType<>("org.openrewrite.Recipe", false), new JavaIsoVisitor<ExecutionContext>() { + final List<J.VariableDeclarations> options = new ArrayList<>(); + @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); @@ -56,13 +70,22 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex getCursor().getMessage("displayName"), getCursor().getMessage("description"), RewriteRecipeSource.RecipeType.Java, - getCursor().firstEnclosingOrThrow(J.CompilationUnit.class).printAllTrimmed() + getCursor().firstEnclosingOrThrow(J.CompilationUnit.class).printAllTrimmed(), + convertOptionsToJSON(options) )); return classDecl.withName(SearchResult.found(classDecl.getName())); } return cd; } + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext executionContext) { + if (multiVariable.getLeadingAnnotations().stream().anyMatch(optionAnnotation::matches)) { + options.add(multiVariable); + } + return super.visitVariableDeclarations(multiVariable, executionContext); + } + @Override public J.Return visitReturn(J.Return aReturn, ExecutionContext ctx) { J.MethodDeclaration method = getCursor().firstEnclosingOrThrow(J.MethodDeclaration.class); @@ -76,6 +99,58 @@ public J.Return visitReturn(J.Return aReturn, ExecutionContext ctx) { } return super.visitReturn(aReturn, ctx); } + + private String convertOptionsToJSON(List<J.VariableDeclarations> options) { + ArrayNode optionsArray = JsonNodeFactory.instance.arrayNode(); + for (J.VariableDeclarations option : options) { + ObjectNode optionNode = optionsArray.addObject(); + optionNode.put("name", option.getVariables().get(0).getSimpleName()); + mapOptionAnnotation(option.getLeadingAnnotations(), optionNode); + } + return optionsArray.toString(); + } + + private void mapOptionAnnotation(List<J.Annotation> leadingAnnotations, ObjectNode optionNode) { + for (J.Annotation annotation : leadingAnnotations) { + if (optionAnnotation.matches(annotation) && annotation.getArguments() != null) { + for (Expression argument : annotation.getArguments()) { + if (argument instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) argument; + if (assignment.getVariable() instanceof J.Identifier) { + J.Identifier identifier = (J.Identifier) assignment.getVariable(); + if (assignment.getAssignment() instanceof J.Literal) { + optionNode.set(identifier.getSimpleName(), mapValue(((J.Literal) assignment.getAssignment()).getValue())); + } else if (assignment.getAssignment() instanceof J.NewArray) { + J.NewArray newArray = (J.NewArray) assignment.getAssignment(); + if (newArray.getInitializer() != null) { + ArrayNode valuesArray = optionNode.putArray(identifier.getSimpleName()); + for (Expression expression : newArray.getInitializer()) { + if (expression instanceof J.Literal) { + valuesArray.add(mapValue(((J.Literal) expression).getValue())); + } + } + } + } + } + } + } + break; + } + } + } + + private ValueNode mapValue(@Nullable Object value) { + if (value instanceof String) { + return JsonNodeFactory.instance.textNode((String) value); + } else if (value instanceof Boolean) { + return JsonNodeFactory.instance.booleanNode((Boolean) value); + } else if (value instanceof Integer) { + return JsonNodeFactory.instance.numberNode((Integer) value); + } else if (value == null) { + return JsonNodeFactory.instance.nullNode(); + } + throw new IllegalArgumentException(Objects.toString(value)); + } }); } } From acb7c2bfa392864c4dda9b27802e77701f03f468 Mon Sep 17 00:00:00 2001 From: traceyyoshima <tracey.yoshima@gmail.com> Date: Tue, 10 Oct 2023 10:17:08 -0600 Subject: [PATCH 304/447] Include tools in build. --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 8cbfbd651a5..e24fb973fdc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -42,7 +42,7 @@ val includedProjects = file("IDE.properties").let { }.toSet() if(!file("IDE.properties").exists() || includedProjects.contains("tools")) { -// includeBuild("tools") + includeBuild("tools") } include(*allProjects.toTypedArray()) From 8c8c9ad074a7b02c5478832c62eb8d2bec82014c Mon Sep 17 00:00:00 2001 From: Jorge Otero <jorgeor@ext.inditex.com> Date: Tue, 10 Oct 2023 18:57:36 +0200 Subject: [PATCH 305/447] [TypeUtils.findOverridenMethod] Skip found methods with default access and not same package (#3603) * fix[TypeUtils.findOverridenMethod]: skip found methods with default access and different package * Apply suggestions from code review * Add test and simplify logic for override check --------- Co-authored-by: Tim te Beek <timtebeek@gmail.com> Co-authored-by: Tim te Beek <tim@moderne.io> --- .../openrewrite/java/tree/TypeUtilsTest.java | 29 ++++++++++++++++++- .../org/openrewrite/java/tree/TypeUtils.java | 7 ++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java index 0bc00c649a6..7b2c4fbcc5c 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java @@ -65,7 +65,7 @@ void isOverrideBasicInheritance() { java( """ class Superclass { - void foo(); + void foo() { } } """ ), @@ -80,6 +80,33 @@ class Clazz extends Superclass { ); } + @Test + void isOverrideOnlyVisible() { + rewriteRun( + java( + """ + package foo; + public class Superclass { + void foo() { } + } + """ + ), + java( + """ + package bar; + import foo.Superclass; + class Clazz extends Superclass { + public void foo() { } + } + """, + s -> s.afterRecipe(cu -> { + var fooMethodType = ((J.MethodDeclaration) cu.getClasses().get(0).getBody().getStatements().get(0)).getMethodType(); + assertThat(TypeUtils.findOverriddenMethod(fooMethodType)).isEmpty(); + }) + ) + ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/1759") @Test void isOverrideParameterizedInterface() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 90f93cd3c7f..52f9382f0f3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -424,7 +424,12 @@ public static Optional<JavaType.Method> findOverriddenMethod(@Nullable JavaType. } } } - return methodResult.filter(m -> !m.getFlags().contains(Flag.Private) && !m.getFlags().contains(Flag.Static)); + + return methodResult + .filter(m -> !m.getFlags().contains(Flag.Private)) + .filter(m -> !m.getFlags().contains(Flag.Static)) + // If access level is default then check if subclass package is the same from parent class + .filter(m -> m.getFlags().contains(Flag.Public) || m.getDeclaringType().getPackageName().equals(dt.getPackageName())); } public static Optional<JavaType.Method> findDeclaredMethod(@Nullable JavaType.FullyQualified clazz, String name, List<JavaType> argumentTypes) { From 9a5b79df031326f2882fbab0389415bf78446783 Mon Sep 17 00:00:00 2001 From: traceyyoshima <tracey.yoshima@gmail.com> Date: Tue, 10 Oct 2023 13:11:51 -0600 Subject: [PATCH 306/447] Minor fix in FindRecipes to prevent unexpected reported errors. --- .../java/recipes/FindRecipesTest.java | 19 ++++++++++++++++++ .../openrewrite/java/recipes/FindRecipes.java | 20 +++++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java index 927a2744bde..453da8e37d8 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/FindRecipesTest.java @@ -109,4 +109,23 @@ public String getDescription() { ) ); } + + @Test + void returnInLambda() { + rewriteRun( + spec -> spec.recipe(new FindRecipes()), + java( + """ + import java.util.function.UnaryOperator; + + class SomeTest { + private final UnaryOperator<String> notEmpty = actual -> { + //noinspection CodeBlock2Expr + return actual + "\\n"; + }; + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java b/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java index 24675af790f..31d50530727 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/recipes/FindRecipes.java @@ -88,15 +88,19 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m @Override public J.Return visitReturn(J.Return aReturn, ExecutionContext ctx) { - J.MethodDeclaration method = getCursor().firstEnclosingOrThrow(J.MethodDeclaration.class); - if (getDisplayName.matches(method.getMethodType()) && aReturn.getExpression() instanceof J.Literal) { - getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, "displayName", - requireNonNull(((J.Literal) aReturn.getExpression()).getValue())); - } - if (getDescription.matches(method.getMethodType()) && aReturn.getExpression() instanceof J.Literal) { - getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, "description", - requireNonNull(((J.Literal) aReturn.getExpression()).getValue())); + J j = getCursor().dropParentUntil(it -> it instanceof J.MethodDeclaration || it instanceof J.ClassDeclaration).getValue(); + if (j instanceof J.MethodDeclaration) { + J.MethodDeclaration method = (J.MethodDeclaration) j; + if (getDisplayName.matches(method.getMethodType()) && aReturn.getExpression() instanceof J.Literal) { + getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, "displayName", + requireNonNull(((J.Literal) aReturn.getExpression()).getValue())); + } + if (getDescription.matches(method.getMethodType()) && aReturn.getExpression() instanceof J.Literal) { + getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, "description", + requireNonNull(((J.Literal) aReturn.getExpression()).getValue())); + } } + return super.visitReturn(aReturn, ctx); } From 6fbb85dc12ad3bfb53e3efdcabe345c5156fa507 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 11 Oct 2023 10:33:51 +0200 Subject: [PATCH 307/447] Disable nested jar scanning in `dependenciesFromClasspath()` Nested jar scanning is currently not supported and is thus explicitly disabled in `JavaParser#dependenciesFromClasspath()` so that it doesn't end up causing exceptions (due to missing `FileSystems#newFileSystem()` call). --- rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index 54a5d89354a..a868729ebb3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -79,7 +79,7 @@ static List<Path> runtimeClasspath() { * matching jars can be found. */ static List<Path> dependenciesFromClasspath(String... artifactNames) { - List<URI> runtimeClasspath = new ClassGraph().getClasspathURIs(); + List<URI> runtimeClasspath = new ClassGraph().disableNestedJarScanning().getClasspathURIs(); List<Path> artifacts = new ArrayList<>(artifactNames.length); List<String> missingArtifactNames = new ArrayList<>(artifactNames.length); for (String artifactName : artifactNames) { From 572ce98fa83271cd8a3ba7abb885ac9abdab3f53 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 11 Oct 2023 21:51:14 +0200 Subject: [PATCH 308/447] Revert effect of 78eb336a44dcde35af98fa3dc779b5c11875c202 Calling `Paths#get(URI)` with an unsupported URI can lead to a runtime exception: ``` Caused by: java.nio.file.FileSystemNotFoundException at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:156) at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:142) at java.base/java.nio.file.Path.of(Path.java:208) at java.base/java.nio.file.Paths.get(Paths.java:98) at org.openrewrite.java.JavaParser.dependenciesFromClasspath(JavaParser.java:92) at org.openrewrite.java.JavaParser$Builder.resolvedClasspath(JavaParser.java:380) at org.openrewrite.java.Java17Parser$Builder.build(Java17Parser.java:92) ``` --- .../java/org/openrewrite/java/JavaParser.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index a868729ebb3..d4e0b5ee277 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -78,6 +78,7 @@ static List<Path> runtimeClasspath() { * @return A set of paths of jars on the runtime classpath matching the provided artifact names, to the extent such * matching jars can be found. */ + @SuppressWarnings("DuplicateExpressions") static List<Path> dependenciesFromClasspath(String... artifactNames) { List<URI> runtimeClasspath = new ClassGraph().disableNestedJarScanning().getClasspathURIs(); List<Path> artifacts = new ArrayList<>(artifactNames.length); @@ -89,11 +90,10 @@ static List<Path> dependenciesFromClasspath(String... artifactNames) { boolean lacking = true; for (URI cpEntry : runtimeClasspath) { String cpEntryString = cpEntry.toString(); - Path cpEntryPath = Paths.get(cpEntry); if (jarPattern.matcher(cpEntryString).find() || - (explodedPattern.matcher(cpEntryString).find() && - cpEntryPath.toFile().isDirectory())) { - artifacts.add(cpEntryPath); + (explodedPattern.matcher(cpEntryString).find() && + Paths.get(cpEntry).toFile().isDirectory())) { + artifacts.add(Paths.get(cpEntry)); lacking = false; // Do not break because jarPattern matches "foo-bar-1.0.jar" and "foo-1.0.jar" to "foo" } @@ -105,8 +105,8 @@ static List<Path> dependenciesFromClasspath(String... artifactNames) { if (!missingArtifactNames.isEmpty()) { throw new IllegalArgumentException("Unable to find runtime dependencies beginning with: " + - missingArtifactNames.stream().map(a -> "'" + a + "'").sorted().collect(joining(", ")) + - ", classpath: " + runtimeClasspath); + missingArtifactNames.stream().map(a -> "'" + a + "'").sorted().collect(joining(", ")) + + ", classpath: " + runtimeClasspath); } return artifacts; @@ -157,7 +157,7 @@ static List<Path> dependenciesFromResources(ExecutionContext ctx, String... arti } }) .filter(c -> !c.getName().equals(JavaParser.class.getName()) && - !c.getName().equals(JavaParser.Builder.class.getName())) + !c.getName().equals(JavaParser.Builder.class.getName())) .findFirst() .orElseThrow(() -> new IllegalStateException("Unable to find caller of JavaParser.dependenciesFromResources(..)"))); } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | @@ -199,10 +199,10 @@ static List<Path> dependenciesFromResources(ExecutionContext ctx, String... arti if (!missingArtifactNames.isEmpty()) { //noinspection ConstantValue throw new IllegalArgumentException("Unable to find classpath resource dependencies beginning with: " + - missingArtifactNames.stream().map(a -> "'" + a + "'").sorted().collect(joining(", ", "", ".\n")) + - "The caller is of type " + caller.getName() + ".\n" + - "The resources resolvable from the caller's classpath are: " + - resources.stream().map(Resource::getPath).sorted().collect(joining(", ")) + missingArtifactNames.stream().map(a -> "'" + a + "'").sorted().collect(joining(", ", "", ".\n")) + + "The caller is of type " + caller.getName() + ".\n" + + "The resources resolvable from the caller's classpath are: " + + resources.stream().map(Resource::getPath).sorted().collect(joining(", ")) ); } } @@ -253,7 +253,7 @@ static List<Path> dependenciesFromResources(ExecutionContext ctx, String... arti return javaParser; } catch (Exception e) { throw new IllegalStateException("Unable to create a Java parser instance. " + - "`rewrite-java-8`, `rewrite-java-11`, or `rewrite-java-17` must be on the classpath.", e); + "`rewrite-java-8`, `rewrite-java-11`, or `rewrite-java-17` must be on the classpath.", e); } } @@ -412,7 +412,7 @@ default Path sourcePathFromSourceText(Path prefix, String sourceCode) { String pkg = packageMatcher.find() ? packageMatcher.group(1).replace('.', '/') + "/" : ""; String className = Optional.ofNullable(simpleName.apply(sourceCode)) - .orElse(Long.toString(System.nanoTime())) + ".java"; + .orElse(Long.toString(System.nanoTime())) + ".java"; return prefix.resolve(Paths.get(pkg + className)); } From c54853fde749c90474d343b7cc0eb914d2d4fa4f Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 12 Oct 2023 11:31:53 +0200 Subject: [PATCH 309/447] Formatter: Fix method parameter alignment bug (#3612) The multi-line method parameter alignment which was introduced in #1920 caused excessive indentation when the declaring method had annotations. --- .../org/openrewrite/java/format/TabsAndIndentsTest.java | 6 ++++-- .../org/openrewrite/java/format/TabsAndIndentsVisitor.java | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java index 90dd75b751e..024ef3dc51f 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java @@ -103,9 +103,10 @@ void alignMethodDeclarationParamsWhenMultiple() { java( """ class Test { + @SuppressWarnings private void firstArgNoPrefix(String first, - int times, - String third + int times, + String third ) { } private void firstArgOnNewLine( @@ -118,6 +119,7 @@ private void firstArgOnNewLine( """, """ class Test { + @SuppressWarnings private void firstArgNoPrefix(String first, int times, String third diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java index a4effda558d..f61b11520ab 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java @@ -227,7 +227,9 @@ public <T> JRightPadded<T> visitRightPadded(@Nullable JRightPadded<T> right, JRi alignTo = firstArg.getPrefix().getLastWhitespace().length() - 1; } else { String source = method.print(getCursor()); - alignTo = source.indexOf(firstArg.print(getCursor())) - 1; + int firstArgIndex = source.indexOf(firstArg.print(getCursor())); + int lineBreakIndex = source.lastIndexOf('\n', firstArgIndex); + alignTo = (firstArgIndex - (lineBreakIndex == -1 ? 0 : lineBreakIndex)) - 1; } getCursor().getParentOrThrow().putMessage("lastIndent", alignTo - style.getContinuationIndent()); elem = visitAndCast(elem, p); From af1b3e9ce5356aaca3446cc5c3607b49a5c0f020 Mon Sep 17 00:00:00 2001 From: Alexis Tual <atual@gradle.com> Date: Fri, 13 Oct 2023 13:23:12 +0200 Subject: [PATCH 310/447] Rename `AddGradleEnterpriseGradlePlugin` and `AddGradleEnterpriseMavenExtension` display and description (#3614) --- ...radlePlugin.java => AddDevelocityGradlePlugin.java} | 8 ++++---- ...ginTest.java => AddDevelocityGradlePluginTest.java} | 10 +++++----- ...Extension.java => AddDevelocityMavenExtension.java} | 8 ++++---- ...nTest.java => AddDevelocityMavenExtensionTest.java} | 8 ++++---- 4 files changed, 17 insertions(+), 17 deletions(-) rename rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/{AddGradleEnterpriseGradlePlugin.java => AddDevelocityGradlePlugin.java} (97%) rename rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/{AddGradleEnterpriseGradlePluginTest.java => AddDevelocityGradlePluginTest.java} (94%) rename rewrite-maven/src/main/java/org/openrewrite/maven/{AddGradleEnterpriseMavenExtension.java => AddDevelocityMavenExtension.java} (97%) rename rewrite-maven/src/test/java/org/openrewrite/maven/{AddGradleEnterpriseMavenExtensionTest.java => AddDevelocityMavenExtensionTest.java} (93%) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddDevelocityGradlePlugin.java similarity index 97% rename from rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java rename to rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddDevelocityGradlePlugin.java index c7f34be7d90..4e7101dd47a 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddDevelocityGradlePlugin.java @@ -50,7 +50,7 @@ @Value @EqualsAndHashCode(callSuper = true) @Incubating(since = "7.33.0") -public class AddGradleEnterpriseGradlePlugin extends Recipe { +public class AddDevelocityGradlePlugin extends Recipe { @EqualsAndHashCode.Exclude MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this); @@ -66,7 +66,7 @@ public class AddGradleEnterpriseGradlePlugin extends Recipe { String version; @Option(displayName = "Server URL", - description = "The URL of the Gradle Enterprise server. If omitted the recipe will set no URL and Gradle will direct scans to https://scans.gradle.com/", + description = "The URL of the Develocity server. If omitted the recipe will set no URL and Gradle will direct scans to https://scans.gradle.com/", required = false, example = "https://scans.gradle.com/") @Nullable @@ -113,12 +113,12 @@ public enum PublishCriteria { @Override public String getDisplayName() { - return "Add the Gradle Enterprise Gradle plugin"; + return "Add the Develocity Gradle plugin"; } @Override public String getDescription() { - return "Add the Gradle Enterprise Gradle plugin to settings.gradle files."; + return "Add the Develocity Gradle plugin to settings.gradle files."; } @Override diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddDevelocityGradlePluginTest.java similarity index 94% rename from rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java rename to rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddDevelocityGradlePluginTest.java index 56f026baaad..88f5ff05ecc 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddGradleEnterpriseGradlePluginTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/AddDevelocityGradlePluginTest.java @@ -36,12 +36,12 @@ import static org.openrewrite.gradle.Assertions.*; import static org.openrewrite.test.SourceSpecs.dir; -class AddGradleEnterpriseGradlePluginTest implements RewriteTest { +class AddDevelocityGradlePluginTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec.beforeRecipe(withToolingApi()) - .recipe(new AddGradleEnterpriseGradlePlugin("3.x", null, null, null, null, null)); + .recipe(new AddDevelocityGradlePlugin("3.x", null, null, null, null, null)); } private static Consumer<SourceSpec<CompilationUnit>> interpolateResolvedVersion(@Language("groovy") String after) { @@ -177,7 +177,7 @@ void addExistingSettingsPluginsBlock() { void withConfigurationInSettings() { rewriteRun( spec -> spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))) - .recipe(new AddGradleEnterpriseGradlePlugin("3.x", "https://ge.sam.com/", true, true, true, AddGradleEnterpriseGradlePlugin.PublishCriteria.Always)), + .recipe(new AddDevelocityGradlePlugin("3.x", "https://ge.sam.com/", true, true, true, AddDevelocityGradlePlugin.PublishCriteria.Always)), buildGradle( "" ), @@ -209,7 +209,7 @@ void withConfigurationInSettings() { void withConfigurationOldInputCapture() { rewriteRun( spec -> spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))) - .recipe(new AddGradleEnterpriseGradlePlugin("3.6", null, null, true, null, null)), + .recipe(new AddDevelocityGradlePlugin("3.6", null, null, true, null, null)), buildGradle( "" ), @@ -233,7 +233,7 @@ void withConfigurationOldInputCapture() { void defaultsToLatestRelease() { rewriteRun( spec -> spec.allSources(s -> s.markers(new BuildTool(randomId(), BuildTool.Type.Gradle, "7.6.1"))) - .recipe(new AddGradleEnterpriseGradlePlugin(null, null, null, null, null, null)), + .recipe(new AddDevelocityGradlePlugin(null, null, null, null, null, null)), buildGradle( "" ), diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDevelocityMavenExtension.java similarity index 97% rename from rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java rename to rewrite-maven/src/main/java/org/openrewrite/maven/AddDevelocityMavenExtension.java index 0c4d0fb6754..9eb55b53877 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtension.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDevelocityMavenExtension.java @@ -60,7 +60,7 @@ @Value @EqualsAndHashCode(callSuper = true) -public class AddGradleEnterpriseMavenExtension extends ScanningRecipe<AddGradleEnterpriseMavenExtension.Accumulator> { +public class AddDevelocityMavenExtension extends ScanningRecipe<AddDevelocityMavenExtension.Accumulator> { private static final String GRADLE_ENTERPRISE_MAVEN_EXTENSION_ARTIFACT_ID = "gradle-enterprise-maven-extension"; private static final String EXTENSIONS_XML_PATH = ".mvn/extensions.xml"; private static final String GRADLE_ENTERPRISE_XML_PATH = ".mvn/gradle-enterprise.xml"; @@ -85,7 +85,7 @@ public class AddGradleEnterpriseMavenExtension extends ScanningRecipe<AddGradleE String version; @Option(displayName = "Server URL", - description = "The URL of the Gradle Enterprise server.", + description = "The URL of the Develocity server.", example = "https://scans.gradle.com/") String server; @@ -138,12 +138,12 @@ public enum PublishCriteria { @Override public String getDisplayName() { - return "Add Gradle Enterprise Maven extension"; + return "Add the Develocity Maven extension"; } @Override public String getDescription() { - return "To integrate Gradle Enterprise Maven extension into maven projects, ensure that the " + + return "To integrate the Develocity Maven extension into Maven projects, ensure that the " + "`gradle-enterprise-maven-extension` is added to the `.mvn/extensions.xml` file if not already present. " + "Additionally, configure the extension by adding the `.mvn/gradle-enterprise.xml` configuration file."; } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtensionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddDevelocityMavenExtensionTest.java similarity index 93% rename from rewrite-maven/src/test/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtensionTest.java rename to rewrite-maven/src/test/java/org/openrewrite/maven/AddDevelocityMavenExtensionTest.java index 06083f5ece3..63719d4cb6d 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/AddGradleEnterpriseMavenExtensionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddDevelocityMavenExtensionTest.java @@ -28,10 +28,10 @@ import static org.openrewrite.maven.Assertions.pomXml; import static org.openrewrite.xml.Assertions.xml; -class AddGradleEnterpriseMavenExtensionTest implements RewriteTest { +class AddDevelocityMavenExtensionTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new AddGradleEnterpriseMavenExtension("1.17", "https://foo", null, + spec.recipe(new AddDevelocityMavenExtension("1.17", "https://foo", null, null, null, null)); } @@ -127,7 +127,7 @@ void createNewExtensionsXmlFileIfNotExist() { @Test void noVersionSpecified() { rewriteRun( - spec -> spec.recipe(new AddGradleEnterpriseMavenExtension(null, "https://foo", null, null, null, null)), + spec -> spec.recipe(new AddDevelocityMavenExtension(null, "https://foo", null, null, null, null)), POM_XML_SOURCE_SPEC, xml( null, @@ -181,7 +181,7 @@ void noChangeIfGradleEnterpriseXmlExists() { @Test void allSettings() { rewriteRun( - spec -> spec.recipe(new AddGradleEnterpriseMavenExtension("1.17", "https://foo", true, true, false, AddGradleEnterpriseMavenExtension.PublishCriteria.Failure)), + spec -> spec.recipe(new AddDevelocityMavenExtension("1.17", "https://foo", true, true, false, AddDevelocityMavenExtension.PublishCriteria.Failure)), POM_XML_SOURCE_SPEC, xml( null, From 6704c9727449d6eda8be16779f2ca81231a55212 Mon Sep 17 00:00:00 2001 From: Dawn James <dawnjames2105@gmail.com> Date: Fri, 13 Oct 2023 17:30:41 +0100 Subject: [PATCH 311/447] Expand assertion to print failure message to make it easier to debug the failed test. (#3615) Co-authored-by: Dawn James <dawnj@spotify.com> --- .../utilities/MavenArtifactDownloaderTest.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/utilities/MavenArtifactDownloaderTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/utilities/MavenArtifactDownloaderTest.java index d46316b6fda..5232561ba1c 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/utilities/MavenArtifactDownloaderTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/utilities/MavenArtifactDownloaderTest.java @@ -32,6 +32,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; class MavenArtifactDownloaderTest { @@ -70,9 +71,16 @@ void downloadDependencies(@TempDir Path tempDir) { MavenResolutionResult mavenModel = parsed.getMarkers().findFirst(MavenResolutionResult.class).orElseThrow(); assertThat(mavenModel.getDependencies()).isNotEmpty(); - List<ResolvedDependency> runtimeDependencies = mavenModel.getDependencies().get(Scope.Runtime); - for (ResolvedDependency runtimeDependency : runtimeDependencies) { - assertThat(downloader.downloadArtifact(runtimeDependency)).isNotNull(); - } + List<ResolvedDependency> runtimeDependencies = mavenModel.getDependencies().get(Scope.Runtime); + for (ResolvedDependency runtimeDependency : runtimeDependencies) { + assertNotNull( + downloader.downloadArtifact(runtimeDependency), + String.format( + "%s:%s:%s:%s failed to download", + runtimeDependency.getGroupId(), + runtimeDependency.getArtifactId(), + runtimeDependency.getVersion(), + runtimeDependency.getType())); } + } } From 5234561de388d50f3771b686b708d0ef3e10bc99 Mon Sep 17 00:00:00 2001 From: Simon Verhoeven <verhoeven.simon@gmail.com> Date: Sun, 15 Oct 2023 15:13:10 +0200 Subject: [PATCH 312/447] feat: initial setup for java 21 (#3619) --- rewrite-benchmarks/build.gradle.kts | 2 +- rewrite-gradle/build.gradle.kts | 2 +- rewrite-groovy/build.gradle.kts | 2 +- rewrite-java-11/build.gradle.kts | 2 +- rewrite-java-17/build.gradle.kts | 2 +- rewrite-java-21/build.gradle.kts | 75 + .../org/openrewrite/java/Java21Parser.java | 100 + .../ReloadableJava21JavadocVisitor.java | 1195 ++++++++++ .../ReloadableJava21ModifierResults.java | 38 + .../java/isolated/ReloadableJava21Parser.java | 387 +++ ...ReloadableJava21ParserInputFileObject.java | 127 + .../ReloadableJava21ParserVisitor.java | 2082 +++++++++++++++++ .../isolated/ReloadableJava21TypeMapping.java | 622 +++++ .../ReloadableJava21TypeSignatureBuilder.java | 272 +++ .../java/isolated/package-info.java | 21 + rewrite-java-8/build.gradle.kts | 4 +- rewrite-java-tck/README.md | 5 +- rewrite-java-tck/build.gradle.kts | 2 +- rewrite-java-test/build.gradle.kts | 6 +- rewrite-java/build.gradle.kts | 2 +- .../java/org/openrewrite/java/JavaParser.java | 14 +- rewrite-maven/build.gradle.kts | 2 +- settings.gradle.kts | 1 + .../language-parser-builder/build.gradle.kts | 2 +- 24 files changed, 4950 insertions(+), 17 deletions(-) create mode 100644 rewrite-java-21/build.gradle.kts create mode 100644 rewrite-java-21/src/main/java/org/openrewrite/java/Java21Parser.java create mode 100644 rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java create mode 100644 rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ModifierResults.java create mode 100644 rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java create mode 100644 rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserInputFileObject.java create mode 100644 rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java create mode 100644 rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java create mode 100644 rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeSignatureBuilder.java create mode 100644 rewrite-java-21/src/main/java/org/openrewrite/java/isolated/package-info.java diff --git a/rewrite-benchmarks/build.gradle.kts b/rewrite-benchmarks/build.gradle.kts index 195fa0a4688..323ee326fd2 100644 --- a/rewrite-benchmarks/build.gradle.kts +++ b/rewrite-benchmarks/build.gradle.kts @@ -9,7 +9,7 @@ dependencies { jmh("org.projectlombok:lombok:latest.release") jmh(project(":rewrite-core")) - jmh(project(":rewrite-java-17")) + jmh(project(":rewrite-java-21")) jmh(project(":rewrite-maven")) jmh("org.rocksdb:rocksdbjni:latest.release") jmh("org.openjdk.jmh:jmh-core:latest.release") diff --git a/rewrite-gradle/build.gradle.kts b/rewrite-gradle/build.gradle.kts index e4a5613e6f3..941e4493d97 100644 --- a/rewrite-gradle/build.gradle.kts +++ b/rewrite-gradle/build.gradle.kts @@ -66,7 +66,7 @@ dependencies { testRuntimeOnly(gradleApi()) testRuntimeOnly("com.gradle:gradle-enterprise-gradle-plugin:latest.release") testRuntimeOnly("com.google.guava:guava:latest.release") - testRuntimeOnly(project(":rewrite-java-17")) + testRuntimeOnly(project(":rewrite-java-21")) testRuntimeOnly("org.projectlombok:lombok:latest.release") } diff --git a/rewrite-groovy/build.gradle.kts b/rewrite-groovy/build.gradle.kts index 743305710d7..936bea0c932 100644 --- a/rewrite-groovy/build.gradle.kts +++ b/rewrite-groovy/build.gradle.kts @@ -20,5 +20,5 @@ dependencies { testImplementation(project(":rewrite-java-test")) testImplementation("org.junit-pioneer:junit-pioneer:2.0.0") testRuntimeOnly("org.codehaus.groovy:groovy-all:latest.release") - testRuntimeOnly(project(":rewrite-java-17")) + testRuntimeOnly(project(":rewrite-java-21")) } diff --git a/rewrite-java-11/build.gradle.kts b/rewrite-java-11/build.gradle.kts index 41d3cfc8a83..b7706712520 100644 --- a/rewrite-java-11/build.gradle.kts +++ b/rewrite-java-11/build.gradle.kts @@ -65,7 +65,7 @@ testing { all { testTask.configure { useJUnitPlatform { - excludeTags("java11", "java17") + excludeTags("java11", "java17", "java21") } jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) diff --git a/rewrite-java-17/build.gradle.kts b/rewrite-java-17/build.gradle.kts index 80c922fb6ff..0cf4421911c 100644 --- a/rewrite-java-17/build.gradle.kts +++ b/rewrite-java-17/build.gradle.kts @@ -59,7 +59,7 @@ testing { all { testTask.configure { useJUnitPlatform { - excludeTags("java11", "java17") + excludeTags("java11", "java17", "java21") } jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) diff --git a/rewrite-java-21/build.gradle.kts b/rewrite-java-21/build.gradle.kts new file mode 100644 index 00000000000..4810d3a3e3d --- /dev/null +++ b/rewrite-java-21/build.gradle.kts @@ -0,0 +1,75 @@ +plugins { + id("org.openrewrite.build.language-library") + id("jvm-test-suite") +} + +dependencies { + api(project(":rewrite-core")) + api(project(":rewrite-java")) + + compileOnly("org.slf4j:slf4j-api:1.7.+") + + implementation("io.micrometer:micrometer-core:1.9.+") + implementation("io.github.classgraph:classgraph:latest.release") + implementation("org.ow2.asm:asm:latest.release") + + testImplementation(project(":rewrite-test")) +} + +tasks.withType<JavaCompile> { + // allows --add-exports to in spite of the JDK's restrictions on this + sourceCompatibility = JavaVersion.VERSION_21.toString() + targetCompatibility = JavaVersion.VERSION_21.toString() + + options.release.set(null as Int?) // remove `--release 8` set in `org.openrewrite.java-base` + options.compilerArgs.addAll( + listOf( + "--add-exports", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" + ) + ) +} + +//Javadoc compiler will complain about the use of the internal types. +tasks.withType<Javadoc> { + exclude( + "**/ReloadableJava21JavadocVisitor**", + "**/ReloadableJava21Parser**", + "**/ReloadableJava21ParserVisitor**", + "**/ReloadableJava21TypeMapping**", + "**/ReloadableJava21TypeSignatureBuilder**" + ) +} + +testing { + suites { + val test by getting(JvmTestSuite::class) + + register("compatibilityTest", JvmTestSuite::class) { + dependencies { + implementation(project()) + implementation(project(":rewrite-java-tck")) + } + + targets { + all { + testTask.configure { + useJUnitPlatform { + excludeTags("java11", "java17", "java21") + } + jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") + shouldRunAfter(test) + } + } + } + } + } +} + +tasks.named("check") { + dependsOn(testing.suites.named("compatibilityTest")) +} diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/Java21Parser.java b/rewrite-java-21/src/main/java/org/openrewrite/java/Java21Parser.java new file mode 100644 index 00000000000..04fa6a90875 --- /dev/null +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/Java21Parser.java @@ -0,0 +1,100 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.SourceFile; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.internal.JavaTypeCache; + +import java.lang.reflect.Constructor; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Collection; +import java.util.stream.Stream; + +public class Java21Parser implements JavaParser { + private final JavaParser delegate; + + Java21Parser(JavaParser delegate) { + this.delegate = delegate; + } + + @Override + public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { + return delegate.parseInputs(sourceFiles, relativeTo, ctx); + } + + @Override + public JavaParser reset() { + delegate.reset(); + return this; + } + + @Override + public JavaParser reset(Collection<URI> cus) { + delegate.reset(cus); + return this; + } + + @Override + public void setClasspath(Collection<Path> classpath) { + delegate.setClasspath(classpath); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends JavaParser.Builder<Java21Parser, Builder> { + + @Nullable + private static ClassLoader moduleClassLoader; + + static synchronized void lazyInitClassLoaders() { + if (moduleClassLoader != null) { + return; + } + + ClassLoader appClassLoader = Java21Parser.class.getClassLoader(); + moduleClassLoader = new JavaUnrestrictedClassLoader(appClassLoader); + } + + @Override + public Java21Parser build() { + lazyInitClassLoaders(); + + try { + //Load the parser implementation use the unrestricted module classloader. + Class<?> parserImplementation = Class.forName("org.openrewrite.java.isolated.ReloadableJava21Parser", true, moduleClassLoader); + + Constructor<?> parserConstructor = parserImplementation + .getDeclaredConstructor(Boolean.TYPE, Collection.class, Collection.class, Collection.class, Charset.class, + Collection.class, JavaTypeCache.class); + + parserConstructor.setAccessible(true); + + JavaParser delegate = (JavaParser) parserConstructor + .newInstance(logCompilationWarningsAndErrors, resolvedClasspath(), classBytesClasspath, dependsOn, charset, styles, javaTypeCache); + + return new Java21Parser(delegate); + } catch (Exception e) { + throw new IllegalStateException("Unable to construct Java21Parser.", e); + } + } + } +} diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java new file mode 100644 index 00000000000..d05a6d1ce07 --- /dev/null +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java @@ -0,0 +1,1195 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.isolated; + +import com.sun.source.doctree.ErroneousTree; +import com.sun.source.doctree.LiteralTree; +import com.sun.source.doctree.ProvidesTree; +import com.sun.source.doctree.ReturnTree; +import com.sun.source.doctree.UsesTree; +import com.sun.source.doctree.*; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.*; +import com.sun.source.util.DocTreeScanner; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreeScanner; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.comp.Attr; +import com.sun.tools.javac.tree.DCTree; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Context; +import org.openrewrite.Tree; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; + +import java.util.*; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.openrewrite.Tree.randomId; + +public class ReloadableJava21JavadocVisitor extends DocTreeScanner<Tree, List<Javadoc>> { + private final Attr attr; + + @Nullable + private final Symbol.TypeSymbol symbol; + + @Nullable + private final Type enclosingClassType; + + private final ReloadableJava21TypeMapping typeMapping; + private final TreeScanner<J, Space> javaVisitor = new JavaVisitor(); + private final Map<Integer, Javadoc.LineBreak> lineBreaks = new HashMap<>(); + + /** + * The whitespace on the first line terminated by a newline (if any) + */ + private String firstPrefix = ""; + + private String source; + private int cursor = 0; + + public ReloadableJava21JavadocVisitor(Context context, TreePath scope, ReloadableJava21TypeMapping typeMapping, String source, JCTree tree) { + this.attr = Attr.instance(context); + this.typeMapping = typeMapping; + this.source = source; + + if (scope.getLeaf() instanceof JCTree.JCCompilationUnit) { + this.enclosingClassType = tree.type; + this.symbol = ((JCTree.JCClassDecl) tree).sym; + } else { + com.sun.source.tree.Tree classDecl = scope.getLeaf(); + if (classDecl instanceof JCTree.JCClassDecl) { + this.enclosingClassType = ((JCTree.JCClassDecl) classDecl).type; + this.symbol = ((JCTree.JCClassDecl) classDecl).sym; + } else if (classDecl instanceof JCTree.JCNewClass) { + this.enclosingClassType = ((JCTree.JCNewClass) classDecl).def.type; + this.symbol = ((JCTree.JCNewClass) classDecl).def.sym; + } else { + this.enclosingClassType = null; + this.symbol = null; + } + } + } + + private void init() { + StringBuilder firstPrefixBuilder = new StringBuilder(); + StringBuilder javadocContent = new StringBuilder(); + StringBuilder marginBuilder = null; + boolean inFirstPrefix = true; + boolean isPrefixAsterisk = true; + + // skip past the opening '/**' + int i = 3; + for (; i < source.length(); i++) { + char c = source.charAt(i); + if (inFirstPrefix) { + // '*' characters are considered a part of the margin until a non '*' is parsed. + if (Character.isWhitespace(c) || c == '*' && isPrefixAsterisk) { + if (isPrefixAsterisk && i + 1 <= source.length() - 1 && source.charAt(i + 1) != '*') { + isPrefixAsterisk = false; + } + firstPrefixBuilder.append(c); + } else { + firstPrefix = firstPrefixBuilder.toString(); + inFirstPrefix = false; + } + } + + if (c == '\r') { + continue; + } + + if (c == '\n') { + char prev = source.charAt(i - 1); + if (inFirstPrefix) { + firstPrefix = firstPrefixBuilder.toString(); + inFirstPrefix = false; + } else { + // Handle consecutive new lines. + if ((prev == '\n' || + prev == '\r' && source.charAt(i - 2) == '\n')) { + String prevLineLine = prev == '\n' ? "\n" : "\r\n"; + lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), prevLineLine, Markers.EMPTY)); + } else if (marginBuilder != null) { // A new line with no '*' that only contains whitespace. + String newLine = prev == '\r' ? "\r\n" : "\n"; + lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), newLine, Markers.EMPTY)); + javadocContent.append(marginBuilder.substring(marginBuilder.indexOf("\n") + 1)); + } + javadocContent.append(c); + } + String newLine = prev == '\r' ? "\r\n" : "\n"; + marginBuilder = new StringBuilder(newLine); + } else if (marginBuilder != null) { + if (!Character.isWhitespace(c)) { + if (c == '*') { + // '*' characters are considered a part of the margin until a non '*' is parsed. + marginBuilder.append(c); + if (i + 1 <= source.length() - 1 && source.charAt(i + 1) != '*') { + lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), + marginBuilder.toString(), Markers.EMPTY)); + marginBuilder = null; + } + } else { + if (c == '@') { + lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), + marginBuilder.toString(), Markers.EMPTY)); + javadocContent.append(c); + } else { + String newLine = marginBuilder.charAt(0) == '\r' ? "\r\n" : "\n"; + lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), + newLine, Markers.EMPTY)); + String margin = marginBuilder.toString(); + javadocContent.append(margin.substring(margin.indexOf("\n") + 1)).append(c); + } + marginBuilder = null; + } + } else { + marginBuilder.append(c); + } + } else if (!inFirstPrefix) { + javadocContent.append(c); + } + } + + if (inFirstPrefix) { + javadocContent.append(firstPrefixBuilder); + } + + source = javadocContent.toString(); + + if (marginBuilder != null && marginBuilder.length() > 0) { + if (javadocContent.length() > 0 && javadocContent.charAt(0) != '\n') { + lineBreaks.put(javadocContent.length(), new Javadoc.LineBreak(randomId(), + marginBuilder.toString(), Markers.EMPTY)); + source = source.substring(0, source.length() - 1); // strip trailing newline + } else { + lineBreaks.put(source.length(), new Javadoc.LineBreak(randomId(), + marginBuilder.toString(), Markers.EMPTY)); + } + } + } + + @Override + public Tree visitAttribute(AttributeTree node, List<Javadoc> body) { + String name = node.getName().toString(); + cursor += name.length(); + List<Javadoc> beforeEqual; + List<Javadoc> value; + + if (node.getValueKind() == AttributeTree.ValueKind.EMPTY) { + beforeEqual = emptyList(); + value = emptyList(); + } else { + beforeEqual = new ArrayList<>(); + value = new ArrayList<>(); + Javadoc.LineBreak lineBreak; + while ((lineBreak = lineBreaks.remove(cursor + 1)) != null) { + cursor++; + beforeEqual.add(lineBreak); + } + String whitespaceBeforeEqual = whitespaceBeforeAsString(); + beforeEqual.add(new Javadoc.Text(randomId(), Markers.EMPTY, whitespaceBeforeEqual)); + + sourceBefore("="); + + while ((lineBreak = lineBreaks.remove(cursor + 1)) != null) { + cursor++; + value.add(lineBreak); + } + + switch (node.getValueKind()) { + case UNQUOTED: + value.addAll(convertMultiline(node.getValue())); + break; + case SINGLE: + value.addAll(sourceBefore("'")); + value.add(new Javadoc.Text(randomId(), Markers.EMPTY, "'")); + value.addAll(convertMultiline(node.getValue())); + value.addAll(sourceBefore("'")); + value.add(new Javadoc.Text(randomId(), Markers.EMPTY, "'")); + break; + case DOUBLE: + default: + value.addAll(sourceBefore("\"")); + value.add(new Javadoc.Text(randomId(), Markers.EMPTY, "\"")); + value.addAll(convertMultiline(node.getValue())); + value.addAll(sourceBefore("\"")); + value.add(new Javadoc.Text(randomId(), Markers.EMPTY, "\"")); + break; + } + } + + return new Javadoc.Attribute( + randomId(), + Markers.EMPTY, + name, + beforeEqual, + value + ); + } + + @Override + public Tree visitAuthor(AuthorTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@author")); + return new Javadoc.Author(randomId(), Markers.EMPTY, convertMultiline(node.getName())); + } + + @Override + public Tree visitComment(CommentTree node, List<Javadoc> body) { + cursor += node.getBody().length(); + return new Javadoc.Text(randomId(), Markers.EMPTY, node.getBody()); + } + + @Override + public Tree visitDeprecated(DeprecatedTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@deprecated")); + return new Javadoc.Deprecated(randomId(), Markers.EMPTY, convertMultiline(node.getBody())); + } + + @Override + public Tree visitDocComment(DocCommentTree node, List<Javadoc> body) { + init(); + + Javadoc.LineBreak leadingLineBreak = lineBreaks.remove(0); + if (leadingLineBreak != null) { + if (!firstPrefix.isEmpty()) { + body.add(new Javadoc.Text(randomId(), Markers.EMPTY, firstPrefix.substring(0, firstPrefix.length() - (firstPrefix.endsWith("\r\n") ? 2 : 1)))); + firstPrefix = ""; + } + body.add(leadingLineBreak); + } + + if (!firstPrefix.isEmpty()) { + body.add(new Javadoc.Text(randomId(), Markers.EMPTY, firstPrefix)); + } + + List<? extends DocTree> fullBody = node.getFullBody(); + for (int i = 0; i < fullBody.size(); i++) { + DocTree docTree = fullBody.get(i); + if (!(docTree instanceof DCTree.DCText && i > 0)) { + body.addAll(whitespaceBefore()); + } + if (docTree instanceof DCTree.DCText) { + body.addAll(visitText(((DCTree.DCText) docTree).getBody())); + } else { + body.add((Javadoc) scan(docTree, body)); + } + } + + Javadoc.LineBreak lineBreak; + + for (DocTree blockTag : node.getBlockTags()) { + spaceBeforeTags: + while (true) { + if ((lineBreak = lineBreaks.remove(cursor + 1)) != null) { + cursor++; + body.add(lineBreak); + } + + StringBuilder whitespaceBeforeNewLine = new StringBuilder(); + for (int j = cursor; j < source.length(); j++) { + char ch = source.charAt(j); + if (ch == '\r') { + cursor++; + continue; + } + + if (ch == '\n') { + if (whitespaceBeforeNewLine.length() > 0) { + body.add(new Javadoc.Text(randomId(), Markers.EMPTY, whitespaceBeforeNewLine.toString())); + } + cursor += whitespaceBeforeNewLine.length(); + break; + } else if (Character.isWhitespace(ch)) { + whitespaceBeforeNewLine.append(ch); + } else { + if (whitespaceBeforeNewLine.length() > 0) { + body.add(new Javadoc.Text(randomId(), Markers.EMPTY, whitespaceBeforeNewLine.toString())); + cursor += whitespaceBeforeNewLine.length(); + } + break spaceBeforeTags; + } + } + + if (lineBreak == null) { + break; + } + } + + body.addAll(whitespaceBefore()); + body.addAll(convertMultiline(singletonList(blockTag))); + } + + // The javadoc ends with trailing whitespace. + if (cursor < source.length()) { + String trailingWhitespace = source.substring(cursor); + if (trailingWhitespace.contains("\n")) { + // 1 or more newlines. + String[] parts = trailingWhitespace.split("\n"); + for (String part : parts) { + // Add trailing whitespace for each new line. + if (!part.isEmpty()) { + body.add(new Javadoc.Text(randomId(), Markers.EMPTY, part)); + } + // Add trailing linebreaks if they exist. + if (!lineBreaks.isEmpty()) { + int pos = Collections.min(lineBreaks.keySet()); + if (lineBreaks.containsKey(pos)) { + body.add(lineBreaks.get(pos)); + lineBreaks.remove(pos); + } + } + } + } else { + // The Javadoc ends with trailing whitespace. + body.add(new Javadoc.Text(randomId(), Markers.EMPTY, trailingWhitespace)); + } + } + + if (!lineBreaks.isEmpty()) { + lineBreaks.keySet().stream().sorted().forEach(o -> body.add(lineBreaks.get(o))); + } + + return new Javadoc.DocComment(randomId(), Markers.EMPTY, body, ""); + } + + @Override + public Tree visitDocRoot(DocRootTree node, List<Javadoc> body) { + body.addAll(sourceBefore("{@docRoot")); + return new Javadoc.DocRoot( + randomId(), + Markers.EMPTY, + endBrace() + ); + } + + @Override + public Tree visitDocType(DocTypeTree node, List<Javadoc> body) { + body.addAll(sourceBefore("<!doctype")); + return new Javadoc.DocType(randomId(), Markers.EMPTY, + ListUtils.concatAll( + ListUtils.concat(sourceBefore(node.getText()), new Javadoc.Text(randomId(), Markers.EMPTY, node.getText())), + sourceBefore(">") + ) + ); + } + + @Override + public Tree visitEndElement(EndElementTree node, List<Javadoc> body) { + body.addAll(sourceBefore("</")); + String name = node.getName().toString(); + cursor += name.length(); + return new Javadoc.EndElement( + randomId(), + Markers.EMPTY, + name, + sourceBefore(">") + ); + } + + @Override + public Tree visitEntity(EntityTree node, List<Javadoc> body) { + body.addAll(sourceBefore("&")); + cursor += node.getName().length() + 1; + return new Javadoc.Text(randomId(), Markers.EMPTY, "&" + node.getName().toString() + ";"); + } + + @Override + public Tree visitErroneous(ErroneousTree node, List<Javadoc> body) { + return new Javadoc.Erroneous(randomId(), Markers.EMPTY, visitText(node.getBody())); + } + + @Override + public Tree visitHidden(HiddenTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@hidden")); + return new Javadoc.Hidden(randomId(), Markers.EMPTY, convertMultiline(node.getBody())); + } + + @Override + public J.Identifier visitIdentifier(com.sun.source.doctree.IdentifierTree node, List<Javadoc> body) { + String name = node.getName().toString(); + sourceBefore(name); + return new J.Identifier( + randomId(), + Space.EMPTY, + Markers.EMPTY, + emptyList(), + name, + null, + null + ); + } + + @Override + public Tree visitIndex(IndexTree node, List<Javadoc> body) { + body.addAll(sourceBefore("{@index")); + List<Javadoc> searchTerm = ListUtils.concatAll(whitespaceBefore(), convertMultiline(singletonList(node.getSearchTerm()))); + List<Javadoc> description = convertMultiline(node.getDescription()); + List<Javadoc> paddedDescription = ListUtils.flatMap(description, (i, desc) -> { + if (i == description.size() - 1) { + if (desc instanceof Javadoc.Text) { + Javadoc.Text text = (Javadoc.Text) desc; + return text.withText(text.getText()); + } else { + return ListUtils.concat(desc, endBrace()); + } + } + return desc; + }); + + return new Javadoc.Index( + randomId(), + Markers.EMPTY, + searchTerm, + paddedDescription, + endBrace() + ); + } + + @Override + public Tree visitInheritDoc(InheritDocTree node, List<Javadoc> body) { + body.addAll(sourceBefore("{@inheritDoc")); + return new Javadoc.InheritDoc( + randomId(), + Markers.EMPTY, + endBrace() + ); + } + + @Override + public Tree visitLink(LinkTree node, List<Javadoc> body) { + body.addAll(sourceBefore(node.getKind() == DocTree.Kind.LINK ? "{@link" : "{@linkplain")); + + List<Javadoc> spaceBeforeRef = whitespaceBefore(); + Javadoc.Reference reference = null; + J ref = visitReference(node.getReference(), body); + //noinspection ConstantConditions + if (ref != null) { + reference = new Javadoc.Reference(randomId(), Markers.EMPTY, ref, lineBreaksInMultilineJReference()); + } + + List<Javadoc> label = convertMultiline(node.getLabel()); + + return new Javadoc.Link( + randomId(), + Markers.EMPTY, + node.getKind() != DocTree.Kind.LINK, + spaceBeforeRef, + null, + reference, + label, + endBrace() + ); + } + + @Override + public Tree visitLiteral(LiteralTree node, List<Javadoc> body) { + body.addAll(sourceBefore(node.getKind() == DocTree.Kind.CODE ? "{@code" : "{@literal")); + + List<Javadoc> description = whitespaceBefore(); + description.addAll(visitText(node.getBody().getBody())); + + return new Javadoc.Literal( + randomId(), + Markers.EMPTY, + node.getKind() == DocTree.Kind.CODE, + description, + endBrace() + ); + } + + @Override + public Tree visitParam(ParamTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@param")); + DCTree.DCParam param = (DCTree.DCParam) node; + + List<Javadoc> spaceBefore; + J typeName; + + if (param.isTypeParameter) { + spaceBefore = sourceBefore("<"); + + String beforeName = whitespaceBeforeAsString(); + Space namePrefix = beforeName.isEmpty() ? Space.EMPTY : Space.build(beforeName, emptyList()); + typeName = new J.TypeParameter( + randomId(), + Space.EMPTY, + Markers.EMPTY, + emptyList(), + visitIdentifier(node.getName(), whitespaceBefore()).withPrefix(namePrefix), + null + ); + // The node will not be considered a type parameter if whitespace exists before `>`. + sourceBefore(">"); + } else { + spaceBefore = whitespaceBefore(); + typeName = (J) scan(node.getName(), body); + } + + List<Javadoc> beforeReference = lineBreaksInMultilineJReference(); + Javadoc.Reference reference = new Javadoc.Reference(randomId(), Markers.EMPTY, typeName, beforeReference); + return new Javadoc.Parameter( + randomId(), + Markers.EMPTY, + spaceBefore, + null, + reference, + convertMultiline(param.getDescription()) + ); + } + + @Override + public Tree visitProvides(ProvidesTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@provides")); + return new Javadoc.Provides(randomId(), Markers.EMPTY, + whitespaceBefore(), + visitReference(node.getServiceType(), body), + convertMultiline(node.getDescription()) + ); + } + + @Override + public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { + DCTree.DCReference ref = (DCTree.DCReference) node; + if (node == null) { + //noinspection ConstantConditions + return null; + } + + JavaType qualifierType; + TypedTree qualifier; + + if (ref.qualifierExpression != null) { + try { + attr.attribType(ref.qualifierExpression, symbol); + } catch (NullPointerException ignored) { + // best effort, can result in: + // java.lang.NullPointerException: Cannot read field "info" because "env" is null + // at com.sun.tools.javac.comp.Attr.attribType(Attr.java:404) + } + } + + if (ref.qualifierExpression != null) { + qualifier = (TypedTree) javaVisitor.scan(ref.qualifierExpression, Space.EMPTY); + qualifierType = qualifier.getType(); + if (ref.memberName != null) { + cursor++; // skip # + } + } else { + qualifierType = typeMapping.type(enclosingClassType); + if (source.charAt(cursor) == '#') { + qualifier = new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "", qualifierType, null); + cursor++; + } else { + qualifier = null; + } + + } + + if (ref.memberName != null) { + J.Identifier name = new J.Identifier( + randomId(), + Space.EMPTY, + Markers.EMPTY, + emptyList(), + ref.memberName.toString(), + null, + null + ); + + cursor += ref.memberName.toString().length(); + + JavaType.Method methodRefType = methodReferenceType(ref, qualifierType); + JavaType.Variable fieldRefType = methodRefType == null ? + fieldReferenceType(ref, qualifierType) : null; + + if (ref.paramTypes != null) { + JContainer<Expression> paramContainer; + sourceBeforeAsString("("); + if (ref.paramTypes.isEmpty()) { + paramContainer = JContainer.build( + Space.EMPTY, + singletonList(JRightPadded.build(new J.Empty(randomId(), + Space.build(sourceBeforeAsString(")"), emptyList()), Markers.EMPTY))), + Markers.EMPTY + ); + } else { + List<JRightPadded<Expression>> parameters = new ArrayList<>(ref.paramTypes.size()); + List<JCTree> paramTypes = ref.paramTypes; + for (int i = 0; i < paramTypes.size(); i++) { + JCTree param = paramTypes.get(i); + Expression paramExpr = (Expression) javaVisitor.scan(param, Space.build(whitespaceBeforeAsString(), emptyList())); + Space rightFmt = Space.format(i == paramTypes.size() - 1 ? + sourceBeforeAsString(")") : sourceBeforeAsString(",")); + parameters.add(new JRightPadded<>(paramExpr, rightFmt, Markers.EMPTY)); + } + paramContainer = JContainer.build( + Space.EMPTY, + parameters, + Markers.EMPTY + ); + } + + return new J.MethodInvocation( + randomId(), + qualifier == null ? Space.EMPTY : qualifier.getPrefix(), + Markers.EMPTY, + qualifier == null ? null : JRightPadded.build(qualifier.withPrefix(Space.EMPTY)), + null, + name, + paramContainer, + methodRefType + ); + } else { + return new J.MemberReference( + randomId(), + qualifier == null ? Space.EMPTY : qualifier.getPrefix(), + Markers.EMPTY, + qualifier == null ? null : JRightPadded.build(qualifier.withPrefix(Space.EMPTY)), + JContainer.empty(), + JLeftPadded.build(name), + null, + methodRefType, + fieldRefType + ); + } + } + + assert qualifier != null; + return qualifier; + } + + @Nullable + private JavaType.Method methodReferenceType(DCTree.DCReference ref, @Nullable JavaType type) { + if (type instanceof JavaType.Class) { + JavaType.Class classType = (JavaType.Class) type; + + nextMethod: + for (JavaType.Method method : classType.getMethods()) { + if (method.getName().equals(ref.memberName.toString())) { + if (ref.paramTypes != null) { + for (JCTree param : ref.paramTypes) { + for (JavaType testParamType : method.getParameterTypes()) { + Type paramType = attr.attribType(param, symbol); + if (testParamType instanceof JavaType.GenericTypeVariable) { + for (JavaType bound : ((JavaType.GenericTypeVariable) testParamType).getBounds()) { + if (paramTypeMatches(bound, paramType)) { + return method; + } + } + continue nextMethod; + } + + if (paramTypeMatches(testParamType, paramType)) { + continue nextMethod; + } + } + } + } + + return method; + } + } + } else if (type instanceof JavaType.GenericTypeVariable) { + JavaType.GenericTypeVariable generic = (JavaType.GenericTypeVariable) type; + for (JavaType bound : generic.getBounds()) { + JavaType.Method method = methodReferenceType(ref, bound); + if (method != null) { + return method; + } + } + } + // a member reference, but not matching anything on type attribution + return null; + } + + private boolean paramTypeMatches(JavaType testParamType, Type paramType) { + if (paramType instanceof Type.ClassType) { + JavaType.FullyQualified fqTestParamType = TypeUtils.asFullyQualified(testParamType); + return fqTestParamType == null || !fqTestParamType.getFullyQualifiedName().equals(((Symbol.ClassSymbol) paramType.tsym) + .fullname.toString()); + } + return false; + } + + @Nullable + private JavaType.Variable fieldReferenceType(DCTree.DCReference ref, @Nullable JavaType type) { + JavaType.Class classType = TypeUtils.asClass(type); + if (classType == null) { + return null; + } + + for (JavaType.Variable member : classType.getMembers()) { + if (member.getName().equals(ref.memberName.toString())) { + return member; + } + } + + // a member reference, but not matching anything on type attribution + return null; + } + + @Override + public Tree visitReturn(ReturnTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@return")); + return new Javadoc.Return(randomId(), Markers.EMPTY, convertMultiline(node.getDescription())); + } + + @Override + public Tree visitSee(SeeTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@see")); + + Javadoc.Reference reference = null; + + J ref; + List<Javadoc> spaceBeforeTree = whitespaceBefore(); + List<Javadoc> docs; + if (node.getReference().get(0) instanceof DCTree.DCReference) { + ref = visitReference((ReferenceTree) node.getReference().get(0), body); + //noinspection ConstantConditions + if (ref != null) { + reference = new Javadoc.Reference(randomId(), Markers.EMPTY, ref, lineBreaksInMultilineJReference()); + } + docs = convertMultiline(node.getReference().subList(1, node.getReference().size())); + } else { + docs = convertMultiline(node.getReference()); + } + + return new Javadoc.See(randomId(), Markers.EMPTY, spaceBeforeTree, null, reference, docs); + } + + @Override + public Tree visitSerial(SerialTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@serial")); + return new Javadoc.Serial(randomId(), Markers.EMPTY, convertMultiline(node.getDescription())); + } + + @Override + public Tree visitSerialData(SerialDataTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@serialData")); + return new Javadoc.SerialData(randomId(), Markers.EMPTY, convertMultiline(node.getDescription())); + } + + @Override + public Tree visitSerialField(SerialFieldTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@serialField")); + return new Javadoc.SerialField(randomId(), Markers.EMPTY, + visitIdentifier(node.getName(), whitespaceBefore()), + visitReference(node.getType(), whitespaceBefore()), + convertMultiline(node.getDescription()) + ); + } + + @Override + public Tree visitSince(SinceTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@since")); + return new Javadoc.Since(randomId(), Markers.EMPTY, convertMultiline(node.getBody())); + } + + @Override + public Tree visitStartElement(StartElementTree node, List<Javadoc> body) { + body.addAll(sourceBefore("<")); + String name = node.getName().toString(); + cursor += name.length(); + return new Javadoc.StartElement( + randomId(), + Markers.EMPTY, + name, + convertMultiline(node.getAttributes()), + node.isSelfClosing(), + node.isSelfClosing() ? sourceBefore("/>") : sourceBefore(">") + ); + } + + @Override + public Tree visitSummary(SummaryTree node, List<Javadoc> body) { + body.addAll(sourceBefore("{@summary")); + + List<Javadoc> summary = convertMultiline(node.getSummary()); + + List<Javadoc> paddedSummary = ListUtils.flatMap(summary, (i, sum) -> { + if (i == summary.size() - 1) { + if (sum instanceof Javadoc.Text) { + Javadoc.Text text = (Javadoc.Text) sum; + return ListUtils.concat(text.withText(text.getText()), endBrace()); + } else { + return ListUtils.concat(sum, endBrace()); + } + } + return sum; + }); + + return new Javadoc.Summary( + randomId(), + Markers.EMPTY, + paddedSummary, + endBrace() + ); + } + + @Override + public Tree visitVersion(VersionTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@version")); + return new Javadoc.Version(randomId(), Markers.EMPTY, convertMultiline(node.getBody())); + } + + @Override + public Tree visitText(TextTree node, List<Javadoc> body) { + throw new UnsupportedOperationException("Anywhere text can occur, we need to call the visitText override that " + + "returns a list of Javadoc elements."); + } + + public List<Javadoc> visitText(String node) { + List<Javadoc> texts = new ArrayList<>(); + + if (!node.isEmpty() && Character.isWhitespace(node.charAt(0)) && + !Character.isWhitespace(source.charAt(cursor))) { + node = node.stripLeading(); + } + + StringBuilder text = new StringBuilder(); + for (int i = 0; i < node.length(); i++) { + char c = node.charAt(i); + cursor++; + if (c == '\n') { + if (text.length() > 0) { + texts.add(new Javadoc.Text(randomId(), Markers.EMPTY, text.toString())); + text = new StringBuilder(); + } + + Javadoc.LineBreak lineBreak = lineBreaks.remove(cursor); + assert lineBreak != null; + texts.add(lineBreak); + } else { + text.append(c); + } + } + + if (text.length() > 0) { + texts.add(new Javadoc.Text(randomId(), Markers.EMPTY, text.toString())); + } + + return texts; + } + + @Override + public Tree visitThrows(ThrowsTree node, List<Javadoc> body) { + boolean throwsKeyword = source.startsWith("@throws", cursor); + sourceBefore(throwsKeyword ? "@throws" : "@exception"); + List<Javadoc> spaceBeforeExceptionName = whitespaceBefore(); + return new Javadoc.Throws( + randomId(), + Markers.EMPTY, + throwsKeyword, + spaceBeforeExceptionName, + visitReference(node.getExceptionName(), body), + convertMultiline(node.getDescription()) + ); + } + + @Override + public Tree visitUnknownBlockTag(UnknownBlockTagTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@" + node.getTagName())); + return new Javadoc.UnknownBlock( + randomId(), + Markers.EMPTY, + node.getTagName(), + convertMultiline(node.getContent()) + ); + } + + @Override + public Tree visitUnknownInlineTag(UnknownInlineTagTree node, List<Javadoc> body) { + body.addAll(sourceBefore("{@" + node.getTagName())); + return new Javadoc.UnknownInline( + randomId(), + Markers.EMPTY, + node.getTagName(), + convertMultiline(node.getContent()), + endBrace() + ); + } + + @Override + public Tree visitUses(UsesTree node, List<Javadoc> body) { + body.addAll(sourceBefore("@uses")); + return new Javadoc.Uses(randomId(), Markers.EMPTY, + whitespaceBefore(), + visitReference(node.getServiceType(), body), + convertMultiline(node.getDescription()) + ); + } + + @Override + public Tree visitValue(ValueTree node, List<Javadoc> body) { + body.addAll(sourceBefore("{@value")); + return new Javadoc.InlinedValue( + randomId(), + Markers.EMPTY, + whitespaceBefore(), + node.getReference() == null ? null : visitReference(node.getReference(), body), + endBrace() + ); + } + + private String sourceBeforeAsString(String delim) { + if (cursor >= source.length()) { + return ""; + } + + int endIndex = source.indexOf(delim, cursor); + if (endIndex < 0) { + throw new IllegalStateException("Expected to be able to find " + delim); + } + String prefix = source.substring(cursor, endIndex); + cursor = endIndex + delim.length(); + return prefix; + } + + private List<Javadoc> sourceBefore(String delim) { + if (cursor >= source.length()) { + return emptyList(); + } + + int endIndex = source.indexOf(delim, cursor); + if (endIndex < 0) { + throw new IllegalStateException("Expected to be able to find " + delim); + } + List<Javadoc> before = whitespaceBefore(); + cursor += delim.length(); + return before; + } + + private String whitespaceBeforeAsString() { + if (cursor >= source.length()) { + return ""; + } + + int i = cursor; + for (; i < source.length(); i++) { + if (!Character.isWhitespace(source.charAt(i))) { + break; + } + } + String fmt = source.substring(cursor, i); + cursor = i; + return fmt; + } + + private List<Javadoc> whitespaceBefore() { + if (cursor >= source.length()) { + return emptyList(); + } + + List<Javadoc> whitespace = new ArrayList<>(); + + Javadoc.LineBreak lineBreak; + while ((lineBreak = lineBreaks.remove(cursor + 1)) != null) { + cursor++; + whitespace.add(lineBreak); + } + + StringBuilder space = new StringBuilder(); + for (; cursor < source.length() && Character.isWhitespace(source.charAt(cursor)); cursor++) { + char c = source.charAt(cursor); + if (c == '\n') { + if (space.length() > 0) { + whitespace.add(new Javadoc.Text(randomId(), Markers.EMPTY, space.toString())); + } + space = new StringBuilder(); + + lineBreak = lineBreaks.remove(cursor + 1); + assert lineBreak != null; + whitespace.add(lineBreak); + } else { + space.append(c); + } + } + + if (space.length() > 0) { + whitespace.add(new Javadoc.Text(randomId(), Markers.EMPTY, space.toString())); + } + + return whitespace; + } + + private List<Javadoc> endBrace() { + if (cursor < source.length()) { + int tempCursor = cursor; + List<Javadoc> end = whitespaceBefore(); + if (cursor < source.length() && source.charAt(cursor) == '}') { + end = ListUtils.concat(end, new Javadoc.Text(randomId(), Markers.EMPTY, "}")); + cursor++; + return end; + } else { + cursor = tempCursor; + } + } + return emptyList(); + } + + private List<Javadoc> convertMultiline(List<? extends DocTree> dts) { + List<Javadoc> js = new ArrayList<>(dts.size()); + Javadoc.LineBreak lineBreak; + while ((lineBreak = lineBreaks.remove(cursor + 1)) != null) { + cursor++; + js.add(lineBreak); + } + + for (int i = 0; i < dts.size(); i++) { + DocTree dt = dts.get(i); + if (i > 0 && dt instanceof DCTree.DCText) { + // the whitespace is part of the text + js.addAll(visitText(((DCTree.DCText) dt).getBody())); + } else { + while ((lineBreak = lineBreaks.remove(cursor + 1)) != null) { + cursor++; + js.add(lineBreak); + } + + js.addAll(whitespaceBefore()); + if (dt instanceof DCTree.DCText) { + js.addAll(visitText(((DCTree.DCText) dt).getBody())); + } else { + js.add((Javadoc) scan(dt, emptyList())); + } + } + } + return js; + } + + /** + * A {@link J} may contain new lines in each {@link Space} and each new line will have a corresponding + * {@link org.openrewrite.java.tree.Javadoc.LineBreak}. + * <p> + * This method collects the linebreaks associated to new lines in a Space, and removes the applicable linebreaks + * from the map. + */ + private List<Javadoc> lineBreaksInMultilineJReference() { + @SuppressWarnings("SimplifyStreamApiCallChains") + List<Integer> linebreakIndexes = lineBreaks.keySet().stream() + .filter(o -> o <= cursor) + .collect(Collectors.toList()); + + List<Javadoc> referenceLineBreaks = linebreakIndexes.stream() + .sorted() + .map(lineBreaks::get) + .collect(Collectors.toList()); + + for (Integer key : linebreakIndexes) { + lineBreaks.remove(key); + } + return referenceLineBreaks; + } + + class JavaVisitor extends TreeScanner<J, Space> { + + @Override + public J visitMemberSelect(MemberSelectTree node, Space fmt) { + JCTree.JCFieldAccess fieldAccess = (JCTree.JCFieldAccess) node; + Expression selected = (Expression) scan(fieldAccess.selected, Space.EMPTY); + sourceBefore("."); + cursor += fieldAccess.name.toString().length(); + return new J.FieldAccess(randomId(), fmt, Markers.EMPTY, + selected, + JLeftPadded.build(new J.Identifier(randomId(), + Space.EMPTY, + Markers.EMPTY, + emptyList(), + fieldAccess.name.toString(), null, null)), + typeMapping.type(node)); + } + + @Override + public J visitIdentifier(IdentifierTree node, Space fmt) { + String name = node.getName().toString(); + cursor += name.length(); + JavaType type = typeMapping.type(node); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, type, null); + } + + @Override + public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { + JCTree.JCPrimitiveTypeTree primitiveType = (JCTree.JCPrimitiveTypeTree) node; + String name = primitiveType.toString(); + cursor += name.length(); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, typeMapping.primitive(primitiveType.typetag), null); + } + + @Override + public J visitArrayType(ArrayTypeTree node, Space fmt) { + com.sun.source.tree.Tree typeIdent = node.getType(); + int dimCount = 1; + + while (typeIdent instanceof ArrayTypeTree) { + dimCount++; + typeIdent = ((ArrayTypeTree) typeIdent).getType(); + } + + TypeTree elemType = (TypeTree) scan(typeIdent, fmt); + + List<JRightPadded<Space>> dimensions = emptyList(); + if (dimCount > 0) { + dimensions = new ArrayList<>(dimCount); + for (int n = 0; n < dimCount; n++) { + if (!source.substring(cursor).startsWith("...")) { + dimensions.add(padRight( + Space.build(sourceBeforeAsString("["), emptyList()), + Space.build(sourceBeforeAsString("]"), emptyList()))); + } + } + } + + return new J.ArrayType( + randomId(), + Space.EMPTY, + Markers.EMPTY, + elemType, + dimensions + ); + } + + private <T> JRightPadded<T> padRight(T tree, Space right) { + return new JRightPadded<>(tree, right, Markers.EMPTY); + } + + @Override + public J visitParameterizedType(ParameterizedTypeTree node, Space fmt) { + NameTree id = (NameTree) javaVisitor.scan(node.getType(), Space.EMPTY); + List<JRightPadded<Expression>> expressions = new ArrayList<>(node.getTypeArguments().size()); + cursor += 1; // skip '<', JavaDocVisitor does not interpret List <Integer> as Parameterized. + int argsSize = node.getTypeArguments().size(); + for (int i = 0; i < argsSize; i++) { + Space space = Space.build(whitespaceBeforeAsString(), emptyList()); + JRightPadded<Expression> expression = JRightPadded.build((Expression) javaVisitor.scan(node.getTypeArguments().get(i), space)); + Space after; + if (i == argsSize - 1) { + after = Space.build(sourceBeforeAsString(">"), emptyList()); + } else { + after = Space.build(sourceBeforeAsString(","), emptyList()); + } + expression = expression.withAfter(after); + expressions.add(expression); + } + return new J.ParameterizedType(randomId(), fmt, Markers.EMPTY, id, JContainer.build(expressions), typeMapping.type(node)); + } + } +} diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ModifierResults.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ModifierResults.java new file mode 100644 index 00000000000..0c787473e80 --- /dev/null +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ModifierResults.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.isolated; + +import org.openrewrite.java.tree.J; + +import java.util.List; +public final class ReloadableJava21ModifierResults { + + private final List<J.Annotation> leadingAnnotations; + private final List<J.Modifier> modifiers; + + public ReloadableJava21ModifierResults(List<J.Annotation> leadingAnnotations, List<J.Modifier> modifiers) { + this.leadingAnnotations = leadingAnnotations; + this.modifiers = modifiers; + } + + public List<J.Annotation> getLeadingAnnotations() { + return leadingAnnotations; + } + + public List<J.Modifier> getModifiers() { + return modifiers; + } +} diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java new file mode 100644 index 00000000000..5ccba7e4a56 --- /dev/null +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21Parser.java @@ -0,0 +1,387 @@ +/* + * Copyright 2020 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.isolated; + +import com.sun.tools.javac.comp.Annotate; +import com.sun.tools.javac.comp.Check; +import com.sun.tools.javac.comp.Enter; +import com.sun.tools.javac.comp.Modules; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.main.JavaCompiler; +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 org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.SourceFile; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaParsingException; +import org.openrewrite.java.internal.JavaTypeCache; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Space; +import org.openrewrite.style.NamedStyles; +import org.openrewrite.tree.ParseError; +import org.openrewrite.tree.ParsingEventListener; +import org.openrewrite.tree.ParsingExecutionContextView; + +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardLocation; +import java.io.*; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static java.util.stream.Collectors.toList; + +/** + * This parser is NOT thread-safe, as the OpenJDK parser maintains in-memory caches in static state. + */ +@NonNullApi +public class ReloadableJava21Parser implements JavaParser { + private final JavaTypeCache typeCache; + + @Nullable + private Collection<Path> classpath; + + @Nullable + private final Collection<Input> dependsOn; + + private final JavacFileManager pfm; + private final Context context; + private final JavaCompiler compiler; + private final ResettableLog compilerLog; + private final Collection<NamedStyles> styles; + + private ReloadableJava21Parser(boolean logCompilationWarningsAndErrors, + @Nullable Collection<Path> classpath, + Collection<byte[]> classBytesClasspath, + @Nullable Collection<Input> dependsOn, + Charset charset, + Collection<NamedStyles> styles, + JavaTypeCache typeCache) { + this.classpath = classpath; + this.dependsOn = dependsOn; + this.styles = styles; + this.typeCache = typeCache; + + this.context = new Context(); + this.compilerLog = new ResettableLog(context); + this.pfm = new ByteArrayCapableJavacFileManager(context, true, charset, classBytesClasspath); + + // otherwise, consecutive string literals in binary expressions are concatenated by the parser, losing the original + // structure of the expression! + Options.instance(context).put("allowStringFolding", "false"); + Options.instance(context).put("compilePolicy", "attr"); + + // JavaCompiler line 452 (call to ImplicitSourcePolicy.decode(..)) + Options.instance(context).put("-implicit", "none"); + + // https://docs.oracle.com/en/java/javacard/3.1/guide/setting-java-compiler-options.html + Options.instance(context).put("-g", "-g"); + Options.instance(context).put("-proc", "none"); + + // MUST be created (registered with the context) after pfm and compilerLog + compiler = new JavaCompiler(context); + + // otherwise, the JavacParser will use EmptyEndPosTable, effectively setting -1 as the end position + // for every tree element + compiler.genEndPos = true; + + compiler.keepComments = true; + + // we don't need this, so as a minor performance improvement, omit these compiler features + compiler.lineDebugInfo = false; + + compilerLog.setWriters(new PrintWriter(new Writer() { + @Override + public void write(char[] cbuf, int off, int len) { + if (logCompilationWarningsAndErrors) { + String log = new String(Arrays.copyOfRange(cbuf, off, len)); + if (!log.isBlank()) { + org.slf4j.LoggerFactory.getLogger(ReloadableJava21Parser.class).warn(log); + } + } + } + + @Override + public void flush() { + } + + @Override + public void close() { + } + })); + + compileDependencies(); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Path relativeTo, ExecutionContext ctx) { + ParsingEventListener parsingListener = ParsingExecutionContextView.view(ctx).getParsingListener(); + LinkedHashMap<Input, JCTree.JCCompilationUnit> cus = parseInputsToCompilerAst(sourceFiles, ctx); + return cus.entrySet().stream().map(cuByPath -> { + Input input = cuByPath.getKey(); + parsingListener.startedParsing(input); + try { + ReloadableJava21ParserVisitor parser = new ReloadableJava21ParserVisitor( + input.getRelativePath(relativeTo), + input.getFileAttributes(), + input.getSource(ctx), + styles, + typeCache, + ctx, + context + ); + + J.CompilationUnit cu = (J.CompilationUnit) parser.scan(cuByPath.getValue(), Space.EMPTY); + cuByPath.setValue(null); // allow memory used by this JCCompilationUnit to be released + parsingListener.parsed(input, cu); + return requirePrintEqualsInput(cu, input, relativeTo, ctx); + } catch (Throwable t) { + ctx.getOnError().accept(t); + return ParseError.build(this, input, relativeTo, ctx, t); + } + }); + } + + LinkedHashMap<Input, JCTree.JCCompilationUnit> parseInputsToCompilerAst(Iterable<Input> sourceFiles, ExecutionContext ctx) { + if (classpath != null) { // override classpath + if (context.get(JavaFileManager.class) != pfm) { + throw new IllegalStateException("JavaFileManager has been forked unexpectedly"); + } + + try { + pfm.setLocationFromPaths(StandardLocation.CLASS_PATH, new ArrayList<>(classpath)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + LinkedHashMap<Input, JCTree.JCCompilationUnit> cus = new LinkedHashMap<>(); + acceptedInputs(sourceFiles).forEach(input1 -> { + try { + JCTree.JCCompilationUnit jcCompilationUnit = compiler.parse(new ReloadableJava21ParserInputFileObject(input1, ctx)); + cus.put(input1, jcCompilationUnit); + } 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 [" + + input1.getPath() + "]\n[\n" + StringUtils.readFully(input1.getSource(ctx), getCharset(ctx)) + "\n]", e); + } + throw e; + } + }); + + try { + initModules(cus.values()); + enterAll(cus.values()); + + // For some reason this is necessary in JDK 9+, where the internal block counter that + // annotationsBlocked() tests against remains >0 after attribution. + Annotate annotate = Annotate.instance(context); + while (annotate.annotationsBlocked()) { + annotate.unblockAnnotations(); // also flushes once unblocked + } + + compiler.attribute(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 best-effort in the presence of errors) + ctx.getOnError().accept(new JavaParsingException("Failed symbol entering or attribution", t)); + } + return cus; + } + + @Override + public ReloadableJava21Parser reset() { + typeCache.clear(); + compilerLog.reset(); + pfm.flush(); + Check.instance(context).newRound(); + Annotate.instance(context).newRound(); + Enter.instance(context).newRound(); + Modules.instance(context).newRound(); + compileDependencies(); + return this; + } + + @Override + public JavaParser reset(Collection<URI> uris) { + if (!uris.isEmpty()) { + compilerLog.reset(uris); + } + pfm.flush(); + Check.instance(context).newRound(); + Annotate.instance(context).newRound(); + Enter.instance(context).newRound(); + Modules.instance(context).newRound(); + return this; + } + + public void setClasspath(Collection<Path> classpath) { + this.classpath = classpath; + } + + private void compileDependencies() { + if (dependsOn != null) { + InMemoryExecutionContext ctx = new InMemoryExecutionContext(); + ctx.putMessage("org.openrewrite.java.skipSourceSetMarker", true); + parseInputs(dependsOn, null, ctx); + } + Modules.instance(context).newRound(); + } + + /** + * Initialize modules + */ + private void initModules(Collection<JCTree.JCCompilationUnit> cus) { + Modules modules = Modules.instance(context); + // Creating a new round is necessary for multiple pass parsing, where we want to keep the symbol table from a + // previous parse intact + modules.newRound(); + modules.initModules(com.sun.tools.javac.util.List.from(cus)); + } + + /** + * Enter symbol definitions into each compilation unit's scope + */ + private void enterAll(Collection<JCTree.JCCompilationUnit> cus) { + Enter enter = Enter.instance(context); + com.sun.tools.javac.util.List<JCTree.JCCompilationUnit> compilationUnits = com.sun.tools.javac.util.List.from( + cus.toArray(JCTree.JCCompilationUnit[]::new)); + enter.main(compilationUnits); + } + + private static class ResettableLog extends Log { + protected ResettableLog(Context context) { + super(context); + } + + public void reset() { + sourceMap.clear(); + } + + public void reset(Collection<URI> uris) { + sourceMap.keySet().removeIf(f -> uris.contains(f.toUri())); + } + } + + public static class Builder extends JavaParser.Builder<ReloadableJava21Parser, Builder> { + @Override + public ReloadableJava21Parser build() { + return new ReloadableJava21Parser(logCompilationWarningsAndErrors, resolvedClasspath(), classBytesClasspath, dependsOn, charset, styles, javaTypeCache); + } + } + + private static class ByteArrayCapableJavacFileManager extends JavacFileManager { + private final List<PackageAwareJavaFileObject> classByteClasspath; + + public ByteArrayCapableJavacFileManager(Context context, + boolean register, + Charset charset, + Collection<byte[]> classByteClasspath) { + super(context, register, charset); + this.classByteClasspath = classByteClasspath.stream() + .map(PackageAwareJavaFileObject::new) + .collect(toList()); + } + + @Override + public String inferBinaryName(Location location, JavaFileObject file) { + if (file instanceof PackageAwareJavaFileObject) { + return ((PackageAwareJavaFileObject) file).getClassName(); + } + return super.inferBinaryName(location, file); + } + + @Override + public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException { + if (StandardLocation.CLASS_PATH.equals(location)) { + Iterable<JavaFileObject> listed = super.list(location, packageName, kinds, recurse); + return classByteClasspath.isEmpty() ? listed + : Stream.concat(classByteClasspath.stream() + .filter(jfo -> jfo.getPackage().equals(packageName)), + StreamSupport.stream(listed.spliterator(), false) + ).collect(toList()); + } + return super.list(location, packageName, kinds, recurse); + } + } + + private static class PackageAwareJavaFileObject extends SimpleJavaFileObject { + private final String pkg; + private final String className; + private final byte[] classBytes; + + private PackageAwareJavaFileObject(byte[] classBytes) { + super(URI.create("file:///.byteArray"), Kind.CLASS); + + AtomicReference<String> pkgRef = new AtomicReference<>(); + AtomicReference<String> nameRef = new AtomicReference<>(); + + ClassReader classReader = new ClassReader(classBytes); + classReader.accept(new ClassVisitor(Opcodes.ASM9) { + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if (name.contains("/")) { + pkgRef.set(name.substring(0, name.lastIndexOf('/')) + .replace('/', '.')); + nameRef.set(name.substring(name.lastIndexOf('/') + 1)); + } else { + pkgRef.set(""); + nameRef.set(name); + } + } + }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); + + this.pkg = pkgRef.get(); + this.className = nameRef.get(); + this.classBytes = classBytes; + } + + public String getPackage() { + return pkg; + } + + public String getClassName() { + return className; + } + + @Override + public InputStream openInputStream() { + return new ByteArrayInputStream(classBytes); + } + } +} diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserInputFileObject.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserInputFileObject.java new file mode 100644 index 00000000000..3eb5fea422a --- /dev/null +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserInputFileObject.java @@ -0,0 +1,127 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.isolated; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Parser; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaParser; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.tools.JavaFileObject; +import java.io.*; +import java.net.URI; +import java.nio.file.Path; + +/** + * So that {@link JavaParser} can ingest source files from {@link InputStream} sources + * other than a file on disk. + */ +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class ReloadableJava21ParserInputFileObject implements JavaFileObject { + @EqualsAndHashCode.Include + @Nullable + private final Path path; + + @Getter + private final Parser.Input input; + + private final ExecutionContext ctx; + + public ReloadableJava21ParserInputFileObject(Parser.Input input, ExecutionContext ctx) { + this.input = input; + this.path = input.getPath(); + this.ctx = ctx; + } + + @Override + public URI toUri() { + if (path == null) { + //noinspection ConstantConditions + return null; + } + return path.toUri(); + } + + @Override + public String getName() { + if (path == null) { + //noinspection ConstantConditions + return null; + } + return path.toString(); + } + + @Override + public InputStream openInputStream() { + return input.getSource(ctx); + } + + @Override + public OutputStream openOutputStream() { + throw new UnsupportedOperationException("Should be no need to write output to this file"); + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) { + return new InputStreamReader(input.getSource(ctx)); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return input.getSource(ctx).readFully(); + } + + @Override + public Writer openWriter() { + throw new UnsupportedOperationException("Should be no need to write output to this file"); + } + + @Override + public long getLastModified() { + return 0; + } + + @Override + public boolean delete() { + return false; + } + + @Override + public Kind getKind() { + return Kind.SOURCE; + } + + @Override + public boolean isNameCompatible(String simpleName, Kind kind) { + String baseName = simpleName + kind.extension; + return kind.equals(getKind()) + && path.getFileName().toString().equals(baseName); + } + + @Override + public NestingKind getNestingKind() { + return NestingKind.TOP_LEVEL; + } + + @Override + public Modifier getAccessLevel() { + return Modifier.PUBLIC; + } +} 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 new file mode 100644 index 00000000000..89957d024e3 --- /dev/null +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java @@ -0,0 +1,2082 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.isolated; + + +import com.sun.source.tree.*; +import com.sun.source.util.TreePathScanner; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.tree.DCTree; +import com.sun.tools.javac.tree.DocCommentTable; +import com.sun.tools.javac.tree.EndPosTable; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.util.Context; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.FileAttributes; +import org.openrewrite.internal.EncodingDetectingInputStream; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaParsingException; +import org.openrewrite.java.internal.JavaTypeCache; +import org.openrewrite.java.marker.CompactConstructor; +import org.openrewrite.java.marker.OmitParentheses; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; +import org.openrewrite.style.NamedStyles; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.Math.max; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static java.util.stream.StreamSupport.stream; +import static org.openrewrite.Tree.randomId; +import static org.openrewrite.internal.StringUtils.indexOfNextNonWhitespace; +import static org.openrewrite.java.tree.Space.EMPTY; +import static org.openrewrite.java.tree.Space.format; + +/** + * Maps the compiler internal AST to the Rewrite {@link J} AST. + * <p> + * This visitor is not thread safe, as it maintains a {@link #cursor} and {@link #endPosTable} + * for each compilation unit visited. + */ +public class ReloadableJava21ParserVisitor extends TreePathScanner<J, Space> { + private final static int SURR_FIRST = 0xD800; + private final static int SURR_LAST = 0xDFFF; + private static final Map<String, Modifier> MODIFIER_BY_KEYWORD = + Stream.of(Modifier.values()).collect(Collectors.toUnmodifiableMap(Modifier::toString, Function.identity())); + + + private final Path sourcePath; + + @Nullable + private final FileAttributes fileAttributes; + private final String source; + private final Charset charset; + private final boolean charsetBomMarked; + private final Collection<NamedStyles> styles; + private final ExecutionContext ctx; + private final Context context; + private final ReloadableJava21TypeMapping typeMapping; + + @SuppressWarnings("NotNullFieldNotInitialized") + private EndPosTable endPosTable; + + @SuppressWarnings("NotNullFieldNotInitialized") + private DocCommentTable docCommentTable; + + private int cursor = 0; + + private static final Pattern whitespaceSuffixPattern = Pattern.compile("\\s*[^\\s]+(\\s*)"); + + public ReloadableJava21ParserVisitor(Path sourcePath, + @Nullable FileAttributes fileAttributes, + EncodingDetectingInputStream source, + Collection<NamedStyles> styles, + JavaTypeCache typeCache, + ExecutionContext ctx, + Context context) { + this.sourcePath = sourcePath; + this.fileAttributes = fileAttributes; + this.source = source.readFully(); + this.charset = source.getCharset(); + this.charsetBomMarked = source.isCharsetBomMarked(); + this.styles = styles; + this.ctx = ctx; + this.context = context; + this.typeMapping = new ReloadableJava21TypeMapping(typeCache); + } + + @Override + public J visitAnnotation(AnnotationTree node, Space fmt) { + skip("@"); + NameTree name = convert(node.getAnnotationType()); + + JContainer<Expression> args = null; + if (node.getArguments().size() > 0) { + Space argsPrefix = sourceBefore("("); + List<JRightPadded<Expression>> expressions; + if (node.getArguments().size() == 1) { + ExpressionTree arg = node.getArguments().get(0); + if (arg instanceof JCAssign) { + if (endPos(arg) < 0) { + expressions = singletonList(convert(((JCAssign) arg).rhs, t -> sourceBefore(")"))); + } else { + expressions = singletonList(convert(arg, t -> sourceBefore(")"))); + } + } else { + expressions = singletonList(convert(arg, t -> sourceBefore(")"))); + } + } else { + expressions = convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")")); + } + + args = JContainer.build(argsPrefix, expressions, Markers.EMPTY); + } else { + String remaining = source.substring(cursor, endPos(node)); + + // TODO: technically, if there is code like this, we have a bug, but seems exceedingly unlikely: + // @MyAnnotation /* Comment () that contains parentheses */ () + + if (remaining.contains("(") && remaining.contains(")")) { + args = JContainer.build( + sourceBefore("("), + singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)), + Markers.EMPTY + ); + } + } + + return new J.Annotation(randomId(), fmt, Markers.EMPTY, name, args); + } + + @Override + public J visitArrayAccess(ArrayAccessTree node, Space fmt) { + return new J.ArrayAccess( + randomId(), + fmt, + Markers.EMPTY, + convert(node.getExpression()), + new J.ArrayDimension(randomId(), sourceBefore("["), Markers.EMPTY, + convert(node.getIndex(), t -> sourceBefore("]"))), + typeMapping.type(node) + ); + } + + @Override + public J visitArrayType(ArrayTypeTree node, Space fmt) { + Tree typeIdent = node.getType(); + int dimCount = 1; + + while (typeIdent instanceof ArrayTypeTree) { + dimCount++; + typeIdent = ((ArrayTypeTree) typeIdent).getType(); + } + + TypeTree elemType = convert(typeIdent); + + List<JRightPadded<Space>> dimensions = emptyList(); + if (dimCount > 0) { + dimensions = new ArrayList<>(dimCount); + for (int n = 0; n < dimCount; n++) { + dimensions.add(padRight(sourceBefore("["), sourceBefore("]"))); + } + } + + return new J.ArrayType( + randomId(), + fmt, + Markers.EMPTY, + elemType, + dimensions + ); + } + + @Override + public J visitAssert(AssertTree node, Space fmt) { + skip("assert"); + JCAssert jcAssert = (JCAssert) node; + return new J.Assert(randomId(), fmt, Markers.EMPTY, + convert(jcAssert.cond), + jcAssert.detail == null ? null : padLeft(sourceBefore(":"), convert(jcAssert.detail))); + } + + @Override + public J visitAssignment(AssignmentTree node, Space fmt) { + return new J.Assignment(randomId(), fmt, Markers.EMPTY, + convert(node.getVariable()), + padLeft(sourceBefore("="), convert(node.getExpression())), + typeMapping.type(node)); + } + + @Override + public J visitBinary(BinaryTree node, Space fmt) { + Expression left = convert(node.getLeftOperand()); + + Space opPrefix = whitespace(); + J.Binary.Type op; + switch (((JCBinary) node).getTag()) { + case PLUS: + skip("+"); + op = J.Binary.Type.Addition; + break; + case MINUS: + skip("-"); + op = J.Binary.Type.Subtraction; + break; + case DIV: + skip("/"); + op = J.Binary.Type.Division; + break; + case MUL: + skip("*"); + op = J.Binary.Type.Multiplication; + break; + case MOD: + skip("%"); + op = J.Binary.Type.Modulo; + break; + case AND: + skip("&&"); + op = J.Binary.Type.And; + break; + case OR: + skip("||"); + op = J.Binary.Type.Or; + break; + case BITAND: + skip("&"); + op = J.Binary.Type.BitAnd; + break; + case BITOR: + skip("|"); + op = J.Binary.Type.BitOr; + break; + case BITXOR: + skip("^"); + op = J.Binary.Type.BitXor; + break; + case SL: + skip("<<"); + op = J.Binary.Type.LeftShift; + break; + case SR: + skip(">>"); + op = J.Binary.Type.RightShift; + break; + case USR: + skip(">>>"); + op = J.Binary.Type.UnsignedRightShift; + break; + case LT: + skip("<"); + op = J.Binary.Type.LessThan; + break; + case GT: + skip(">"); + op = J.Binary.Type.GreaterThan; + break; + case LE: + skip("<="); + op = J.Binary.Type.LessThanOrEqual; + break; + case GE: + skip(">="); + op = J.Binary.Type.GreaterThanOrEqual; + break; + case EQ: + skip("=="); + op = J.Binary.Type.Equal; + break; + case NE: + skip("!="); + op = J.Binary.Type.NotEqual; + break; + default: + throw new IllegalArgumentException("Unexpected binary tag " + ((JCBinary) node).getTag()); + } + + return new J.Binary(randomId(), fmt, Markers.EMPTY, left, padLeft(opPrefix, op), + convert(node.getRightOperand()), typeMapping.type(node)); + } + + @Override + public J visitBlock(BlockTree node, Space fmt) { + JRightPadded<Boolean> stat; + + if ((((JCBlock) node).flags & (long) Flags.STATIC) != 0L) { + skip("static"); + stat = new JRightPadded<>(true, sourceBefore("{"), Markers.EMPTY); + } else { + skip("{"); + stat = new JRightPadded<>(false, EMPTY, Markers.EMPTY); + } + + // filter out synthetic super() invocations and the like + List<StatementTree> statementTrees = new ArrayList<>(node.getStatements().size()); + for (StatementTree s : node.getStatements()) { + if (endPos(s) > 0) { + statementTrees.add(s); + } + } + + return new J.Block(randomId(), fmt, Markers.EMPTY, + stat, + convertStatements(statementTrees), + sourceBefore("}")); + } + + @Override + public J visitBreak(BreakTree node, Space fmt) { + skip("break"); + + J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(), + sourceBefore(node.getLabel().toString()), Markers.EMPTY, + emptyList(), skip(node.getLabel().toString()), null, null); + + return new J.Break(randomId(), fmt, Markers.EMPTY, label); + } + + @Override + public J visitCase(CaseTree node, Space fmt) { + J.Case.Type type = node.getCaseKind() == CaseTree.CaseKind.RULE ? J.Case.Type.Rule : J.Case.Type.Statement; + return new J.Case( + randomId(), + fmt, + Markers.EMPTY, + type, + null, + JContainer.build( + node.getExpressions().isEmpty() ? EMPTY : sourceBefore("case"), + node.getExpressions().isEmpty() ? + List.of(JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null))) : + convertAll(node.getExpressions(), commaDelim, t -> EMPTY), + Markers.EMPTY + ), + JContainer.build( + sourceBefore(type == J.Case.Type.Rule ? "->" : ":"), + convertStatements(node.getStatements()), + Markers.EMPTY + ), + type == J.Case.Type.Rule ? + padRight(convert(node.getBody()), statementDelim(node.getBody())) : + null + ); + } + + @Override + public J visitYield(YieldTree node, Space fmt) { + boolean implicit = !source.startsWith("yield", cursor); + if (!implicit) { + skip("yield"); + } + return new J.Yield(randomId(), fmt, Markers.EMPTY, implicit, convert(node.getValue())); + } + + @Override + public J visitCatch(CatchTree node, Space fmt) { + skip("catch"); + + Space paramPrefix = sourceBefore("("); + J.VariableDeclarations paramDecl = convert(node.getParameter()); + + J.ControlParentheses<J.VariableDeclarations> param = new J.ControlParentheses<>(randomId(), paramPrefix, + Markers.EMPTY, padRight(paramDecl, sourceBefore(")"))); + + return new J.Try.Catch(randomId(), fmt, Markers.EMPTY, param, convert(node.getBlock())); + } + + @Override + public J visitClass(ClassTree node, Space fmt) { + Map<Integer, JCAnnotation> annotationPosTable = new HashMap<>(); + for (AnnotationTree annotationNode : node.getModifiers().getAnnotations()) { + JCAnnotation annotation = (JCAnnotation) annotationNode; + annotationPosTable.put(annotation.pos, annotation); + } + + ReloadableJava21ModifierResults modifierResults = sortedModifiersAndAnnotations(node.getModifiers(), annotationPosTable); + + List<J.Annotation> kindAnnotations = collectAnnotations(annotationPosTable); + + J.ClassDeclaration.Kind kind; + if (hasFlag(node.getModifiers(), Flags.ENUM)) { + kind = new J.ClassDeclaration.Kind(randomId(), sourceBefore("enum"), Markers.EMPTY, kindAnnotations, J.ClassDeclaration.Kind.Type.Enum); + } else if (hasFlag(node.getModifiers(), Flags.ANNOTATION)) { + // note that annotations ALSO have the INTERFACE flag + kind = new J.ClassDeclaration.Kind(randomId(), sourceBefore("@interface"), Markers.EMPTY, kindAnnotations, J.ClassDeclaration.Kind.Type.Annotation); + } else if (hasFlag(node.getModifiers(), Flags.INTERFACE)) { + kind = new J.ClassDeclaration.Kind(randomId(), sourceBefore("interface"), Markers.EMPTY, kindAnnotations, J.ClassDeclaration.Kind.Type.Interface); + } else if (hasFlag(node.getModifiers(), Flags.RECORD)) { + kind = new J.ClassDeclaration.Kind(randomId(), sourceBefore("record"), Markers.EMPTY, kindAnnotations, J.ClassDeclaration.Kind.Type.Record); + } else { + kind = new J.ClassDeclaration.Kind(randomId(), sourceBefore("class"), Markers.EMPTY, kindAnnotations, J.ClassDeclaration.Kind.Type.Class); + } + + J.Identifier name = new J.Identifier(randomId(), sourceBefore(node.getSimpleName().toString()), + Markers.EMPTY, emptyList(), ((JCClassDecl) node).getSimpleName().toString(), typeMapping.type(node), null); + + JContainer<J.TypeParameter> typeParams = node.getTypeParameters().isEmpty() ? null : JContainer.build( + sourceBefore("<"), + convertAll(node.getTypeParameters(), commaDelim, t -> sourceBefore(">")), + Markers.EMPTY); + + JContainer<Statement> primaryConstructor = null; + if (kind.getType() == J.ClassDeclaration.Kind.Type.Record) { + List<Tree> stateVector = new ArrayList<>(); + for (Tree member : node.getMembers()) { + if (member instanceof VariableTree vt) { + if (hasFlag(vt.getModifiers(), Flags.RECORD)) { + stateVector.add(vt); + } + } + } + primaryConstructor = JContainer.build( + sourceBefore("("), + stateVector.isEmpty() ? + singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) : + convertAll(stateVector, commaDelim, t -> sourceBefore(")")), + Markers.EMPTY + ); + } + + JLeftPadded<TypeTree> extendings = node.getExtendsClause() == null ? null : + padLeft(sourceBefore("extends"), convertOrNull(node.getExtendsClause())); + + JContainer<TypeTree> implementings = null; + if (node.getImplementsClause() != null && !node.getImplementsClause().isEmpty()) { + Space implementsPrefix = sourceBefore(kind.getType() == J.ClassDeclaration.Kind.Type.Interface ? + "extends" : "implements"); + + implementings = JContainer.build( + implementsPrefix, + convertAll(node.getImplementsClause(), commaDelim, noDelim), + Markers.EMPTY + ); + } + + JContainer<TypeTree> permitting = null; + if (node.getPermitsClause() != null && !node.getPermitsClause().isEmpty()) { + permitting = JContainer.build( + sourceBefore("permits"), + convertAll(node.getPermitsClause(), commaDelim, noDelim), + Markers.EMPTY + ); + } + + Space bodyPrefix = sourceBefore("{"); + + // enum values are required by the grammar to occur before any ordinary field, constructor, or method members + List<Tree> jcEnums = new ArrayList<>(node.getMembers().size()); + for (Tree tree : node.getMembers()) { + if (tree instanceof JCVariableDecl) { + if (hasFlag(((JCVariableDecl) tree).getModifiers(), Flags.ENUM)) { + jcEnums.add(tree); + } + } + } + + JRightPadded<Statement> enumSet = null; + if (!jcEnums.isEmpty()) { + AtomicBoolean semicolonPresent = new AtomicBoolean(false); + + List<JRightPadded<J.EnumValue>> enumValues = convertAll(jcEnums, commaDelim, t -> { + // this semicolon is required when there are non-value members, but can still + // be present when there are not + semicolonPresent.set(positionOfNext(";", '}') > 0); + return semicolonPresent.get() ? sourceBefore(";", '}') : EMPTY; + }); + + enumSet = padRight( + new J.EnumValueSet( + randomId(), + EMPTY, + Markers.EMPTY, + enumValues, + semicolonPresent.get() + ), + EMPTY + ); + } + + List<Tree> membersMultiVariablesSeparated = new ArrayList<>(node.getMembers().size()); + for (Tree m : node.getMembers()) { + // we don't care about the compiler-inserted default constructor, + // since it will never be subject to refactoring + if (m instanceof JCMethodDecl md && ( + hasFlag(md.getModifiers(), Flags.GENERATEDCONSTR) || + hasFlag(md.getModifiers(), Flags.RECORD))) { + continue; + } + if (m instanceof JCVariableDecl vt && + (hasFlag(vt.getModifiers(), Flags.ENUM) || + hasFlag(vt.getModifiers(), Flags.RECORD))) { + continue; + } + membersMultiVariablesSeparated.add(m); + } + + List<JRightPadded<Statement>> members = new ArrayList<>( + membersMultiVariablesSeparated.size() + (enumSet == null ? 0 : 1)); + if (enumSet != null) { + members.add(enumSet); + } + members.addAll(convertStatements(membersMultiVariablesSeparated)); + + J.Block body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), + members, sourceBefore("}")); + + return new J.ClassDeclaration(randomId(), fmt, Markers.EMPTY, modifierResults.getLeadingAnnotations(), modifierResults.getModifiers(), kind, name, typeParams, + primaryConstructor, extendings, implementings, permitting, body, (JavaType.FullyQualified) typeMapping.type(node)); + } + + @Override + public J visitCompilationUnit(CompilationUnitTree node, Space fmt) { + JCCompilationUnit cu = (JCCompilationUnit) node; + + if (node.getTypeDecls().isEmpty() || cu.getPackageName() != null || !node.getImports().isEmpty()) { + // if the package and imports are empty, allow the formatting to apply to the first class declaration. + // in this way, javadoc comments are interpreted as javadocs on that class declaration. + fmt = format(source, 0, cu.getStartPosition()); + cursor(cu.getStartPosition()); + } + + endPosTable = cu.endPositions; + docCommentTable = cu.docComments; + + Map<Integer, JCAnnotation> annotationPosTable = new HashMap<>(); + for (AnnotationTree annotationNode : node.getPackageAnnotations()) { + JCAnnotation annotation = (JCAnnotation) annotationNode; + annotationPosTable.put(annotation.pos, annotation); + } + List<J.Annotation> packageAnnotations = collectAnnotations(annotationPosTable); + + J.Package packageDecl = null; + if (cu.getPackageName() != null) { + Space packagePrefix = sourceBefore("package"); + packageDecl = new J.Package(randomId(), packagePrefix, Markers.EMPTY, + convert(cu.getPackageName()), packageAnnotations); + } + return new J.CompilationUnit( + randomId(), + fmt, + Markers.build(styles), + sourcePath, + fileAttributes, + charset.name(), + charsetBomMarked, + null, + packageDecl == null ? null : padRight(packageDecl, sourceBefore(";")), + convertAll(node.getImports(), this::statementDelim, this::statementDelim), + convertAll(node.getTypeDecls().stream().filter(JCClassDecl.class::isInstance).collect(toList())), + format(source, cursor, source.length()) + ); + } + + @Override + public J visitCompoundAssignment(CompoundAssignmentTree node, Space fmt) { + Expression left = convert(((JCAssignOp) node).lhs); + + Space opPrefix = whitespace(); + J.AssignmentOperation.Type op; + switch (((JCAssignOp) node).getTag()) { + case PLUS_ASG: + skip("+="); + op = J.AssignmentOperation.Type.Addition; + break; + case MINUS_ASG: + skip("-="); + op = J.AssignmentOperation.Type.Subtraction; + break; + case DIV_ASG: + skip("/="); + op = J.AssignmentOperation.Type.Division; + break; + case MUL_ASG: + skip("*="); + op = J.AssignmentOperation.Type.Multiplication; + break; + case MOD_ASG: + skip("%="); + op = J.AssignmentOperation.Type.Modulo; + break; + case BITAND_ASG: + skip("&="); + op = J.AssignmentOperation.Type.BitAnd; + break; + case BITOR_ASG: + skip("|="); + op = J.AssignmentOperation.Type.BitOr; + break; + case BITXOR_ASG: + skip("^="); + op = J.AssignmentOperation.Type.BitXor; + break; + case SL_ASG: + skip("<<="); + op = J.AssignmentOperation.Type.LeftShift; + break; + case SR_ASG: + skip(">>="); + op = J.AssignmentOperation.Type.RightShift; + break; + case USR_ASG: + skip(">>>="); + op = J.AssignmentOperation.Type.UnsignedRightShift; + break; + default: + throw new IllegalArgumentException("Unexpected compound assignment tag " + ((JCAssignOp) node).getTag()); + } + + return new J.AssignmentOperation(randomId(), fmt, Markers.EMPTY, left, + padLeft(opPrefix, op), convert(((JCAssignOp) node).rhs), typeMapping.type(node)); + } + + @Override + public J visitConditionalExpression(ConditionalExpressionTree node, Space fmt) { + return new J.Ternary(randomId(), fmt, Markers.EMPTY, + convert(node.getCondition()), + padLeft(sourceBefore("?"), convert(node.getTrueExpression())), + padLeft(sourceBefore(":"), convert(node.getFalseExpression())), + typeMapping.type(node)); + } + + @Override + public J visitContinue(ContinueTree node, Space fmt) { + skip("continue"); + Name label = node.getLabel(); + return new J.Continue(randomId(), fmt, Markers.EMPTY, + label == null ? null : new J.Identifier(randomId(), sourceBefore(label.toString()), + Markers.EMPTY, emptyList(), label.toString(), null, null)); + } + + @Override + public J visitDoWhileLoop(DoWhileLoopTree node, Space fmt) { + skip("do"); + return new J.DoWhileLoop(randomId(), fmt, Markers.EMPTY, + convert(node.getStatement(), this::statementDelim), + padLeft(sourceBefore("while"), convert(node.getCondition()))); + } + + @Override + public J visitEmptyStatement(EmptyStatementTree node, Space fmt) { + return new J.Empty(randomId(), fmt, Markers.EMPTY); + } + + @Override + public J visitEnhancedForLoop(EnhancedForLoopTree node, Space fmt) { + skip("for"); + return new J.ForEachLoop(randomId(), fmt, Markers.EMPTY, + new J.ForEachLoop.Control(randomId(), sourceBefore("("), Markers.EMPTY, + convert(node.getVariable(), t -> sourceBefore(":")), + convert(node.getExpression(), t -> sourceBefore(")"))), + convert(node.getStatement(), this::statementDelim)); + } + + private J visitEnumVariable(VariableTree node, Space fmt) { + List<J.Annotation> annotations = emptyList(); + Space nameSpace = EMPTY; + + if (!node.getModifiers().getAnnotations().isEmpty()) { + annotations = convertAll(node.getModifiers().getAnnotations()); + nameSpace = sourceBefore(node.getName().toString()); + } else { + skip(node.getName().toString()); + } + + J.Identifier name = new J.Identifier(randomId(), nameSpace, Markers.EMPTY, emptyList(), node.getName().toString(), typeMapping.type(node), null); + + J.NewClass initializer = null; + if (source.charAt(endPos(node) - 1) == ')' || source.charAt(endPos(node) - 1) == '}') { + initializer = convert(node.getInitializer()); + } + + return new J.EnumValue(randomId(), fmt, Markers.EMPTY, annotations, name, initializer); + } + + @Override + public J visitForLoop(ForLoopTree node, Space fmt) { + skip("for"); + Space ctrlPrefix = sourceBefore("("); + + List<JRightPadded<Statement>> init = node.getInitializer().isEmpty() ? + singletonList(padRight(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), EMPTY)) : + convertStatements(node.getInitializer(), t -> + positionOfNext(",", ';') == -1 ? + semiDelim.apply(t) : + commaDelim.apply(t) + ); + + JRightPadded<Expression> condition = convertOrNull(node.getCondition(), semiDelim); + if (condition == null) { + condition = padRight(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), EMPTY); + } + + List<JRightPadded<Statement>> update; + if (node.getUpdate().isEmpty()) { + update = singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)); + } else { + List<? extends ExpressionStatementTree> nodeUpdate = node.getUpdate(); + update = new ArrayList<>(nodeUpdate.size()); + for (int i = 0; i < nodeUpdate.size(); i++) { + ExpressionStatementTree tree = nodeUpdate.get(i); + update.add(convert(tree, i == nodeUpdate.size() - 1 ? t -> sourceBefore(")") : commaDelim)); + } + } + + return new J.ForLoop(randomId(), fmt, Markers.EMPTY, + new J.ForLoop.Control(randomId(), ctrlPrefix, Markers.EMPTY, init, condition, update), + convert(node.getStatement(), this::statementDelim)); + } + + @Override + public J visitIdentifier(IdentifierTree node, Space fmt) { + String name = node.getName().toString(); + cursor += name.length(); + + JCIdent ident = (JCIdent) node; + JavaType type = typeMapping.type(node); + return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, type, typeMapping.variableType(ident.sym)); + } + + @Override + public J visitIf(IfTree node, Space fmt) { + skip("if"); + return new J.If(randomId(), fmt, Markers.EMPTY, + convert(node.getCondition()), + convert(node.getThenStatement(), this::statementDelim), + node.getElseStatement() instanceof JCTree.JCStatement ? + new J.If.Else(randomId(), sourceBefore("else"), Markers.EMPTY, convert(node.getElseStatement(), this::statementDelim)) : + null); + } + + @Override + public J visitImport(ImportTree node, Space fmt) { + skip("import"); + return new J.Import(randomId(), fmt, Markers.EMPTY, + new JLeftPadded<>(node.isStatic() ? sourceBefore("static") : EMPTY, + node.isStatic(), Markers.EMPTY), + convert(node.getQualifiedIdentifier()), null); + } + + @Override + public J visitInstanceOf(InstanceOfTree node, Space fmt) { + JavaType type = typeMapping.type(node); + return new J.InstanceOf(randomId(), fmt, Markers.EMPTY, + convert(node.getExpression(), t -> sourceBefore("instanceof")), + convert(node.getType()), + node.getPattern() instanceof JCBindingPattern b ? + new J.Identifier(randomId(), sourceBefore(b.getVariable().getName().toString()), Markers.EMPTY, emptyList(), b.getVariable().getName().toString(), + type, typeMapping.variableType(b.var.sym)) : null, + type); + } + + @Override + public J visitLabeledStatement(LabeledStatementTree node, Space fmt) { + skip(node.getLabel().toString()); + return new J.Label(randomId(), fmt, Markers.EMPTY, + padRight(new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), node.getLabel().toString(), null, null), sourceBefore(":")), + convert(node.getStatement())); + } + + @Override + public J visitLambdaExpression(LambdaExpressionTree node, Space fmt) { + boolean parenthesized = source.charAt(cursor) == '('; + skip("("); + + List<JRightPadded<J>> paramList; + if (parenthesized && node.getParameters().isEmpty()) { + paramList = singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)); + } else { + paramList = convertAll(node.getParameters(), commaDelim, t -> parenthesized ? sourceBefore(")") : EMPTY); + } + + J.Lambda.Parameters params = new J.Lambda.Parameters(randomId(), EMPTY, Markers.EMPTY, parenthesized, paramList); + Space arrow = sourceBefore("->"); + + J body; + if (node.getBody() instanceof JCTree.JCBlock) { + Space prefix = sourceBefore("{"); + cursor--; + body = convert(node.getBody()); + body = body.withPrefix(prefix); + } else { + body = convert(node.getBody()); + } + + return new J.Lambda(randomId(), fmt, Markers.EMPTY, params, arrow, body, typeMapping.type(node)); + } + + @Override + public J visitLiteral(LiteralTree node, Space fmt) { + cursor(endPos(node)); + Object value = node.getValue(); + String valueSource = source.substring(((JCLiteral) node).getStartPosition(), endPos(node)); + JavaType.Primitive type = typeMapping.primitive(((JCTree.JCLiteral) node).typetag); + + if (value instanceof Character) { + char c = (Character) value; + if (c >= SURR_FIRST && c <= SURR_LAST) { + return new J.Literal(randomId(), fmt, Markers.EMPTY, null, "''", + singletonList(new J.Literal.UnicodeEscape(1, + valueSource.substring(3, valueSource.length() - 1))), type); + } + } else if (JavaType.Primitive.String.equals(type)) { + StringBuilder valueSourceWithoutSurrogates = new StringBuilder(); + List<J.Literal.UnicodeEscape> unicodeEscapes = null; + + int i = 0; + for (int j = 0; j < valueSource.length(); j++) { + char c = valueSource.charAt(j); + if (c == '\\' && j < valueSource.length() - 1 && (j == 0 || valueSource.charAt(j - 1) != '\\')) { + if (valueSource.charAt(j + 1) == 'u' && j < valueSource.length() - 5) { + String codePoint = valueSource.substring(j + 2, j + 6); + int codePointNumeric = Integer.parseInt(codePoint, 16); + if (codePointNumeric >= SURR_FIRST && codePointNumeric <= SURR_LAST) { + if (unicodeEscapes == null) { + unicodeEscapes = new ArrayList<>(1); + } + unicodeEscapes.add(new J.Literal.UnicodeEscape(i, codePoint)); + j += 5; + continue; + } + } + } + valueSourceWithoutSurrogates.append(c); + i++; + } + + return new J.Literal(randomId(), fmt, Markers.EMPTY, + unicodeEscapes == null ? value : valueSourceWithoutSurrogates.toString(), + valueSourceWithoutSurrogates.toString(), unicodeEscapes, type); + } + + return new J.Literal(randomId(), fmt, Markers.EMPTY, value, valueSource, null, type); + } + + @Override + public J visitMemberReference(MemberReferenceTree node, Space fmt) { + JCMemberReference ref = (JCMemberReference) node; + + String referenceName; + switch (ref.getMode()) { + case NEW: + referenceName = "new"; + break; + case INVOKE: + default: + referenceName = node.getName().toString(); + break; + } + + JavaType.Method methodReferenceType = null; + if (ref.sym instanceof Symbol.MethodSymbol) { + Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) ref.sym; + methodReferenceType = typeMapping.methodInvocationType(methodSymbol.type, methodSymbol); + } + + JavaType.Variable fieldReferenceType = null; + if (ref.sym instanceof Symbol.VarSymbol) { + Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) ref.sym; + fieldReferenceType = typeMapping.variableType(varSymbol); + } + + return new J.MemberReference(randomId(), + fmt, + Markers.EMPTY, + padRight(convert(ref.expr), sourceBefore("::")), + convertTypeParameters(node.getTypeArguments()), + padLeft(whitespace(), new J.Identifier(randomId(), + sourceBefore(referenceName), + Markers.EMPTY, + emptyList(), + referenceName, + null, null)), + typeMapping.type(node), + methodReferenceType, + fieldReferenceType + ); + } + + @Override + public J visitMemberSelect(MemberSelectTree node, Space fmt) { + JCFieldAccess fieldAccess = (JCFieldAccess) node; + JavaType type = typeMapping.type(node); + return new J.FieldAccess(randomId(), fmt, Markers.EMPTY, + convert(fieldAccess.selected), + padLeft(sourceBefore("."), new J.Identifier(randomId(), + sourceBefore(fieldAccess.name.toString()), Markers.EMPTY, + emptyList(), fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), + type); + } + + @Override + public J visitMethodInvocation(MethodInvocationTree node, Space fmt) { + JCExpression jcSelect = ((JCTree.JCMethodInvocation) node).getMethodSelect(); + + JRightPadded<Expression> select = null; + if (jcSelect instanceof JCFieldAccess) { + select = convert(((JCFieldAccess) jcSelect).selected, t -> sourceBefore(".")); + } else if (!(jcSelect instanceof JCIdent)) { + throw new IllegalStateException("Unexpected method select type " + jcSelect.getClass().getSimpleName()); + } + + // generic type parameters can only exist on qualified targets + JContainer<Expression> typeParams = null; + if (!node.getTypeArguments().isEmpty()) { + typeParams = JContainer.build(sourceBefore("<"), convertAll(node.getTypeArguments(), commaDelim, + t -> sourceBefore(">")), Markers.EMPTY); + } + + J.Identifier name; + if (jcSelect instanceof JCFieldAccess) { + String selectName = ((JCFieldAccess) jcSelect).name.toString(); + name = new J.Identifier(randomId(), sourceBefore(selectName), Markers.EMPTY, emptyList(), selectName, null, null); + } else { + name = convert(jcSelect); + } + + JContainer<Expression> args = JContainer.build(sourceBefore("("), node.getArguments().isEmpty() ? + singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) : + convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")")), Markers.EMPTY); + + Symbol methodSymbol = (jcSelect instanceof JCFieldAccess) ? ((JCFieldAccess) jcSelect).sym : + ((JCIdent) jcSelect).sym; + + return new J.MethodInvocation(randomId(), fmt, Markers.EMPTY, select, typeParams, name, args, + typeMapping.methodInvocationType(jcSelect.type, methodSymbol)); + } + + @Override + public J visitMethod(MethodTree node, Space fmt) { + JCMethodDecl jcMethod = (JCMethodDecl) node; + + Map<Integer, JCAnnotation> annotationPosTable = new HashMap<>(); + for (AnnotationTree annotationNode : node.getModifiers().getAnnotations()) { + JCAnnotation annotation = (JCAnnotation) annotationNode; + annotationPosTable.put(annotation.pos, annotation); + } + ReloadableJava21ModifierResults modifierResults = sortedModifiersAndAnnotations(node.getModifiers(), annotationPosTable); + + J.TypeParameters typeParams; + if (node.getTypeParameters().isEmpty()) { + typeParams = null; + } else { + List<J.Annotation> typeParamsAnnotations = collectAnnotations(annotationPosTable); + + // see https://docs.oracle.com/javase/tutorial/java/generics/methods.html + typeParams = new J.TypeParameters(randomId(), sourceBefore("<"), Markers.EMPTY, + typeParamsAnnotations, + convertAll(node.getTypeParameters(), commaDelim, t -> sourceBefore(">"))); + } + + List<J.Annotation> returnTypeAnnotations = collectAnnotations(annotationPosTable); + TypeTree returnType = convertOrNull(node.getReturnType()); + if (returnType != null && !returnTypeAnnotations.isEmpty()) { + returnType = new J.AnnotatedType(randomId(), Space.EMPTY, Markers.EMPTY, + returnTypeAnnotations, returnType); + } + + Symbol.MethodSymbol nodeSym = jcMethod.sym; + + J.MethodDeclaration.IdentifierWithAnnotations name; + if ("<init>".equals(node.getName().toString())) { + String owner = null; + if (nodeSym == null) { + for (Tree tree : getCurrentPath()) { + if (tree instanceof JCClassDecl) { + owner = ((JCClassDecl) tree).getSimpleName().toString(); + break; + } + } + + if (owner == null) { + throw new IllegalStateException("Should have been able to locate an owner"); + } + } else { + owner = jcMethod.sym.owner.name.toString(); + } + name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner), + Markers.EMPTY, emptyList(), owner, null, null), returnType == null ? returnTypeAnnotations : emptyList()); + } else { + name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(node.getName().toString(), null), Markers.EMPTY, + emptyList(), node.getName().toString(), null, null), returnType == null ? returnTypeAnnotations : emptyList()); + } + + boolean isCompactConstructor = nodeSym != null && (nodeSym.flags() & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0; + JContainer<Statement> params = JContainer.empty(); + if (!isCompactConstructor) { + Space paramFmt = sourceBefore("("); + params = !node.getParameters().isEmpty() ? + JContainer.build(paramFmt, convertAll(node.getParameters(), commaDelim, t -> sourceBefore(")")), + Markers.EMPTY) : + JContainer.build(paramFmt, singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), + Markers.EMPTY), EMPTY)), Markers.EMPTY); + } + + JContainer<NameTree> throws_ = node.getThrows().isEmpty() ? null : + JContainer.build(sourceBefore("throws"), convertAll(node.getThrows(), commaDelim, noDelim), + Markers.EMPTY); + + J.Block body = convertOrNull(node.getBody()); + + JLeftPadded<Expression> defaultValue = node.getDefaultValue() == null ? null : + padLeft(sourceBefore("default"), convert(node.getDefaultValue())); + + J.MethodDeclaration md = new J.MethodDeclaration(randomId(), fmt, Markers.EMPTY, + modifierResults.getLeadingAnnotations(), + modifierResults.getModifiers(), typeParams, + returnType, name, params, throws_, body, defaultValue, + typeMapping.methodDeclarationType(jcMethod.sym, null)); + md = isCompactConstructor ? md.withMarkers(md.getMarkers().addIfAbsent(new CompactConstructor(randomId()))) : md; + return md; + } + + @Override + public J visitNewArray(NewArrayTree node, Space fmt) { + skip("new"); + + JCExpression jcVarType = ((JCNewArray) node).elemtype; + TypeTree typeExpr; + if (jcVarType instanceof JCArrayTypeTree) { + // we'll capture the array dimensions in a bit, just convert the element type + JCExpression elementType = ((JCArrayTypeTree) jcVarType).elemtype; + while (elementType instanceof JCArrayTypeTree) { + elementType = ((JCArrayTypeTree) elementType).elemtype; + } + typeExpr = convertOrNull(elementType); + } else { + typeExpr = convertOrNull(jcVarType); + } + + List<? extends ExpressionTree> nodeDimensions = node.getDimensions(); + List<J.ArrayDimension> dimensions = new ArrayList<>(nodeDimensions.size()); + for (ExpressionTree dim : nodeDimensions) { + dimensions.add(new J.ArrayDimension( + randomId(), + sourceBefore("["), + Markers.EMPTY, + convert(dim, t -> sourceBefore("]")))); + } + + while (true) { + int beginBracket = indexOfNextNonWhitespace(cursor, source); + if (source.charAt(beginBracket) == '[') { + int endBracket = indexOfNextNonWhitespace(beginBracket + 1, source); + dimensions.add(new J.ArrayDimension( + randomId(), + format(source, cursor, beginBracket), + Markers.EMPTY, + padRight(new J.Empty(randomId(), format(source, beginBracket + 1, endBracket), Markers.EMPTY), EMPTY))); + cursor = endBracket + 1; + } else { + break; + } + } + + JContainer<Expression> initializer = node.getInitializers() == null ? null : + JContainer.build(sourceBefore("{"), node.getInitializers().isEmpty() ? + singletonList(padRight(new J.Empty(randomId(), sourceBefore("}"), Markers.EMPTY), EMPTY)) : + convertAll(node.getInitializers(), commaDelim, t -> sourceBefore("}")), Markers.EMPTY); + + return new J.NewArray(randomId(), fmt, Markers.EMPTY, typeExpr, dimensions, + initializer, typeMapping.type(node)); + } + + @Override + public J visitNewClass(NewClassTree node, Space fmt) { + JRightPadded<Expression> encl = node.getEnclosingExpression() == null ? null : + convert(node.getEnclosingExpression(), t -> sourceBefore(".")); + + Space whitespaceBeforeNew = EMPTY; + + Tree parent = getCurrentPath().getParentPath().getLeaf(); + if (!(parent instanceof JCVariableDecl && ((((JCVariableDecl) parent).mods.flags & Flags.ENUM) != 0))) { + whitespaceBeforeNew = sourceBefore("new"); + } + + // for enum definitions with anonymous class initializers, endPos of node identifier will be -1 + TypeTree clazz = endPos(node.getIdentifier()) >= 0 ? convertOrNull(node.getIdentifier()) : null; + + JContainer<Expression> args; + if (positionOfNext("(", '{') > -1) { + args = JContainer.build(sourceBefore("("), + node.getArguments().isEmpty() ? + singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) : + convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")")), Markers.EMPTY); + } else { + args = JContainer.<Expression>empty() + .withMarkers(Markers.build(singletonList(new OmitParentheses(randomId())))); + } + + J.Block body = null; + if (node.getClassBody() != null) { + Space bodyPrefix = sourceBefore("{"); + + // we don't care about the compiler-inserted default constructor, + // since it will never be subject to refactoring + List<Tree> members = new ArrayList<>(node.getClassBody().getMembers().size()); + for (Tree m : node.getClassBody().getMembers()) { + if (!(m instanceof JCMethodDecl) || (((JCMethodDecl) m).getModifiers().flags & Flags.GENERATEDCONSTR) == 0L) { + members.add(m); + } + } + + body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), + convertStatements(members), sourceBefore("}")); + } + + JCNewClass jcNewClass = (JCNewClass) node; + JavaType.Method constructorType = typeMapping.methodInvocationType(jcNewClass.constructorType, jcNewClass.constructor); + + return new J.NewClass(randomId(), fmt, Markers.EMPTY, encl, whitespaceBeforeNew, + clazz, args, body, constructorType); + } + + @Override + public J visitParameterizedType(ParameterizedTypeTree node, Space fmt) { + return new J.ParameterizedType(randomId(), fmt, Markers.EMPTY, convert(node.getType()), convertTypeParameters(node.getTypeArguments()), typeMapping.type(node)); + } + + @Override + public J visitParenthesized(ParenthesizedTree node, Space fmt) { + skip("("); + Tree parent = getCurrentPath().getParentPath().getLeaf(); + switch (parent.getKind()) { + case CATCH: + case DO_WHILE_LOOP: + case IF: + case SWITCH: + case SWITCH_EXPRESSION: + case SYNCHRONIZED: + case TYPE_CAST: + case WHILE_LOOP: + return new J.ControlParentheses<Expression>(randomId(), fmt, Markers.EMPTY, + convert(node.getExpression(), t -> sourceBefore(")"))); + default: + return new J.Parentheses<Expression>(randomId(), fmt, Markers.EMPTY, + convert(node.getExpression(), t -> sourceBefore(")"))); + } + } + + @Override + public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) { + cursor(endPos(node)); + + JavaType.Primitive primitiveType; + switch (node.getPrimitiveTypeKind()) { + case BOOLEAN: + primitiveType = JavaType.Primitive.Boolean; + break; + case BYTE: + primitiveType = JavaType.Primitive.Byte; + break; + case CHAR: + primitiveType = JavaType.Primitive.Char; + break; + case DOUBLE: + primitiveType = JavaType.Primitive.Double; + break; + case FLOAT: + primitiveType = JavaType.Primitive.Float; + break; + case INT: + primitiveType = JavaType.Primitive.Int; + break; + case LONG: + primitiveType = JavaType.Primitive.Long; + break; + case SHORT: + primitiveType = JavaType.Primitive.Short; + break; + case VOID: + primitiveType = JavaType.Primitive.Void; + break; + default: + throw new IllegalArgumentException("Unknown primitive type " + node.getPrimitiveTypeKind()); + } + + return new J.Primitive(randomId(), fmt, Markers.EMPTY, primitiveType); + } + + @Override + public J visitReturn(ReturnTree node, Space fmt) { + skip("return"); + Expression expression = convertOrNull(node.getExpression()); + return new J.Return(randomId(), fmt, Markers.EMPTY, expression); + } + + @Override + public J visitSwitch(SwitchTree node, Space fmt) { + skip("switch"); + return new J.Switch(randomId(), fmt, Markers.EMPTY, + convert(node.getExpression()), + new J.Block(randomId(), sourceBefore("{"), Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), + convertAll(node.getCases(), noDelim, noDelim), sourceBefore("}"))); + } + + @Override + public J visitSwitchExpression(SwitchExpressionTree node, Space fmt) { + skip("switch"); + return new J.SwitchExpression(randomId(), fmt, Markers.EMPTY, + convert(node.getExpression()), + new J.Block(randomId(), sourceBefore("{"), Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY), + convertAll(node.getCases(), noDelim, noDelim), sourceBefore("}"))); + } + + @Override + public J visitSynchronized(SynchronizedTree node, Space fmt) { + skip("synchronized"); + return new J.Synchronized(randomId(), fmt, Markers.EMPTY, convert(node.getExpression()), + convert(node.getBlock())); + } + + @Override + public J visitThrow(ThrowTree node, Space fmt) { + skip("throw"); + return new J.Throw(randomId(), fmt, Markers.EMPTY, convert(node.getExpression())); + } + + @Override + public J visitTry(TryTree node, Space fmt) { + skip("try"); + JContainer<J.Try.Resource> resources; + if (node.getResources().isEmpty()) { + resources = null; + } else { + Space before = sourceBefore("("); + List<JRightPadded<J.Try.Resource>> resourceList = new ArrayList<>(node.getResources().size()); + for (int i = 0; i < node.getResources().size(); i++) { + Tree resource = node.getResources().get(i); + J resourceVar = convert(resource); + boolean semicolonPresent = true; + if (i == node.getResources().size() - 1) { + semicolonPresent = positionOfNext(";", ')') > 0; + } + + Space resourcePrefix = resourceVar.getPrefix(); + resourceVar = resourceVar.withPrefix(EMPTY); // moved to the containing Try.Resource + + if (semicolonPresent && resourceVar instanceof J.VariableDeclarations) { + J.VariableDeclarations resourceVarDecl = (J.VariableDeclarations) resourceVar; + resourceVar = resourceVarDecl.getPadding().withVariables(Space.formatLastSuffix(resourceVarDecl + .getPadding().getVariables(), sourceBefore(";"))); + } + + J.Try.Resource tryResource = new J.Try.Resource(randomId(), resourcePrefix, Markers.EMPTY, + resourceVar.withPrefix(EMPTY), semicolonPresent); + + // Starting in Java 9, you can have an identifier in the try with resource, if an Identifier + // is parsed, the cursor is advance to the trailing semicolon. We do not want to pick this up in the + // prefix of the next resource (or the right padding of identifier (if it is the last resource) + skip(";"); + + resourceList.add(padRight(tryResource, i == node.getResources().size() - 1 ? + sourceBefore(")") : EMPTY)); + } + + resources = JContainer.build(before, resourceList, Markers.EMPTY); + } + + J.Block block = convert(node.getBlock()); + List<J.Try.Catch> catches = convertAll(node.getCatches()); + + JLeftPadded<J.Block> finally_ = node.getFinallyBlock() == null ? null : + padLeft(sourceBefore("finally"), convert(node.getFinallyBlock())); + + return new J.Try(randomId(), fmt, Markers.EMPTY, resources, block, catches, finally_); + } + + @Override + public J visitTypeCast(TypeCastTree node, Space fmt) { + return new J.TypeCast(randomId(), fmt, Markers.EMPTY, + new J.ControlParentheses<>(randomId(), + sourceBefore("("), Markers.EMPTY, + convert(node.getType(), t -> sourceBefore(")"))), + convert(node.getExpression())); + } + + @Override + public J visitAnnotatedType(AnnotatedTypeTree node, Space fmt) { + return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, convertAll(node.getAnnotations()), + convert(node.getUnderlyingType())); + } + + @Override + public J visitTypeParameter(TypeParameterTree node, Space fmt) { + List<J.Annotation> annotations = convertAll(node.getAnnotations()); + + Expression name = buildName(node.getName().toString()) + .withPrefix(sourceBefore(node.getName().toString())); + + // see https://docs.oracle.com/javase/tutorial/java/generics/bounded.html + JContainer<TypeTree> bounds = node.getBounds().isEmpty() ? null : + JContainer.build(sourceBefore("extends"), + convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY); + + return new J.TypeParameter(randomId(), fmt, Markers.EMPTY, annotations, name, bounds); + } + + private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) { + String[] parts = fullyQualifiedName.split("\\."); + + String fullName = ""; + Expression expr = null; + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + if (i == 0) { + fullName = part; + expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), part, null, null); + } else { + fullName += "." + part; + + int endOfPrefix = indexOfNextNonWhitespace(0, part); + Space identFmt = endOfPrefix > 0 ? format(part, 0, endOfPrefix) : EMPTY; + + Matcher whitespaceSuffix = whitespaceSuffixPattern.matcher(part); + //noinspection ResultOfMethodCallIgnored + whitespaceSuffix.matches(); + Space namePrefix = i == parts.length - 1 ? Space.EMPTY : format(whitespaceSuffix.group(1)); + + expr = new J.FieldAccess( + randomId(), + EMPTY, + Markers.EMPTY, + expr, + padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, emptyList(), part.trim(), null, null)), + (Character.isUpperCase(part.charAt(0)) || i == parts.length - 1) ? + JavaType.ShallowClass.build(fullName) : + null + ); + } + } + + //noinspection unchecked,ConstantConditions + return (T) expr; + } + + @Override + public J visitUnionType(UnionTypeTree node, Space fmt) { + return new J.MultiCatch(randomId(), fmt, Markers.EMPTY, + convertAll(node.getTypeAlternatives(), t -> sourceBefore("|"), noDelim)); + } + + @Override + public J visitUnary(UnaryTree node, Space fmt) { + JCUnary unary = (JCUnary) node; + Tag tag = unary.getTag(); + JLeftPadded<J.Unary.Type> op; + Expression expr; + + switch (tag) { + case POS: + skip("+"); + op = padLeft(EMPTY, J.Unary.Type.Positive); + expr = convert(unary.arg); + break; + case NEG: + skip("-"); + op = padLeft(EMPTY, J.Unary.Type.Negative); + expr = convert(unary.arg); + break; + case PREDEC: + skip("--"); + op = padLeft(EMPTY, J.Unary.Type.PreDecrement); + expr = convert(unary.arg); + break; + case PREINC: + skip("++"); + op = padLeft(EMPTY, J.Unary.Type.PreIncrement); + expr = convert(unary.arg); + break; + case POSTDEC: + expr = convert(unary.arg); + op = padLeft(sourceBefore("--"), J.Unary.Type.PostDecrement); + break; + case POSTINC: + expr = convert(unary.arg); + op = padLeft(sourceBefore("++"), J.Unary.Type.PostIncrement); + break; + case COMPL: + skip("~"); + op = padLeft(EMPTY, J.Unary.Type.Complement); + expr = convert(unary.arg); + break; + case NOT: + skip("!"); + op = padLeft(EMPTY, J.Unary.Type.Not); + expr = convert(unary.arg); + break; + default: + throw new IllegalArgumentException("Unexpected unary tag " + tag); + } + + return new J.Unary(randomId(), fmt, Markers.EMPTY, op, expr, typeMapping.type(node)); + } + + @Override + public J visitVariable(VariableTree node, Space fmt) { + return hasFlag(node.getModifiers(), Flags.ENUM) ? + visitEnumVariable(node, fmt) : + visitVariables(singletonList(node), fmt); // method arguments cannot be multi-declarations + } + + private J.VariableDeclarations visitVariables(List<VariableTree> nodes, Space fmt) { + JCTree.JCVariableDecl node = (JCVariableDecl) nodes.get(0); + + JCExpression vartype = node.vartype; + + Map<Integer, JCAnnotation> annotationPosTable = new HashMap<>(); + for (JCAnnotation annotationNode : node.getModifiers().getAnnotations()) { + annotationPosTable.put(annotationNode.pos, annotationNode); + } + ReloadableJava21ModifierResults modifierResults = sortedModifiersAndAnnotations(node.getModifiers(), annotationPosTable); + + List<J.Annotation> typeExprAnnotations = collectAnnotations(annotationPosTable); + + TypeTree typeExpr; + if (vartype == null || vartype instanceof JCErroneous) { + typeExpr = null; + } else if (endPos(vartype) < 0) { + if ((node.sym.flags() & Flags.PARAMETER) > 0) { + // this is a lambda parameter with an inferred type expression + typeExpr = null; + } else { + typeExpr = new J.Identifier(randomId(), sourceBefore("var"), Markers.EMPTY, emptyList(), "var", typeMapping.type(vartype), null); + typeExpr = typeExpr.withMarkers(typeExpr.getMarkers().add(JavaVarKeyword.build())); + } + } else if (vartype instanceof JCArrayTypeTree) { + // we'll capture the array dimensions in a bit, just convert the element type + JCExpression elementType = ((JCArrayTypeTree) vartype).elemtype; + while (elementType instanceof JCArrayTypeTree) { + elementType = ((JCArrayTypeTree) elementType).elemtype; + } + typeExpr = convert(elementType); + } else { + typeExpr = convert(vartype); + } + + if (typeExpr != null && !typeExprAnnotations.isEmpty()) { + typeExpr = new J.AnnotatedType(randomId(), Space.EMPTY, Markers.EMPTY, typeExprAnnotations, typeExpr); + } + + List<JLeftPadded<Space>> beforeDimensions = arrayDimensions(); + + Space varargs = null; + if (typeExpr != null && typeExpr.getMarkers().findFirst(JavaVarKeyword.class).isEmpty()) { + int varargStart = indexOfNextNonWhitespace(vartype.getStartPosition(), source); + if (source.startsWith("...", varargStart)) { + varargs = format(source, cursor, varargStart); + cursor = varargStart + 3; + } + } + + List<JRightPadded<J.VariableDeclarations.NamedVariable>> vars = new ArrayList<>(nodes.size()); + for (int i = 0; i < nodes.size(); i++) { + JCVariableDecl n = (JCVariableDecl) nodes.get(i); + + Space namedVarPrefix = sourceBefore(n.getName().toString()); + + JavaType type = typeMapping.type(n); + J.Identifier name = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), n.getName().toString(), + type instanceof JavaType.Variable ? ((JavaType.Variable) type).getType() : type, + type instanceof JavaType.Variable ? (JavaType.Variable) type : null); + List<JLeftPadded<Space>> dimensionsAfterName = arrayDimensions(); + + vars.add( + padRight( + new J.VariableDeclarations.NamedVariable(randomId(), namedVarPrefix, Markers.EMPTY, + name, + dimensionsAfterName, + n.init != null ? padLeft(sourceBefore("="), convertOrNull(n.init)) : null, + (JavaType.Variable) typeMapping.type(n) + ), + i == nodes.size() - 1 ? EMPTY : sourceBefore(",") + ) + ); + } + + return new J.VariableDeclarations(randomId(), fmt, Markers.EMPTY, modifierResults.getLeadingAnnotations(), modifierResults.getModifiers(), typeExpr, varargs, beforeDimensions, vars); + } + + private List<JLeftPadded<Space>> arrayDimensions() { + List<JLeftPadded<Space>> dims = null; + while (true) { + int beginBracket = indexOfNextNonWhitespace(cursor, source); + if (source.charAt(beginBracket) == '[') { + int endBracket = indexOfNextNonWhitespace(beginBracket + 1, source); + if (dims == null) { + dims = new ArrayList<>(2); + } + dims.add(padLeft(format(source, cursor, beginBracket), + format(source, beginBracket + 1, endBracket))); + cursor = endBracket + 1; + } else { + break; + } + } + return dims == null ? emptyList() : dims; + } + + @Override + public J visitWhileLoop(WhileLoopTree node, Space fmt) { + skip("while"); + return new J.WhileLoop(randomId(), fmt, Markers.EMPTY, + convert(node.getCondition()), + convert(node.getStatement(), this::statementDelim)); + } + + @Override + public J visitWildcard(WildcardTree node, Space fmt) { + skip("?"); + + JCWildcard wildcard = (JCWildcard) node; + + JLeftPadded<J.Wildcard.Bound> bound; + switch (wildcard.kind.kind) { + case EXTENDS: + bound = padLeft(sourceBefore("extends"), J.Wildcard.Bound.Extends); + break; + case SUPER: + bound = padLeft(sourceBefore("super"), J.Wildcard.Bound.Super); + break; + case UNBOUND: + default: + bound = null; + } + + return new J.Wildcard(randomId(), fmt, Markers.EMPTY, bound, convertOrNull(wildcard.inner)); + } + + /** + * -------------- + * Conversion utilities + * -------------- + */ + + private <J2 extends J> J2 convert(Tree t) { + try { + String prefix = source.substring(cursor, max(((JCTree) t).getStartPosition(), cursor)); + cursor += prefix.length(); + @SuppressWarnings("unchecked") J2 j = (J2) scan(t, formatWithCommentTree(prefix, (JCTree) t, docCommentTable.getCommentTree((JCTree) t))); + return j; + } catch (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:"); + message.append("--- BEGIN PATH ---\n"); + + List<Tree> paths = stream(getCurrentPath().spliterator(), false).toList(); + for (int i = paths.size(); i-- > 0; ) { + JCTree tree = (JCTree) paths.get(i); + if (tree instanceof JCCompilationUnit) { + message.append("JCCompilationUnit(sourceFile = ").append(((JCCompilationUnit) tree).sourcefile.getName()).append(")\n"); + } else if (tree instanceof JCClassDecl) { + message.append("JCClassDecl(name = ").append(((JCClassDecl) tree).name).append(", line = ").append(lineNumber(tree)).append(")\n"); + } else if (tree instanceof JCVariableDecl) { + message.append("JCVariableDecl(name = ").append(((JCVariableDecl) tree).name).append(", line = ").append(lineNumber(tree)).append(")\n"); + } else { + message.append(tree.getClass().getSimpleName()).append("(line = ").append(lineNumber(tree)).append(")\n"); + } + } + + message.append("--- END PATH ---\n"); + + ctx.getOnError().accept(new JavaParsingException(message.toString(), ex)); + throw ex; + } + } + + private <J2 extends J> JRightPadded<J2> convert(Tree t, Function<Tree, Space> suffix) { + J2 j = convert(t); + @SuppressWarnings("ConstantConditions") JRightPadded<J2> rightPadded = j == null ? null : + new JRightPadded<>(j, suffix.apply(t), Markers.EMPTY); + cursor(max(endPos(t), cursor)); // if there is a non-empty suffix, the cursor may have already moved past it + return rightPadded; + } + + private long lineNumber(Tree tree) { + return source.substring(0, ((JCTree) tree).getStartPosition()).chars().filter(c -> c == '\n').count() + 1; + } + + @Nullable + private <T extends J> T convertOrNull(@Nullable Tree t) { + return t == null ? null : convert(t); + } + + @Nullable + private <J2 extends J> JRightPadded<J2> convertOrNull(@Nullable Tree t, Function<Tree, Space> suffix) { + return t == null ? null : convert(t, suffix); + } + + private <J2 extends J> List<J2> convertAll(List<? extends Tree> trees) { + List<J2> converted = new ArrayList<>(trees.size()); + for (Tree tree : trees) { + converted.add(convert(tree)); + } + return converted; + } + + private <J2 extends J> List<JRightPadded<J2>> convertAll(List<? extends Tree> trees, + Function<Tree, Space> innerSuffix, + Function<Tree, Space> suffix) { + if (trees.isEmpty()) { + return emptyList(); + } + List<JRightPadded<J2>> converted = new ArrayList<>(trees.size()); + for (int i = 0; i < trees.size(); i++) { + converted.add(convert(trees.get(i), i == trees.size() - 1 ? suffix : innerSuffix)); + } + return converted; + } + + @Nullable + private JContainer<Expression> convertTypeParameters(@Nullable List<? extends Tree> typeArguments) { + if (typeArguments == null) { + return null; + } + + Space typeArgPrefix = sourceBefore("<"); + List<JRightPadded<Expression>> params; + if (typeArguments.isEmpty()) { + // raw type, see http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html + // adding space before > as a suffix to be consistent with space before > for non-empty lists of type args + params = singletonList(padRight(new J.Empty(randomId(), sourceBefore(">"), Markers.EMPTY), EMPTY)); + } else { + params = convertAll(typeArguments, commaDelim, t -> sourceBefore(">")); + } + + return JContainer.build(typeArgPrefix, params, Markers.EMPTY); + } + + private Space statementDelim(@Nullable Tree t) { + switch (t.getKind()) { + case ASSERT: + case ASSIGNMENT: + case BREAK: + case CONTINUE: + case DO_WHILE_LOOP: + case METHOD_INVOCATION: + case NEW_CLASS: + case RETURN: + case THROW: + case EXPRESSION_STATEMENT: + case VARIABLE: + case YIELD: + return sourceBefore(";"); + case LABELED_STATEMENT: + return statementDelim(((JCLabeledStatement) t).getStatement()); + case METHOD: + JCMethodDecl m = (JCMethodDecl) t; + return sourceBefore(m.body == null || m.defaultValue != null ? ";" : ""); + default: + return t instanceof JCAssignOp || t instanceof JCUnary ? sourceBefore(";") : EMPTY; + } + } + + private List<JRightPadded<Statement>> convertStatements(@Nullable List<? extends Tree> trees) { + return convertStatements(trees, this::statementDelim); + } + + @SuppressWarnings("unchecked") + private List<JRightPadded<Statement>> convertStatements(@Nullable List<? extends Tree> trees, + Function<Tree, Space> suffix) { + if (trees == null || trees.isEmpty()) { + return emptyList(); + } + + Map<Integer, List<Tree>> treesGroupedByStartPosition = new LinkedHashMap<>(); + for (Tree t : trees) { + treesGroupedByStartPosition.computeIfAbsent(((JCTree) t).getStartPosition(), k -> new ArrayList<>(1)).add(t); + } + + List<JRightPadded<Statement>> converted = new ArrayList<>(treesGroupedByStartPosition.size()); + for (List<? extends Tree> treeGroup : treesGroupedByStartPosition.values()) { + if (treeGroup.size() == 1) { + converted.add(convert(treeGroup.get(0), suffix)); + } else { + // multi-variable declarations are split into independent overlapping JCVariableDecl's by the OpenJDK AST + String prefix = source.substring(cursor, max(((JCTree) treeGroup.get(0)).getStartPosition(), cursor)); + cursor += prefix.length(); + + Tree last = treeGroup.get(treeGroup.size() - 1); + + @SuppressWarnings("unchecked") + J.VariableDeclarations vars = visitVariables((List<VariableTree>) treeGroup, format(prefix)); + JRightPadded<Statement> paddedVars = padRight(vars, semiDelim.apply(last)); + cursor(max(endPos(last), cursor)); + converted.add(paddedVars); + } + } + + return converted; + } + + /** + * -------------- + * Other convenience utilities + * -------------- + */ + + private int endPos(Tree t) { + return ((JCTree) t).getEndPosition(endPosTable); + } + + private Space sourceBefore(String untilDelim) { + return sourceBefore(untilDelim, null); + } + + /** + * @return Source from <code>cursor</code> to next occurrence of <code>untilDelim</code>, + * and if not found in the remaining source, the empty String. If <code>stop</code> is reached before + * <code>untilDelim</code> return the empty String. + */ + private Space sourceBefore(String untilDelim, @Nullable Character stop) { + int delimIndex = positionOfNext(untilDelim, stop); + if (delimIndex < 0) { + return EMPTY; // unable to find this delimiter + } + + if (delimIndex == cursor) { + cursor += untilDelim.length(); + return EMPTY; + } + + Space space = format(source, cursor, delimIndex); + cursor = delimIndex + untilDelim.length(); // advance past the delimiter + return space; + } + + private <T> JRightPadded<T> padRight(T tree, Space right) { + return new JRightPadded<>(tree, right, Markers.EMPTY); + } + + private <T> JLeftPadded<T> padLeft(Space left, T tree) { + return new JLeftPadded<>(left, tree, Markers.EMPTY); + } + + private int positionOfNext(String untilDelim, @Nullable Character stop) { + boolean inMultiLineComment = false; + boolean inSingleLineComment = false; + + int delimIndex = cursor; + for (; delimIndex < source.length() - untilDelim.length() + 1; delimIndex++) { + if (inSingleLineComment) { + if (source.charAt(delimIndex) == '\n') { + inSingleLineComment = false; + } + } else { + if (source.length() - untilDelim.length() > delimIndex + 1) { + char c1 = source.charAt(delimIndex); + char c2 = source.charAt(delimIndex + 1); + switch (c1) { + case '/': + switch (c2) { + case '/': + inSingleLineComment = true; + delimIndex++; + break; + case '*': + inMultiLineComment = true; + delimIndex++; + break; + } + break; + case '*': + if (c2 == '/') { + inMultiLineComment = false; + delimIndex += 2; + } + break; + } + } + + if (!inMultiLineComment && !inSingleLineComment) { + if (stop != null && source.charAt(delimIndex) == stop) + return -1; // reached stop word before finding the delimiter + + if (source.startsWith(untilDelim, delimIndex)) { + break; // found it! + } + } + } + } + + return delimIndex > source.length() - untilDelim.length() ? -1 : delimIndex; + } + + private final Function<Tree, Space> semiDelim = ignored -> sourceBefore(";"); + private final Function<Tree, Space> commaDelim = ignored -> sourceBefore(","); + private final Function<Tree, Space> noDelim = ignored -> EMPTY; + + private Space whitespace() { + int nextNonWhitespace = indexOfNextNonWhitespace(cursor, source); + if (nextNonWhitespace == cursor) { + return EMPTY; + } + Space space = format(source, cursor, nextNonWhitespace); + cursor = nextNonWhitespace; + return space; + } + + private String skip(@Nullable String token) { + if (token == null) { + //noinspection ConstantConditions + return null; + } + if (source.startsWith(token, cursor)) { + cursor += token.length(); + } + return token; + } + + // Only exists as a function to make it easier to debug unexpected cursor shifts + private void cursor(int n) { + cursor = n; + } + + private boolean hasFlag(ModifiersTree modifiers, long flag) { + return (((JCModifiers) modifiers).flags & flag) != 0L; + } + + @SuppressWarnings("unused") + // Used for debugging + private List<String> listFlags(long flags) { + Map<String, Long> allFlags = Arrays.stream(Flags.class.getDeclaredFields()) + .filter(field -> { + field.setAccessible(true); + try { + // FIXME instanceof probably not right here... + return field.get(null) instanceof Long && + field.getName().matches("[A-Z_]+"); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toMap(Field::getName, field -> { + try { + return (Long) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + })); + + List<String> all = new ArrayList<>(allFlags.size()); + for (Map.Entry<String, Long> flagNameAndCode : allFlags.entrySet()) { + if ((flagNameAndCode.getValue() & flags) != 0L) { + all.add(flagNameAndCode.getKey()); + } + } + return all; + } + + /** + * Leading annotations and modifiers in the order they appear in the source, which is not necessarily the same as the order in + * which they appear in the OpenJDK AST + */ + private ReloadableJava21ModifierResults sortedModifiersAndAnnotations(ModifiersTree modifiers, Map<Integer, JCAnnotation> annotationPosTable) { + List<J.Annotation> leadingAnnotations = new ArrayList<>(); + List<J.Modifier> sortedModifiers = new ArrayList<>(); + List<J.Annotation> currentAnnotations = new ArrayList<>(2); + boolean afterFirstModifier = false; + boolean inComment = false; + boolean inMultilineComment = false; + int afterLastModifierPosition = cursor; + int lastAnnotationPosition = cursor; + + int keywordStartIdx = -1; + for (int i = cursor; i < source.length(); i++) { + if (annotationPosTable.containsKey(i)) { + J.Annotation annotation = convert(annotationPosTable.get(i)); + if (afterFirstModifier) { + currentAnnotations.add(annotation); + } else { + leadingAnnotations.add(annotation); + } + i = cursor -1; + lastAnnotationPosition = cursor; + continue; + } + char c = source.charAt(i); + if (c == '/' && source.length() > i + 1) { + char next = source.charAt(i + 1); + if (next == '*') { + inMultilineComment = true; + } else if (next == '/') { + inComment = true; + } + } + + if (inMultilineComment && c == '/' && source.charAt(i - 1) == '*') { + inMultilineComment = false; + } else if (inComment && c == '\n' || c == '\r') { + inComment = false; + } else if (!inMultilineComment && !inComment) { + if (Character.isWhitespace(c)) { + if (keywordStartIdx != -1) { + Modifier matching = MODIFIER_BY_KEYWORD.get(source.substring(keywordStartIdx, i)); + keywordStartIdx = -1; + + if (matching == null) { + this.cursor = afterLastModifierPosition; + break; + } else { + sortedModifiers.add(mapModifier(matching, currentAnnotations)); + afterFirstModifier = true; + currentAnnotations = new ArrayList<>(2); + afterLastModifierPosition = cursor; + } + } + } else if (keywordStartIdx == -1) { + keywordStartIdx = i; + } + } + } + if (sortedModifiers.isEmpty()) { + cursor = lastAnnotationPosition; + } + return new ReloadableJava21ModifierResults( + leadingAnnotations.isEmpty() ? emptyList() : leadingAnnotations, + sortedModifiers.isEmpty() ? emptyList() : sortedModifiers + ); + } + + private J.Modifier mapModifier(Modifier mod, List<J.Annotation> annotations) { + Space modFormat = whitespace(); + cursor += mod.name().length(); + J.Modifier.Type type; + switch (mod) { + case DEFAULT: + type = J.Modifier.Type.Default; + break; + case PUBLIC: + type = J.Modifier.Type.Public; + break; + case PROTECTED: + type = J.Modifier.Type.Protected; + break; + case PRIVATE: + type = J.Modifier.Type.Private; + break; + case ABSTRACT: + type = J.Modifier.Type.Abstract; + break; + case STATIC: + type = J.Modifier.Type.Static; + break; + case FINAL: + type = J.Modifier.Type.Final; + break; + case NATIVE: + type = J.Modifier.Type.Native; + break; + case STRICTFP: + type = J.Modifier.Type.Strictfp; + break; + case SYNCHRONIZED: + type = J.Modifier.Type.Synchronized; + break; + case TRANSIENT: + type = J.Modifier.Type.Transient; + break; + case VOLATILE: + type = J.Modifier.Type.Volatile; + break; + case SEALED: + type = J.Modifier.Type.Sealed; + break; + case NON_SEALED: + type = J.Modifier.Type.NonSealed; + break; + default: + throw new IllegalArgumentException("Unexpected modifier " + mod); + } + return new J.Modifier(randomId(), modFormat, Markers.EMPTY, null, type, annotations); + } + + private List<J.Annotation> collectAnnotations(Map<Integer, JCAnnotation> annotationPosTable) { + int maxAnnotationPosition = 0; + for (Integer pos : annotationPosTable.keySet()) { + if (pos > maxAnnotationPosition) { + maxAnnotationPosition = pos; + } + } + + List<J.Annotation> annotations = new ArrayList<>(); + boolean inComment = false; + boolean inMultilineComment = false; + for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) { + if (annotationPosTable.containsKey(i)) { + annotations.add(convert(annotationPosTable.get(i))); + i = cursor; + continue; + } + char c = source.charAt(i); + if (c == '/' && source.length() > i + 1) { + char next = source.charAt(i + 1); + if (next == '*') { + inMultilineComment = true; + } else if (next == '/') { + inComment = true; + } + } + + if (inMultilineComment && c == '/' && i > 0 && source.charAt(i - 1) == '*') { + inMultilineComment = false; + } else if (inComment && c == '\n' || c == '\r') { + inComment = false; + } else if (!inMultilineComment && !inComment) { + if (!Character.isWhitespace(c)) { + break; + } + } + } + return annotations; + } + + Space formatWithCommentTree(String prefix, JCTree tree, @Nullable DCTree.DCDocComment commentTree) { + Space fmt = format(prefix); + if (commentTree != null) { + List<Comment> comments = fmt.getComments(); + int i; + for (i = comments.size() - 1; i >= 0; i--) { + Comment comment = comments.get(i); + if (comment.isMultiline() && ((TextComment) comment).getText().startsWith("*")) { + break; + } + } + + AtomicReference<Javadoc.DocComment> javadoc = new AtomicReference<>(); + int commentCursor = cursor - prefix.length() + fmt.getWhitespace().length(); + for (int j = 0; j < comments.size(); j++) { + Comment comment = comments.get(j); + if (i == j) { + javadoc.set((Javadoc.DocComment) new ReloadableJava21JavadocVisitor( + context, + getCurrentPath(), + typeMapping, + source.substring(commentCursor, source.indexOf("*/", commentCursor + 1)), + tree + ).scan(commentTree, new ArrayList<>(1))); + break; + } else { + commentCursor += comment.printComment(new Cursor(null, "root")).length() + comment.getSuffix().length(); + } + } + + int javadocIndex = i; + return fmt.withComments(ListUtils.map(fmt.getComments(), (j, c) -> + j == javadocIndex ? javadoc.get().withSuffix(c.getSuffix()) : c)); + } + + return fmt; + } +} diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java new file mode 100644 index 00000000000..046153bb5a1 --- /dev/null +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java @@ -0,0 +1,622 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.isolated; + +import com.sun.source.tree.Tree; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.tree.JCTree; +import lombok.RequiredArgsConstructor; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaTypeMapping; +import org.openrewrite.java.internal.JavaTypeCache; +import org.openrewrite.java.tree.Flag; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; + +import javax.lang.model.type.NullType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Collections.singletonList; +import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.*; + +@RequiredArgsConstructor +class ReloadableJava21TypeMapping implements JavaTypeMapping<Tree> { + + private final ReloadableJava21TypeSignatureBuilder signatureBuilder = new ReloadableJava21TypeSignatureBuilder(); + + private final JavaTypeCache typeCache; + + public JavaType type(@Nullable com.sun.tools.javac.code.Type type) { + if (type == null || type instanceof Type.ErrorType || type instanceof Type.PackageType || type instanceof Type.UnknownType || + type instanceof NullType) { + return JavaType.Class.Unknown.getInstance(); + } + + String signature = signatureBuilder.signature(type); + JavaType existing = typeCache.get(signature); + if (existing != null) { + return existing; + } + + if (type instanceof Type.ClassType) { + return classType((Type.ClassType) type, signature); + } else if (type instanceof Type.TypeVar) { + return generic((Type.TypeVar) type, signature); + } else if (type instanceof Type.JCPrimitiveType) { + return primitive(type.getTag()); + } else if (type instanceof Type.JCVoidType) { + return JavaType.Primitive.Void; + } else if (type instanceof Type.ArrayType) { + return array(type, signature); + } else if (type instanceof Type.WildcardType) { + return generic((Type.WildcardType) type, signature); + } else if (type instanceof Type.JCNoType) { + return JavaType.Class.Unknown.getInstance(); + } + + throw new UnsupportedOperationException("Unknown type " + type.getClass().getName()); + } + + private JavaType array(Type type, String signature) { + JavaType.Array arr = new JavaType.Array(null, null); + typeCache.put(signature, arr); + arr.unsafeSet(type(((Type.ArrayType) type).elemtype)); + return arr; + } + + private JavaType.GenericTypeVariable generic(Type.WildcardType wildcard, String signature) { + JavaType.GenericTypeVariable gtv = new JavaType.GenericTypeVariable(null, "?", INVARIANT, null); + typeCache.put(signature, gtv); + + JavaType.GenericTypeVariable.Variance variance; + List<JavaType> bounds; + + switch (wildcard.kind) { + case SUPER: + variance = CONTRAVARIANT; + bounds = singletonList(type(wildcard.getSuperBound())); + break; + case EXTENDS: + variance = COVARIANT; + bounds = singletonList(type(wildcard.getExtendsBound())); + break; + case UNBOUND: + default: + variance = INVARIANT; + bounds = null; + break; + } + + if (bounds != null && bounds.get(0) instanceof JavaType.FullyQualified && "java.lang.Object".equals(((JavaType.FullyQualified) bounds.get(0)) + .getFullyQualifiedName())) { + bounds = null; + } + + gtv.unsafeSet(gtv.getName(), variance, bounds); + return gtv; + } + + private JavaType generic(Type.TypeVar type, String signature) { + String name = type.tsym.name.toString(); + JavaType.GenericTypeVariable gtv = new JavaType.GenericTypeVariable(null, + name, INVARIANT, null); + typeCache.put(signature, gtv); + + List<JavaType> bounds = null; + if (type.getUpperBound() instanceof Type.IntersectionClassType) { + Type.IntersectionClassType intersectionBound = (Type.IntersectionClassType) type.getUpperBound(); + boolean isIntersectionSuperType = !intersectionBound.supertype_field.tsym.getQualifiedName().toString().equals("java.lang.Object"); + bounds = new ArrayList<>((isIntersectionSuperType ? 1 : 0) + intersectionBound.interfaces_field.length()); + + if (isIntersectionSuperType) { + bounds.add(type(intersectionBound.supertype_field)); + } + for (Type bound : intersectionBound.interfaces_field) { + bounds.add(type(bound)); + } + } else if (type.getUpperBound() != null) { + JavaType mappedBound = type(type.getUpperBound()); + if (!(mappedBound instanceof JavaType.FullyQualified) || !"java.lang.Object".equals(((JavaType.FullyQualified) mappedBound).getFullyQualifiedName())) { + bounds = singletonList(mappedBound); + } + } + + gtv.unsafeSet(gtv.getName(), bounds == null ? INVARIANT : COVARIANT, bounds); + return gtv; + } + + private JavaType.FullyQualified classType(Type.ClassType classType, String signature) { + Symbol.ClassSymbol sym = (Symbol.ClassSymbol) classType.tsym; + Type.ClassType symType = (Type.ClassType) sym.type; + String fqn = sym.flatName().toString(); + + JavaType.FullyQualified fq = typeCache.get(fqn); + JavaType.Class clazz = (JavaType.Class) (fq instanceof JavaType.Parameterized ? ((JavaType.Parameterized) fq).getType() : fq); + if (clazz == null) { + if (!sym.completer.isTerminal()) { + completeClassSymbol(sym); + } + + clazz = new JavaType.Class( + null, + sym.flags_field, + fqn, + getKind(sym), + null, null, null, null, null, null, null + ); + + typeCache.put(fqn, clazz); + + JavaType.FullyQualified supertype = TypeUtils.asFullyQualified(type(symType.supertype_field)); + + JavaType.FullyQualified owner = null; + if (sym.owner instanceof Symbol.ClassSymbol) { + owner = TypeUtils.asFullyQualified(type(sym.owner.type)); + } + + List<JavaType.FullyQualified> interfaces = null; + if (symType.interfaces_field != null) { + interfaces = new ArrayList<>(symType.interfaces_field.length()); + for (com.sun.tools.javac.code.Type iParam : symType.interfaces_field) { + JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(iParam)); + if (javaType != null) { + interfaces.add(javaType); + } + } + } + + List<JavaType.Variable> fields = null; + List<JavaType.Method> methods = null; + + if (sym.members_field != null) { + for (Symbol elem : sym.members_field.getSymbols()) { + if (elem instanceof Symbol.VarSymbol && + (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | + Flags.GENERATEDCONSTR | Flags.ANONCONSTR)) == 0) { + if (fqn.equals("java.lang.String") && elem.name.toString().equals("serialPersistentFields")) { + // there is a "serialPersistentFields" member within the String class which is used in normal Java + // serialization to customize how the String field is serialized. This field is tripping up Jackson + // serialization and is intentionally filtered to prevent errors. + continue; + } + + if (fields == null) { + fields = new ArrayList<>(); + } + fields.add(variableType(elem, clazz)); + } else if (elem instanceof Symbol.MethodSymbol && + (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | Flags.ANONCONSTR)) == 0) { + if (methods == null) { + methods = new ArrayList<>(); + } + Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) elem; + if (!methodSymbol.isStaticOrInstanceInit()) { + methods.add(methodDeclarationType(methodSymbol, clazz)); + } + } + } + } + + List<JavaType> typeParameters = null; + if (symType.typarams_field != null && symType.typarams_field.length() > 0) { + typeParameters = new ArrayList<>(symType.typarams_field.length()); + for (Type tParam : symType.typarams_field) { + typeParameters.add(type(tParam)); + } + } + clazz.unsafeSet(typeParameters, supertype, owner, listAnnotations(sym), interfaces, fields, methods); + } + + if (classType.typarams_field != null && classType.typarams_field.length() > 0) { + JavaType.Parameterized pt = typeCache.get(signature); + if (pt == null) { + pt = new JavaType.Parameterized(null, null, null); + typeCache.put(signature, pt); + + List<JavaType> typeParameters = new ArrayList<>(classType.typarams_field.length()); + for (Type tParam : classType.typarams_field) { + typeParameters.add(type(tParam)); + } + + pt.unsafeSet(clazz, typeParameters); + } + return pt; + } + return clazz; + } + + private JavaType.FullyQualified.Kind getKind(Symbol.ClassSymbol sym) { + switch (sym.getKind()) { + case ENUM: + return JavaType.FullyQualified.Kind.Enum; + case ANNOTATION_TYPE: + return JavaType.FullyQualified.Kind.Annotation; + case INTERFACE: + return JavaType.FullyQualified.Kind.Interface; + case RECORD: + return JavaType.FullyQualified.Kind.Record; + default: + return JavaType.FullyQualified.Kind.Class; + } + } + + @SuppressWarnings("ConstantConditions") + public JavaType type(@Nullable Tree tree) { + if (tree == null) { + return null; + } + + Symbol symbol = null; + if (tree instanceof JCTree.JCIdent) { + symbol = ((JCTree.JCIdent) tree).sym; + } else if (tree instanceof JCTree.JCMethodDecl) { + symbol = ((JCTree.JCMethodDecl) tree).sym; + } else if (tree instanceof JCTree.JCVariableDecl) { + return variableType(((JCTree.JCVariableDecl) tree).sym); + } + + return type(((JCTree) tree).type, symbol); + } + + @Nullable + private JavaType type(Type type, Symbol symbol) { + if (type instanceof Type.MethodType) { + return methodInvocationType(type, symbol); + } + return type(type); + } + + public JavaType.Primitive primitive(TypeTag tag) { + switch (tag) { + case BOOLEAN: + return JavaType.Primitive.Boolean; + case BYTE: + return JavaType.Primitive.Byte; + case CHAR: + return JavaType.Primitive.Char; + case DOUBLE: + return JavaType.Primitive.Double; + case FLOAT: + return JavaType.Primitive.Float; + case INT: + return JavaType.Primitive.Int; + case LONG: + return JavaType.Primitive.Long; + case SHORT: + return JavaType.Primitive.Short; + case VOID: + return JavaType.Primitive.Void; + case NONE: + return JavaType.Primitive.None; + case CLASS: + return JavaType.Primitive.String; + case BOT: + return JavaType.Primitive.Null; + default: + throw new IllegalArgumentException("Unknown type tag " + tag); + } + } + + @Nullable + public JavaType.Variable variableType(@Nullable Symbol symbol) { + return variableType(symbol, null); + } + + @Nullable + private JavaType.Variable variableType(@Nullable Symbol symbol, + @Nullable JavaType.FullyQualified owner) { + if (!(symbol instanceof Symbol.VarSymbol)) { + return null; + } + + String signature = signatureBuilder.variableSignature(symbol); + JavaType.Variable existing = typeCache.get(signature); + if (existing != null) { + return existing; + } + + JavaType.Variable variable = new JavaType.Variable( + null, + symbol.flags_field, + symbol.name.toString(), + null, null, null); + + typeCache.put(signature, variable); + + JavaType resolvedOwner = owner; + if (owner == null) { + Type type = symbol.owner.type; + Symbol sym = symbol.owner; + + if (sym.type instanceof Type.ForAll) { + type = ((Type.ForAll) type).qtype; + } + + resolvedOwner = type instanceof Type.MethodType ? + methodInvocationType(type, sym) : + type(type); + assert resolvedOwner != null; + } + + variable.unsafeSet(resolvedOwner, type(symbol.type), listAnnotations(symbol)); + return variable; + } + + /** + * Method type of a method invocation. Parameters and return type represent resolved types when they are generic + * in the method declaration. + * + * @param selectType The method type. + * @param symbol The method symbol. + * @return Method type attribution. + */ + @Nullable + public JavaType.Method methodInvocationType(@Nullable com.sun.tools.javac.code.Type selectType, @Nullable Symbol symbol) { + if (selectType == null || selectType instanceof Type.ErrorType || symbol == null || symbol.kind == Kinds.Kind.ERR || symbol.type instanceof Type.UnknownType) { + return null; + } + + Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) symbol; + + if (selectType instanceof Type.ForAll) { + Type.ForAll fa = (Type.ForAll) selectType; + return methodInvocationType(fa.qtype, methodSymbol); + } + + String signature = signatureBuilder.methodSignature(selectType, methodSymbol); + JavaType.Method existing = typeCache.get(signature); + if (existing != null) { + return existing; + } + + String[] paramNames = null; + if (!methodSymbol.params().isEmpty()) { + paramNames = new String[methodSymbol.params().size()]; + com.sun.tools.javac.util.List<Symbol.VarSymbol> params = methodSymbol.params(); + for (int i = 0; i < params.size(); i++) { + Symbol.VarSymbol p = params.get(i); + String s = p.name.toString(); + paramNames[i] = s; + } + } + + JavaType.Method method = new JavaType.Method( + null, + methodSymbol.flags_field, + null, + methodSymbol.isConstructor() ? "<constructor>" : methodSymbol.getSimpleName().toString(), + null, + paramNames, + null, null, null, null + ); + typeCache.put(signature, method); + + JavaType returnType = null; + List<JavaType> parameterTypes = null; + List<JavaType.FullyQualified> exceptionTypes = null; + + if (selectType instanceof Type.MethodType) { + Type.MethodType methodType = (Type.MethodType) selectType; + + if (!methodType.argtypes.isEmpty()) { + parameterTypes = new ArrayList<>(methodType.argtypes.size()); + for (com.sun.tools.javac.code.Type argtype : methodType.argtypes) { + if (argtype != null) { + JavaType javaType = type(argtype); + parameterTypes.add(javaType); + } + } + } + + returnType = type(methodType.restype); + + if (!methodType.thrown.isEmpty()) { + exceptionTypes = new ArrayList<>(methodType.thrown.size()); + for (Type exceptionType : methodType.thrown) { + JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(exceptionType)); + if (javaType == null) { + // if the type cannot be resolved to a class (it might not be on the classpath, or it might have + // been mapped to cyclic) + if (exceptionType instanceof Type.ClassType) { + Symbol.ClassSymbol sym = (Symbol.ClassSymbol) exceptionType.tsym; + javaType = new JavaType.Class(null, Flag.Public.getBitMask(), sym.flatName().toString(), JavaType.Class.Kind.Class, + null, null, null, null, null, null, null); + } + } + if (javaType != null) { + // if the exception type is not resolved, it is not added to the list of exceptions + exceptionTypes.add(javaType); + } + } + } + } else if (selectType instanceof Type.UnknownType) { + returnType = JavaType.Unknown.getInstance(); + } + + JavaType.FullyQualified resolvedDeclaringType = TypeUtils.asFullyQualified(type(methodSymbol.owner.type)); + if (resolvedDeclaringType == null) { + return null; + } + + assert returnType != null; + + method.unsafeSet(resolvedDeclaringType, + methodSymbol.isConstructor() ? resolvedDeclaringType : returnType, + parameterTypes, exceptionTypes, listAnnotations(methodSymbol)); + return method; + } + + /** + * Method type of a method declaration. Parameters and return type represent generic signatures when applicable. + * + * @param symbol The method symbol. + * @param declaringType The method's declaring type. + * @return Method type attribution. + */ + @Nullable + public JavaType.Method methodDeclarationType(@Nullable Symbol symbol, @Nullable JavaType.FullyQualified declaringType) { + // if the symbol is not a method symbol, there is a parser error in play + Symbol.MethodSymbol methodSymbol = symbol instanceof Symbol.MethodSymbol ? (Symbol.MethodSymbol) symbol : null; + + if (methodSymbol != null) { + String signature = signatureBuilder.methodSignature(methodSymbol); + JavaType.Method existing = typeCache.get(signature); + if (existing != null) { + return existing; + } + + String[] paramNames = null; + if (!methodSymbol.params().isEmpty()) { + paramNames = new String[methodSymbol.params().size()]; + com.sun.tools.javac.util.List<Symbol.VarSymbol> params = methodSymbol.params(); + for (int i = 0; i < params.size(); i++) { + Symbol.VarSymbol p = params.get(i); + String s = p.name.toString(); + paramNames[i] = s; + } + } + List<String> defaultValues = null; + if(methodSymbol.getDefaultValue() != null) { + if(methodSymbol.getDefaultValue() instanceof Attribute.Array) { + defaultValues = ((Attribute.Array) methodSymbol.getDefaultValue()).getValue().stream() + .map(attr -> attr.getValue().toString()) + .collect(Collectors.toList()); + } else { + defaultValues = Collections.singletonList(methodSymbol.getDefaultValue().getValue().toString()); + } + } + JavaType.Method method = new JavaType.Method( + null, + methodSymbol.flags_field, + null, + methodSymbol.isConstructor() ? "<constructor>" : methodSymbol.getSimpleName().toString(), + null, + paramNames, + null, null, null, + defaultValues + ); + typeCache.put(signature, method); + + Type signatureType = methodSymbol.type instanceof Type.ForAll ? + ((Type.ForAll) methodSymbol.type).qtype : + methodSymbol.type; + + List<JavaType.FullyQualified> exceptionTypes = null; + + Type selectType = methodSymbol.type; + if (selectType instanceof Type.ForAll) { + selectType = ((Type.ForAll) selectType).qtype; + } + + if (selectType instanceof Type.MethodType) { + Type.MethodType methodType = (Type.MethodType) selectType; + if (!methodType.thrown.isEmpty()) { + exceptionTypes = new ArrayList<>(methodType.thrown.size()); + for (Type exceptionType : methodType.thrown) { + JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(exceptionType)); + if (javaType == null) { + // if the type cannot be resolved to a class (it might not be on the classpath, or it might have + // been mapped to cyclic) + if (exceptionType instanceof Type.ClassType) { + Symbol.ClassSymbol sym = (Symbol.ClassSymbol) exceptionType.tsym; + javaType = new JavaType.Class(null, Flag.Public.getBitMask(), sym.flatName().toString(), JavaType.Class.Kind.Class, + null, null, null, null, null, null, null); + } + } + if (javaType != null) { + // if the exception type is not resolved, it is not added to the list of exceptions + exceptionTypes.add(javaType); + } + } + } + } + + JavaType.FullyQualified resolvedDeclaringType = declaringType; + if (declaringType == null) { + if (methodSymbol.owner instanceof Symbol.ClassSymbol || methodSymbol.owner instanceof Symbol.TypeVariableSymbol) { + resolvedDeclaringType = TypeUtils.asFullyQualified(type(methodSymbol.owner.type)); + } + } + + if (resolvedDeclaringType == null) { + return null; + } + + JavaType returnType; + List<JavaType> parameterTypes = null; + + if (signatureType instanceof Type.ForAll) { + signatureType = ((Type.ForAll) signatureType).qtype; + } + if (signatureType instanceof Type.MethodType) { + Type.MethodType mt = (Type.MethodType) signatureType; + + if (!mt.argtypes.isEmpty()) { + parameterTypes = new ArrayList<>(mt.argtypes.size()); + for (com.sun.tools.javac.code.Type argtype : mt.argtypes) { + if (argtype != null) { + JavaType javaType = type(argtype); + parameterTypes.add(javaType); + } + } + } + + returnType = type(mt.restype); + } else { + throw new UnsupportedOperationException("Unexpected method signature type" + signatureType.getClass().getName()); + } + + method.unsafeSet(resolvedDeclaringType, + methodSymbol.isConstructor() ? resolvedDeclaringType : returnType, + parameterTypes, exceptionTypes, listAnnotations(methodSymbol)); + return method; + } + + return null; + } + + private void completeClassSymbol(Symbol.ClassSymbol classSymbol) { + try { + classSymbol.complete(); + } catch (Symbol.CompletionFailure ignore) { + } + } + + @Nullable + private List<JavaType.FullyQualified> listAnnotations(Symbol symb) { + List<JavaType.FullyQualified> annotations = null; + if (!symb.getDeclarationAttributes().isEmpty()) { + annotations = new ArrayList<>(symb.getDeclarationAttributes().size()); + for (Attribute.Compound a : symb.getDeclarationAttributes()) { + JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(a.type)); + if (annotType == null) { + continue; + } + Retention retention = a.getAnnotationType().asElement().getAnnotation(Retention.class); + if(retention != null && retention.value() == RetentionPolicy.SOURCE) { + continue; + } + annotations.add(annotType); + } + } + return annotations; + } +} diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeSignatureBuilder.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeSignatureBuilder.java new file mode 100644 index 00000000000..2d2ccd15955 --- /dev/null +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeSignatureBuilder.java @@ -0,0 +1,272 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.isolated; + +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTag; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaTypeSignatureBuilder; +import org.openrewrite.java.tree.JavaType; + +import javax.lang.model.type.NullType; +import java.util.HashSet; +import java.util.Set; +import java.util.StringJoiner; + +class ReloadableJava21TypeSignatureBuilder implements JavaTypeSignatureBuilder { + @Nullable + private Set<String> typeVariableNameStack; + + @Override + public String signature(@Nullable Object t) { + return signature((Type) t); + } + + private String signature(@Nullable Type type) { + if (type == null || type instanceof Type.UnknownType || type instanceof NullType) { + return "{undefined}"; + } else if (type instanceof Type.ClassType) { + try { + return ((Type.ClassType) type).typarams_field != null && ((Type.ClassType) type).typarams_field.length() > 0 ? parameterizedSignature(type) : classSignature(type); + } catch (Symbol.CompletionFailure ignored) { + return ((Type.ClassType) type).typarams_field != null && ((Type.ClassType) type).typarams_field.length() > 0 ? parameterizedSignature(type) : classSignature(type); + } + } else if (type instanceof Type.CapturedType) { // CapturedType must be evaluated before TypeVar + return signature(((Type.CapturedType) type).wildcard); + } else if (type instanceof Type.TypeVar) { + return genericSignature(type); + } else if (type instanceof Type.JCPrimitiveType) { + return primitiveSignature(type); + } else if (type instanceof Type.JCVoidType) { + return "void"; + } else if (type instanceof Type.ArrayType) { + return arraySignature(type); + } else if (type instanceof Type.WildcardType) { + Type.WildcardType wildcard = (Type.WildcardType) type; + StringBuilder s = new StringBuilder("Generic{" + wildcard.kind.toString()); + if (!type.isUnbound()) { + s.append(signature(wildcard.type)); + } + return s.append("}").toString(); + } else if (type instanceof Type.JCNoType) { + return "{none}"; + } + + throw new IllegalStateException("Unexpected type " + type.getClass().getName()); + } + + private void completeClassSymbol(Symbol.ClassSymbol classSymbol) { + try { + classSymbol.complete(); + } catch (Symbol.CompletionFailure ignore) { + } + } + + @Override + public String arraySignature(Object type) { + return signature(((Type.ArrayType) type).elemtype) + "[]"; + } + + @Override + public String classSignature(Object type) { + if (type instanceof Type.JCVoidType) { + return "void"; + } else if (type instanceof Type.JCPrimitiveType) { + return primitiveSignature(type); + } else if (type instanceof Type.JCNoType) { + return "{undefined}"; + } + + Symbol.ClassSymbol sym = (Symbol.ClassSymbol) ((Type.ClassType) type).tsym; + if (!sym.completer.isTerminal()) { + completeClassSymbol(sym); + } + return sym.flatName().toString(); + } + + @Override + public String genericSignature(Object type) { + Type.TypeVar generic = (Type.TypeVar) type; + String name = generic.tsym.name.toString(); + + if (typeVariableNameStack == null) { + typeVariableNameStack = new HashSet<>(); + } + + if (!typeVariableNameStack.add(name)) { + return "Generic{" + name + "}"; + } + + StringBuilder s = new StringBuilder("Generic{").append(name); + + StringJoiner boundSigs = new StringJoiner(" & "); + if (generic.getUpperBound() instanceof Type.IntersectionClassType) { + Type.IntersectionClassType intersectionBound = (Type.IntersectionClassType) generic.getUpperBound(); + if (intersectionBound.supertype_field != null) { + String bound = signature(intersectionBound.supertype_field); + if (!"java.lang.Object".equals(bound)) { + boundSigs.add(bound); + } + } + for (Type bound : intersectionBound.interfaces_field) { + boundSigs.add(signature(bound)); + } + } else { + String bound = signature(generic.getUpperBound()); + if (!"java.lang.Object".equals(bound)) { + boundSigs.add(bound); + } + } + + String boundSigStr = boundSigs.toString(); + if (!boundSigStr.isEmpty()) { + s.append(" extends ").append(boundSigStr); + } + + typeVariableNameStack.remove(name); + + return s.append("}").toString(); + } + + @Override + public String parameterizedSignature(Object type) { + StringBuilder s = new StringBuilder(classSignature(type)); + StringJoiner joiner = new StringJoiner(", ", "<", ">"); + for (Type tp : ((Type.ClassType) type).typarams_field) { + String signature = signature(tp); + joiner.add(signature); + } + s.append(joiner); + return s.toString(); + } + + @Override + public String primitiveSignature(Object type) { + TypeTag tag = ((Type.JCPrimitiveType) type).getTag(); + switch (tag) { + case BOOLEAN: + return JavaType.Primitive.Boolean.getKeyword(); + case BYTE: + return JavaType.Primitive.Byte.getKeyword(); + case CHAR: + return JavaType.Primitive.Char.getKeyword(); + case DOUBLE: + return JavaType.Primitive.Double.getKeyword(); + case FLOAT: + return JavaType.Primitive.Float.getKeyword(); + case INT: + return JavaType.Primitive.Int.getKeyword(); + case LONG: + return JavaType.Primitive.Long.getKeyword(); + case SHORT: + return JavaType.Primitive.Short.getKeyword(); + case VOID: + return JavaType.Primitive.Void.getKeyword(); + case NONE: + return JavaType.Primitive.None.getKeyword(); + case CLASS: + return JavaType.Primitive.String.getKeyword(); + case BOT: + return JavaType.Primitive.Null.getKeyword(); + default: + throw new IllegalArgumentException("Unknown type tag " + tag); + } + } + + public String methodSignature(Type selectType, Symbol.MethodSymbol symbol) { + String s = classSignature(symbol.owner.type); + if (symbol.isConstructor()) { + s += "{name=<constructor>,return=" + s; + } else { + s += "{name=" + symbol.getSimpleName().toString() + + ",return=" + signature(selectType.getReturnType()); + } + + return s + ",parameters=" + methodArgumentSignature(selectType) + '}'; + } + + public String methodSignature(Symbol.MethodSymbol symbol) { + String s = classSignature(symbol.owner.type); + + String returnType; + if (symbol.isStaticOrInstanceInit()) { + returnType = "void"; + } else { + returnType = signature(symbol.getReturnType()); + } + + if (symbol.isConstructor()) { + s += "{name=<constructor>,return=" + s; + } else { + s += "{name=" + symbol.getSimpleName().toString() + + ",return=" + returnType; + } + + return s + ",parameters=" + methodArgumentSignature(symbol) + '}'; + } + + private String methodArgumentSignature(Symbol.MethodSymbol sym) { + if (sym.isStaticOrInstanceInit()) { + return "[]"; + } + + StringJoiner genericArgumentTypes = new StringJoiner(",", "[", "]"); + if (sym.type == null) { + genericArgumentTypes.add("{undefined}"); + } else { + for (Symbol.VarSymbol parameter : sym.getParameters()) { + genericArgumentTypes.add(signature(parameter.type)); + } + } + return genericArgumentTypes.toString(); + } + + private String methodArgumentSignature(Type selectType) { + if (selectType instanceof Type.MethodType) { + StringJoiner resolvedArgumentTypes = new StringJoiner(",", "[", "]"); + Type.MethodType mt = (Type.MethodType) selectType; + if (!mt.argtypes.isEmpty()) { + for (Type argtype : mt.argtypes) { + if (argtype != null) { + resolvedArgumentTypes.add(signature(argtype)); + } + } + } + return resolvedArgumentTypes.toString(); + } else if (selectType instanceof Type.ForAll) { + return methodArgumentSignature(((Type.ForAll) selectType).qtype); + } else if (selectType instanceof Type.JCNoType || selectType instanceof Type.UnknownType) { + return "{undefined}"; + } + + throw new UnsupportedOperationException("Unexpected method type " + selectType.getClass().getName()); + } + + public String variableSignature(Symbol symbol) { + String owner; + if (symbol.owner instanceof Symbol.MethodSymbol) { + owner = methodSignature((Symbol.MethodSymbol) symbol.owner); + } else { + owner = signature(symbol.owner.type); + if (owner.contains("<")) { + owner = owner.substring(0, owner.indexOf('<')); + } + } + + return owner + "{name=" + symbol.name.toString() + ",type=" + signature(symbol.type) + '}'; + } +} diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/package-info.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/package-info.java new file mode 100644 index 00000000000..dfc8b441031 --- /dev/null +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + */ +@NonNullApi +@NonNullFields +package org.openrewrite.java.isolated; + +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-java-8/build.gradle.kts b/rewrite-java-8/build.gradle.kts index d041ca78a14..c37644b3a47 100644 --- a/rewrite-java-8/build.gradle.kts +++ b/rewrite-java-8/build.gradle.kts @@ -62,7 +62,7 @@ testing { all { testTask.configure { useJUnitPlatform { - excludeTags("java11", "java17") + excludeTags("java11", "java17", "java21") } jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) @@ -75,4 +75,4 @@ testing { tasks.named("check") { dependsOn(testing.suites.named("compatibilityTest")) -} +} \ No newline at end of file diff --git a/rewrite-java-tck/README.md b/rewrite-java-tck/README.md index a1fc89d1270..14df4584293 100644 --- a/rewrite-java-tck/README.md +++ b/rewrite-java-tck/README.md @@ -10,14 +10,15 @@ Dependency relationships between parser implementations and the TCK: classDiagram `rewrite-java-tck` ..> `rewrite-java`: implementation `rewrite-java-tck` ..> `rewrite-java-test`: implementation +`rewrite-java-21` ..> `rewrite-java-tck` : compatibilityTest classpath `rewrite-java-17` ..> `rewrite-java-tck` : compatibilityTest classpath `rewrite-java-11` ..> `rewrite-java-tck` : compatibilityTest classpath `rewrite-java-8` ..> `rewrite-java-tck` : compatibilityTest classpath `rewrite-groovy` ..> `rewrite-java-test` : testImplementation `rewrite-java` ..> `rewrite-java-test` : testImplementation -`rewrite-java-tck` ..> `rewrite-java-17`: testRuntimeOnly +`rewrite-java-tck` ..> `rewrite-java-21`: testRuntimeOnly ``` -* The `testRuntimeOnly` dependency that `rewrite-java-tck` has on `rewrite-java-17` allows us to run these tests in the IDE. +* The `testRuntimeOnly` dependency that `rewrite-java-tck` has on `rewrite-java-21` allows us to run these tests in the IDE. * The `rewrite-java` dependency on `rewrite-java-test` is for testing the Java Reflection type mapping. * `rewrite-java-tck` should be bound to the latest language level supported at any given time. diff --git a/rewrite-java-tck/build.gradle.kts b/rewrite-java-tck/build.gradle.kts index 231c68c6938..f65d17c6798 100644 --- a/rewrite-java-tck/build.gradle.kts +++ b/rewrite-java-tck/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { if (System.getProperty("idea.active") != null || System.getProperty("idea.sync.active") != null) { // so we can run tests in the IDE with the IntelliJ IDEA runner - runtimeOnly(project(":rewrite-java-17")) + runtimeOnly(project(":rewrite-java-21")) } } diff --git a/rewrite-java-test/build.gradle.kts b/rewrite-java-test/build.gradle.kts index 39e9e9d99f1..c1eb20b7dcb 100644 --- a/rewrite-java-test/build.gradle.kts +++ b/rewrite-java-test/build.gradle.kts @@ -9,7 +9,7 @@ dependencies { testImplementation("io.github.classgraph:classgraph:latest.release") testImplementation("org.junit-pioneer:junit-pioneer:2.0.0") - testRuntimeOnly(project(":rewrite-java-17")) + testRuntimeOnly(project(":rewrite-java-21")) testRuntimeOnly("junit:junit:4.13.2") { because("Used for RemoveUnneededAssertionTest") } @@ -29,8 +29,8 @@ tasks.withType<Javadoc> { } tasks.named<JavaCompile>("compileTestJava") { - sourceCompatibility = JavaVersion.VERSION_17.toString() - targetCompatibility = JavaVersion.VERSION_17.toString() + sourceCompatibility = JavaVersion.VERSION_21.toString() + targetCompatibility = JavaVersion.VERSION_21.toString() options.release.set(null as Int?) // remove `--release 8` set in `org.openrewrite.java-base` } diff --git a/rewrite-java/build.gradle.kts b/rewrite-java/build.gradle.kts index 632f09205aa..b0956e671a7 100644 --- a/rewrite-java/build.gradle.kts +++ b/rewrite-java/build.gradle.kts @@ -48,7 +48,7 @@ dependencies { } testImplementation(project(":rewrite-test")) testImplementation(project(":rewrite-java-test")) - testRuntimeOnly(project(":rewrite-java-17")) + testRuntimeOnly(project(":rewrite-java-21")) testImplementation("com.tngtech.archunit:archunit:1.0.1") testImplementation("com.tngtech.archunit:archunit-junit5:1.0.1") diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index d4e0b5ee277..5607d146414 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -221,6 +221,18 @@ static List<Path> dependenciesFromResources(ExecutionContext ctx, String... arti version = 8; } + if (version >= 21) { + try { + javaParser = (JavaParser.Builder<? extends JavaParser, ?>) Class + .forName("org.openrewrite.java.Java21Parser") + .getDeclaredMethod("builder") + .invoke(null); + return javaParser; + } catch (Exception e) { + //Fall through, look for a parser on an older version. + } + } + if (version >= 17) { try { javaParser = (JavaParser.Builder<? extends JavaParser, ?>) Class @@ -253,7 +265,7 @@ static List<Path> dependenciesFromResources(ExecutionContext ctx, String... arti return javaParser; } catch (Exception e) { throw new IllegalStateException("Unable to create a Java parser instance. " + - "`rewrite-java-8`, `rewrite-java-11`, or `rewrite-java-17` must be on the classpath.", e); + "`rewrite-java-8`, `rewrite-java-11`, `rewrite-java-17`, or `rewrite-java-21` must be on the classpath.", e); } } diff --git a/rewrite-maven/build.gradle.kts b/rewrite-maven/build.gradle.kts index 2a9c6f06417..e3df599a387 100755 --- a/rewrite-maven/build.gradle.kts +++ b/rewrite-maven/build.gradle.kts @@ -44,7 +44,7 @@ dependencies { testImplementation("guru.nidi:graphviz-java:latest.release") testRuntimeOnly("org.mapdb:mapdb:latest.release") - testRuntimeOnly(project(":rewrite-java-17")) + testRuntimeOnly(project(":rewrite-java-21")) testRuntimeOnly("org.rocksdb:rocksdbjni:latest.release") } diff --git a/settings.gradle.kts b/settings.gradle.kts index e24fb973fdc..25a352846a1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,7 @@ val allProjects = listOf( "rewrite-java-tck", "rewrite-java-test", "rewrite-java-17", + "rewrite-java-21", "rewrite-json", "rewrite-maven", "rewrite-properties", diff --git a/tools/language-parser-builder/build.gradle.kts b/tools/language-parser-builder/build.gradle.kts index 11e71cf5910..561ca7b251e 100644 --- a/tools/language-parser-builder/build.gradle.kts +++ b/tools/language-parser-builder/build.gradle.kts @@ -19,7 +19,7 @@ configurations["modelRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get()) dependencies { compileOnly("org.projectlombok:lombok:latest.release") compileOnly("org.openrewrite:rewrite-test") - implementation("org.openrewrite:rewrite-java-17") + implementation("org.openrewrite:rewrite-java-21") implementation(platform("org.openrewrite.recipe:rewrite-recipe-bom:latest.release")) modelAnnotationProcessor("org.projectlombok:lombok:latest.release") From 4a0c3a8531e823b3aff57c50d95c7e1768c5b965 Mon Sep 17 00:00:00 2001 From: Simon Verhoeven <verhoeven.simon@gmail.com> Date: Sun, 15 Oct 2023 15:14:13 +0200 Subject: [PATCH 313/447] chore: update comment about resilience4j (#3618) --- rewrite-maven/build.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rewrite-maven/build.gradle.kts b/rewrite-maven/build.gradle.kts index e3df599a387..e74d2dac57c 100755 --- a/rewrite-maven/build.gradle.kts +++ b/rewrite-maven/build.gradle.kts @@ -16,8 +16,7 @@ dependencies { implementation("com.github.ben-manes.caffeine:caffeine:2.+") implementation("org.antlr:antlr4:4.11.1") - // FIXME: switch to `latest.release` - // when https://github.com/resilience4j/resilience4j/issues/1472 is resolved + // Use 1.7.0 due https://github.com/resilience4j/resilience4j/issues/1472, it has been resolved in Resilience4j 2.x, but that requires Java 17 implementation("io.github.resilience4j:resilience4j-retry:1.7.0") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-smile") From 70d9106f64a003da876cf2af458fd9619e5aba51 Mon Sep 17 00:00:00 2001 From: Simon Verhoeven <verhoeven.simon@gmail.com> Date: Sun, 15 Oct 2023 15:14:34 +0200 Subject: [PATCH 314/447] feat: Update gradle wrapper to 8.4 (#3617) --- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 29 +++++++++++++---------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch delta 41154 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%0DEqyP8OADCy^l*0 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 QNgOGU6!F)$^GoUf0Ea$J+W-In delta 39065 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c30b486a892..3fa8f862f75 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 79a61d421cc..1aa94a42690 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ From d53b36febdc9ba609ae64295bcaa54b9fe4169b8 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sun, 15 Oct 2023 15:14:46 +0200 Subject: [PATCH 315/447] Fix the indentation of parameterized chained method calls (#3616) Make sure the formatting in the following code remains as is. ```java class Foo { String test() { return "foobar" .substring( 1 ).substring( 2 ).substring( 3 ); } } ``` --- .../java/format/TabsAndIndentsTest.java | 74 ++++++++++++++++--- .../java/format/TabsAndIndentsVisitor.java | 44 +++++------ 2 files changed, 85 insertions(+), 33 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java index 024ef3dc51f..acbd72321f5 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java @@ -339,7 +339,7 @@ class Test { Test withData(Object... arg0) { return this; } - + void method(Test t) { t = t.withData(withData() .withData(t @@ -485,7 +485,7 @@ void method(Collection<List<String>> c) { return 0; }) ) - .collect(Collectors.toList()); + .collect(Collectors.toList()); } } """ @@ -507,7 +507,7 @@ void method(Collection<List<String>> c) { 0 ) ) - .collect(Collectors.toList()); + .collect(Collectors.toList()); } } """ @@ -2320,14 +2320,66 @@ void enumConstants() { java( """ public enum WorkflowStatus { - @SuppressWarnings("value1") - VALUE1, - @SuppressWarnings("value2") - VALUE2, - @SuppressWarnings("value3") - VALUE3, - @SuppressWarnings("value4") - VALUE4 + @Nullable + VALUE1 + } + """ + ) + ); + } + + @Test + void longMethodChainWithMultiLineParameters_Correct() { + rewriteRun( + java( + """ + class Foo { + String test() { + return "foobar" + .substring( + 1 + ).substring( + 2 + ).substring( + 3 + ); + } + } + """ + ) + ); + } + + @Test + void longMethodChainWithMultiLineParameters_Incorrect() { + rewriteRun( + java( + """ + class Foo { + String test() { + return "foobar" + .substring( + 1 + ).substring( + 2 + ).substring( + 3 + ); + } + } + """, + """ + class Foo { + String test() { + return "foobar" + .substring( + 1 + ).substring( + 2 + ).substring( + 3 + ); + } } """ ) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java index f61b11520ab..fb4ca5940a2 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java @@ -131,14 +131,23 @@ public Space visitSpace(Space space, Space.Location loc, P p) { return space; } + if (loc == Space.Location.METHOD_SELECT_SUFFIX) { + Integer chainedIndent = getCursor().getParentTreeCursor().getMessage("chainedIndent"); + if (chainedIndent != null) { + getCursor().getParentTreeCursor().putMessage("lastIndent", chainedIndent); + return indentTo(space, chainedIndent, loc); + } + } + int indent = getCursor().getNearestMessage("lastIndent", 0); IndentType indentType = getCursor().getParentOrThrow().getNearestMessage("indentType", IndentType.ALIGN); // block spaces are always aligned to their parent + Object value = getCursor().getValue(); boolean alignBlockPrefixToParent = loc.equals(Space.Location.BLOCK_PREFIX) && space.getWhitespace().contains("\n") && // ignore init blocks. - (getCursor().getValue() instanceof J.Block && !(getCursor().getParentTreeCursor().getValue() instanceof J.Block)); + (value instanceof J.Block && !(getCursor().getParentTreeCursor().getValue() instanceof J.Block)); boolean alignBlockToParent = loc.equals(Space.Location.BLOCK_END) || loc.equals(Space.Location.NEW_ARRAY_INITIALIZER_SUFFIX) || @@ -167,8 +176,10 @@ public Space visitSpace(Space space, Space.Location loc, P p) { } Space s = indentTo(space, indent, loc); - if (!(getCursor().getValue() instanceof JLeftPadded) && !(getCursor().getValue() instanceof J.EnumValueSet)) { + if (value instanceof J && !(value instanceof J.EnumValueSet)) { getCursor().putMessage("lastIndent", indent); + } else if (loc == Space.Location.METHOD_SELECT_SUFFIX) { + getCursor().getParentTreeCursor().putMessage("lastIndent", indent); } return s; @@ -257,6 +268,15 @@ public <T> JRightPadded<T> visitRightPadded(@Nullable JRightPadded<T> right, JRi } elem = visitAndCast(elem, p); after = indentTo(right.getAfter(), indent, loc.getAfterLocation()); + if (!after.getComments().isEmpty() || after.getLastWhitespace().contains("\n")) { + Cursor parent = getCursor().getParentTreeCursor(); + Cursor grandparent = parent.getParentTreeCursor(); + // propagate indentation up in the method chain hierarchy + if (grandparent.getValue() instanceof J.MethodInvocation && ((J.MethodInvocation) grandparent.getValue()).getSelect() == parent.getValue()) { + grandparent.putMessage("lastIndent", indent); + grandparent.putMessage("chainedIndent", indent); + } + } break; case NEW_CLASS_ARGUMENTS: case ARRAY_INDEX: @@ -266,26 +286,6 @@ public <T> JRightPadded<T> visitRightPadded(@Nullable JRightPadded<T> right, JRi after = indentTo(right.getAfter(), indent, loc.getAfterLocation()); break; } - case METHOD_SELECT: { - for (Cursor cursor = getCursor(); ; cursor = cursor.getParentOrThrow()) { - if (cursor.getValue() instanceof JRightPadded) { - cursor = cursor.getParentOrThrow(); - } - if (!(cursor.getValue() instanceof J.MethodInvocation)) { - break; - } - Integer methodIndent = cursor.getNearestMessage("lastIndent"); - if (methodIndent != null) { - indent = methodIndent; - } - } - - getCursor().getParentOrThrow().putMessage("lastIndent", indent); - elem = visitAndCast(elem, p); - after = visitSpace(right.getAfter(), loc.getAfterLocation(), p); - getCursor().getParentOrThrow().putMessage("lastIndent", indent + style.getContinuationIndent()); - break; - } case ANNOTATION_ARGUMENT: JContainer<J> args = getCursor().getParentOrThrow().getValue(); elem = visitAndCast(elem, p); From 68411292985f30565ac5cd9ba3d5b342088c4744 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Mon, 16 Oct 2023 23:26:33 -0600 Subject: [PATCH 316/447] More Java 21 setup --- rewrite-benchmarks/build.gradle.kts | 2 +- rewrite-gradle/build.gradle.kts | 2 +- rewrite-groovy/build.gradle.kts | 2 +- rewrite-java-11/build.gradle.kts | 2 +- rewrite-java-17/build.gradle.kts | 2 +- rewrite-java-21/build.gradle.kts | 36 ++++++++++--------- rewrite-java-tck/README.md | 6 ++-- rewrite-java-tck/build.gradle.kts | 2 +- .../org/openrewrite/java/MinimumJava21.java | 31 ++++++++++++++++ .../org/openrewrite/java/tree/SwitchTest.java | 9 ++--- rewrite-java-test/build.gradle.kts | 6 ++-- rewrite-java/build.gradle.kts | 2 +- .../java/org/openrewrite/java/JavaParser.java | 6 ++-- rewrite-maven/build.gradle.kts | 2 +- settings.gradle.kts | 6 ++-- 15 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 rewrite-java-tck/src/main/java/org/openrewrite/java/MinimumJava21.java diff --git a/rewrite-benchmarks/build.gradle.kts b/rewrite-benchmarks/build.gradle.kts index 323ee326fd2..195fa0a4688 100644 --- a/rewrite-benchmarks/build.gradle.kts +++ b/rewrite-benchmarks/build.gradle.kts @@ -9,7 +9,7 @@ dependencies { jmh("org.projectlombok:lombok:latest.release") jmh(project(":rewrite-core")) - jmh(project(":rewrite-java-21")) + jmh(project(":rewrite-java-17")) jmh(project(":rewrite-maven")) jmh("org.rocksdb:rocksdbjni:latest.release") jmh("org.openjdk.jmh:jmh-core:latest.release") diff --git a/rewrite-gradle/build.gradle.kts b/rewrite-gradle/build.gradle.kts index 941e4493d97..e4a5613e6f3 100644 --- a/rewrite-gradle/build.gradle.kts +++ b/rewrite-gradle/build.gradle.kts @@ -66,7 +66,7 @@ dependencies { testRuntimeOnly(gradleApi()) testRuntimeOnly("com.gradle:gradle-enterprise-gradle-plugin:latest.release") testRuntimeOnly("com.google.guava:guava:latest.release") - testRuntimeOnly(project(":rewrite-java-21")) + testRuntimeOnly(project(":rewrite-java-17")) testRuntimeOnly("org.projectlombok:lombok:latest.release") } diff --git a/rewrite-groovy/build.gradle.kts b/rewrite-groovy/build.gradle.kts index 936bea0c932..743305710d7 100644 --- a/rewrite-groovy/build.gradle.kts +++ b/rewrite-groovy/build.gradle.kts @@ -20,5 +20,5 @@ dependencies { testImplementation(project(":rewrite-java-test")) testImplementation("org.junit-pioneer:junit-pioneer:2.0.0") testRuntimeOnly("org.codehaus.groovy:groovy-all:latest.release") - testRuntimeOnly(project(":rewrite-java-21")) + testRuntimeOnly(project(":rewrite-java-17")) } diff --git a/rewrite-java-11/build.gradle.kts b/rewrite-java-11/build.gradle.kts index b7706712520..d629806b642 100644 --- a/rewrite-java-11/build.gradle.kts +++ b/rewrite-java-11/build.gradle.kts @@ -65,7 +65,7 @@ testing { all { testTask.configure { useJUnitPlatform { - excludeTags("java11", "java17", "java21") + excludeTags("java17", "java21") } jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) diff --git a/rewrite-java-17/build.gradle.kts b/rewrite-java-17/build.gradle.kts index 0cf4421911c..e041d3e8e2e 100644 --- a/rewrite-java-17/build.gradle.kts +++ b/rewrite-java-17/build.gradle.kts @@ -59,7 +59,7 @@ testing { all { testTask.configure { useJUnitPlatform { - excludeTags("java11", "java17", "java21") + excludeTags("java21") } jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) diff --git a/rewrite-java-21/build.gradle.kts b/rewrite-java-21/build.gradle.kts index 4810d3a3e3d..c55a94672b4 100644 --- a/rewrite-java-21/build.gradle.kts +++ b/rewrite-java-21/build.gradle.kts @@ -3,6 +3,12 @@ plugins { id("jvm-test-suite") } +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } +} + dependencies { api(project(":rewrite-core")) api(project(":rewrite-java")) @@ -23,25 +29,25 @@ tasks.withType<JavaCompile> { options.release.set(null as Int?) // remove `--release 8` set in `org.openrewrite.java-base` options.compilerArgs.addAll( - listOf( - "--add-exports", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-exports", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - "--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" - ) + listOf( + "--add-exports", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" + ) ) } //Javadoc compiler will complain about the use of the internal types. tasks.withType<Javadoc> { exclude( - "**/ReloadableJava21JavadocVisitor**", - "**/ReloadableJava21Parser**", - "**/ReloadableJava21ParserVisitor**", - "**/ReloadableJava21TypeMapping**", - "**/ReloadableJava21TypeSignatureBuilder**" + "**/ReloadableJava21JavadocVisitor**", + "**/ReloadableJava21Parser**", + "**/ReloadableJava21ParserVisitor**", + "**/ReloadableJava21TypeMapping**", + "**/ReloadableJava21TypeSignatureBuilder**" ) } @@ -58,9 +64,7 @@ testing { targets { all { testTask.configure { - useJUnitPlatform { - excludeTags("java11", "java17", "java21") - } + useJUnitPlatform() jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) } diff --git a/rewrite-java-tck/README.md b/rewrite-java-tck/README.md index 14df4584293..ec594c26b10 100644 --- a/rewrite-java-tck/README.md +++ b/rewrite-java-tck/README.md @@ -10,15 +10,15 @@ Dependency relationships between parser implementations and the TCK: classDiagram `rewrite-java-tck` ..> `rewrite-java`: implementation `rewrite-java-tck` ..> `rewrite-java-test`: implementation -`rewrite-java-21` ..> `rewrite-java-tck` : compatibilityTest classpath +`rewrite-java-17` ..> `rewrite-java-tck` : compatibilityTest classpath `rewrite-java-17` ..> `rewrite-java-tck` : compatibilityTest classpath `rewrite-java-11` ..> `rewrite-java-tck` : compatibilityTest classpath `rewrite-java-8` ..> `rewrite-java-tck` : compatibilityTest classpath `rewrite-groovy` ..> `rewrite-java-test` : testImplementation `rewrite-java` ..> `rewrite-java-test` : testImplementation -`rewrite-java-tck` ..> `rewrite-java-21`: testRuntimeOnly +`rewrite-java-tck` ..> `rewrite-java-17`: testRuntimeOnly ``` -* The `testRuntimeOnly` dependency that `rewrite-java-tck` has on `rewrite-java-21` allows us to run these tests in the IDE. +* The `testRuntimeOnly` dependency that `rewrite-java-tck` has on `rewrite-java-17` allows us to run these tests in the IDE. * The `rewrite-java` dependency on `rewrite-java-test` is for testing the Java Reflection type mapping. * `rewrite-java-tck` should be bound to the latest language level supported at any given time. diff --git a/rewrite-java-tck/build.gradle.kts b/rewrite-java-tck/build.gradle.kts index f65d17c6798..231c68c6938 100644 --- a/rewrite-java-tck/build.gradle.kts +++ b/rewrite-java-tck/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { if (System.getProperty("idea.active") != null || System.getProperty("idea.sync.active") != null) { // so we can run tests in the IDE with the IntelliJ IDEA runner - runtimeOnly(project(":rewrite-java-21")) + runtimeOnly(project(":rewrite-java-17")) } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/MinimumJava21.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/MinimumJava21.java new file mode 100644 index 00000000000..6cf364ca0c6 --- /dev/null +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/MinimumJava21.java @@ -0,0 +1,31 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java; + +import org.junit.jupiter.api.Tag; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention(RUNTIME) +@Target({TYPE, METHOD}) +@Tag("java21") +public @interface MinimumJava21 { +} diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/SwitchTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/SwitchTest.java index f3292867cf2..15b8aa96b5e 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/SwitchTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/SwitchTest.java @@ -37,11 +37,12 @@ void test() { } } } - """ + """ ) ); } + /** @noinspection SwitchStatementWithTooFewBranches*/ @Test void defaultCase() { rewriteRun( @@ -55,7 +56,7 @@ void test() { } } } - """ + """ ) ); } @@ -71,7 +72,7 @@ void test() { switch(n) {} } } - """ + """ ) ); } @@ -95,7 +96,7 @@ void test() { } } } - """ + """ ) ); } diff --git a/rewrite-java-test/build.gradle.kts b/rewrite-java-test/build.gradle.kts index c1eb20b7dcb..39e9e9d99f1 100644 --- a/rewrite-java-test/build.gradle.kts +++ b/rewrite-java-test/build.gradle.kts @@ -9,7 +9,7 @@ dependencies { testImplementation("io.github.classgraph:classgraph:latest.release") testImplementation("org.junit-pioneer:junit-pioneer:2.0.0") - testRuntimeOnly(project(":rewrite-java-21")) + testRuntimeOnly(project(":rewrite-java-17")) testRuntimeOnly("junit:junit:4.13.2") { because("Used for RemoveUnneededAssertionTest") } @@ -29,8 +29,8 @@ tasks.withType<Javadoc> { } tasks.named<JavaCompile>("compileTestJava") { - sourceCompatibility = JavaVersion.VERSION_21.toString() - targetCompatibility = JavaVersion.VERSION_21.toString() + sourceCompatibility = JavaVersion.VERSION_17.toString() + targetCompatibility = JavaVersion.VERSION_17.toString() options.release.set(null as Int?) // remove `--release 8` set in `org.openrewrite.java-base` } diff --git a/rewrite-java/build.gradle.kts b/rewrite-java/build.gradle.kts index b0956e671a7..632f09205aa 100644 --- a/rewrite-java/build.gradle.kts +++ b/rewrite-java/build.gradle.kts @@ -48,7 +48,7 @@ dependencies { } testImplementation(project(":rewrite-test")) testImplementation(project(":rewrite-java-test")) - testRuntimeOnly(project(":rewrite-java-21")) + testRuntimeOnly(project(":rewrite-java-17")) testImplementation("com.tngtech.archunit:archunit:1.0.1") testImplementation("com.tngtech.archunit:archunit-junit5:1.0.1") diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index 5607d146414..897058bd838 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -264,9 +264,11 @@ static List<Path> dependenciesFromResources(ExecutionContext ctx, String... arti .invoke(null); return javaParser; } catch (Exception e) { - throw new IllegalStateException("Unable to create a Java parser instance. " + - "`rewrite-java-8`, `rewrite-java-11`, `rewrite-java-17`, or `rewrite-java-21` must be on the classpath.", e); + //Fall through to an exception without making this the "cause". } + + throw new IllegalStateException("Unable to create a Java parser instance. " + + "`rewrite-java-8`, `rewrite-java-11`, `rewrite-java-17`, or `rewrite-java-21` must be on the classpath."); } @Override diff --git a/rewrite-maven/build.gradle.kts b/rewrite-maven/build.gradle.kts index e74d2dac57c..dc96e447477 100755 --- a/rewrite-maven/build.gradle.kts +++ b/rewrite-maven/build.gradle.kts @@ -43,7 +43,7 @@ dependencies { testImplementation("guru.nidi:graphviz-java:latest.release") testRuntimeOnly("org.mapdb:mapdb:latest.release") - testRuntimeOnly(project(":rewrite-java-21")) + testRuntimeOnly(project(":rewrite-java-17")) testRuntimeOnly("org.rocksdb:rocksdbjni:latest.release") } diff --git a/settings.gradle.kts b/settings.gradle.kts index 25a352846a1..2a755396f16 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,7 +19,7 @@ val allProjects = listOf( "rewrite-java", "rewrite-java-tck", "rewrite-java-test", - "rewrite-java-17", + "rewrite-java-17", // remove this when rewrite recipe gradle plugin moves to 21 "rewrite-java-21", "rewrite-json", "rewrite-maven", @@ -72,7 +72,9 @@ if (System.getProperty("idea.active") == null && System.getProperty("idea.sync.active") == null) { include( "rewrite-java-8", - "rewrite-java-11" + "rewrite-java-11", + "rewrite-java-17", + "rewrite-java-21" ) } From 974370976a8aaffe22a62a302cbd0074fdf02514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Carpenter=E2=84=A2=EF=B8=8F?= <kevin@moderne.io> Date: Tue, 17 Oct 2023 17:52:42 -0300 Subject: [PATCH 317/447] feat: Make getAccumulator public in ScanningRecipe --- rewrite-core/src/main/java/org/openrewrite/ScanningRecipe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/ScanningRecipe.java b/rewrite-core/src/main/java/org/openrewrite/ScanningRecipe.java index 592cc42db4f..c9e8ac9a6d8 100644 --- a/rewrite-core/src/main/java/org/openrewrite/ScanningRecipe.java +++ b/rewrite-core/src/main/java/org/openrewrite/ScanningRecipe.java @@ -85,7 +85,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor(T acc) { return TreeVisitor.noop(); } - T getAccumulator(Cursor cursor, ExecutionContext ctx) { + public T getAccumulator(Cursor cursor, ExecutionContext ctx) { return cursor.getRoot().computeMessageIfAbsent(recipeAccMessage, m -> getInitialValue(ctx)); } From 3aa4af541646624a05ab0b9bb9472da311663e96 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 09:03:13 +0200 Subject: [PATCH 318/447] Support Groovy `assert` Fixes: #3473 --- .../groovy/GroovyParserVisitor.java | 16 +++++- .../openrewrite/groovy/tree/AssertTest.java | 51 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AssertTest.java diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index 7617e6fd7ed..dc5e58c76f5 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -699,6 +699,20 @@ public void visitClassExpression(ClassExpression clazz) { .withPrefix(sourceBefore(clazz.getType().getUnresolvedName()))); } + @Override + public void visitAssertStatement(AssertStatement statement) { + Space prefix = whitespace(); + skip("assert"); + Expression condition = visit(statement.getBooleanExpression()); + JLeftPadded<Expression> message = null; + if (!(statement.getMessageExpression() instanceof ConstantExpression) || !((ConstantExpression) statement.getMessageExpression()).isNullExpression()) { + Space messagePrefix = whitespace(); + skip(":"); + message = padLeft(messagePrefix, visit(statement.getMessageExpression())); + } + queue.add(new J.Assert(randomId(), prefix, Markers.EMPTY, condition, message)); + } + @Override public void visitBinaryExpression(BinaryExpression binary) { queue.add(insideParentheses(binary, fmt -> { @@ -1145,7 +1159,7 @@ public void visitConstantExpression(ConstantExpression expression) { throw new IllegalStateException("Unexpected constant type " + type); } - if (source.charAt(cursor) == '+' && !text.startsWith("+")) { + if (cursor < source.length() && source.charAt(cursor) == '+' && !text.startsWith("+")) { // A unaryPlus operator is implied on numerics and needs to be manually detected / added via the source. text = "+" + text; } diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AssertTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AssertTest.java new file mode 100644 index 00000000000..8312f0ade47 --- /dev/null +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/AssertTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.groovy.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.Issue; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.groovy.Assertions.groovy; + +@SuppressWarnings({"GroovyUnusedAssignment", "GrUnnecessarySemicolon"}) +class AssertTest implements RewriteTest { + + @Issue("https://github.com/openrewrite/rewrite/issues/3473") + @Test + void basic() { + rewriteRun( + groovy( + """ + def x = 1 + assert x == 2 + """ + ) + ); + } + + @Test + void withMessage() { + rewriteRun( + groovy( + """ + def x = 1 + assert x == 2: "foo" + """ + ) + ); + } +} From 1a4a7d2f0ce61216387e83b30739eb461662d0f8 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 09:52:38 +0200 Subject: [PATCH 319/447] Let GitHub Actions use Java 21 (#3627) --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d50cb1998d0..6b302477b02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,8 @@ concurrency: jobs: build: uses: openrewrite/gh-automation/.github/workflows/ci-gradle.yml@main + with: + java_version: 21 secrets: gradle_enterprise_access_key: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} gradle_enterprise_cache_username: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} From 2e11d908170df4afb2e66907b1a8e0df06fd788e Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 15:13:41 +0200 Subject: [PATCH 320/447] Skip over nested classpath entries in `JavaParser` A jar can in its `MANIFEST.MF` declare the OSGi header `Bundle-Classpath` to list additional folders within the jar to be added separately to the classpath. In ClassGraph this in turn results in corresponding URIs with a `jar` scheme. These are excluded in `dependenciesFromClasspath()` when looking for Maven artifacts. --- .../main/java/org/openrewrite/java/JavaParser.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java index 897058bd838..3f8dc7b09ad 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaParser.java @@ -78,7 +78,6 @@ static List<Path> runtimeClasspath() { * @return A set of paths of jars on the runtime classpath matching the provided artifact names, to the extent such * matching jars can be found. */ - @SuppressWarnings("DuplicateExpressions") static List<Path> dependenciesFromClasspath(String... artifactNames) { List<URI> runtimeClasspath = new ClassGraph().disableNestedJarScanning().getClasspathURIs(); List<Path> artifacts = new ArrayList<>(artifactNames.length); @@ -89,11 +88,15 @@ static List<Path> dependenciesFromClasspath(String... artifactNames) { Pattern explodedPattern = Pattern.compile("/" + artifactName + "/"); boolean lacking = true; for (URI cpEntry : runtimeClasspath) { + if (!"file".equals(cpEntry.getScheme())) { + // exclude any `jar` entries which could result from `Bundle-ClassPath` in `MANIFEST.MF` + continue; + } String cpEntryString = cpEntry.toString(); + Path path = Paths.get(cpEntry); if (jarPattern.matcher(cpEntryString).find() || - (explodedPattern.matcher(cpEntryString).find() && - Paths.get(cpEntry).toFile().isDirectory())) { - artifacts.add(Paths.get(cpEntry)); + explodedPattern.matcher(cpEntryString).find() && path.toFile().isDirectory()) { + artifacts.add(path); lacking = false; // Do not break because jarPattern matches "foo-bar-1.0.jar" and "foo-1.0.jar" to "foo" } @@ -268,7 +271,7 @@ static List<Path> dependenciesFromResources(ExecutionContext ctx, String... arti } throw new IllegalStateException("Unable to create a Java parser instance. " + - "`rewrite-java-8`, `rewrite-java-11`, `rewrite-java-17`, or `rewrite-java-21` must be on the classpath."); + "`rewrite-java-8`, `rewrite-java-11`, `rewrite-java-17`, or `rewrite-java-21` must be on the classpath."); } @Override From 212f9a10539fa8ce3df1c465287504eaf28e2ebc Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 16:24:29 +0200 Subject: [PATCH 321/447] Don't explicitly instantiate `AutoFormatVisitor` Instead use `autoFormat()` or `maybeAutoFormat()` which will delegate to the appropriate implementation (e.g. for Kotlin). --- .../java/cleanup/SimplifyBooleanExpressionVisitor.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java index a88312bed0f..4be59e179b4 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java @@ -23,7 +23,6 @@ import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.UnwrapParentheses; -import org.openrewrite.java.format.AutoFormatVisitor; import org.openrewrite.java.tree.*; import java.util.Collections; @@ -125,8 +124,8 @@ public J visitBinary(J.Binary binary, ExecutionContext ctx) { @Override public J postVisit(J tree, ExecutionContext ctx) { J j = super.postVisit(tree, ctx); - if (getCursor().pollMessage(MAYBE_AUTO_FORMAT_ME) != null) { - j = new AutoFormatVisitor<>().visit(j, ctx, getCursor().getParentOrThrow()); + if (j != null && getCursor().pollMessage(MAYBE_AUTO_FORMAT_ME) != null) { + j = autoFormat(j, ctx); } return j; } From 88493fb1dca4461e6ebe9bf8e05ea078f4cdbb04 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 17:26:05 +0200 Subject: [PATCH 322/447] Maven error handler should not process its own exception (#3628) When the error handler given to `MavenArtifactDownloader` handles errors by throwing an exception, then the current logic will have this error handler handle its own exception, because it basically contains two nested `try-catch` statements. This commit removes the inner `try-catch` as it doesn't offer any additional resource closing or similar. --- .../utilities/MavenArtifactDownloader.java | 59 +++++++++---------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java index 646c21213c1..9f4987bb3b7 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java @@ -91,43 +91,38 @@ public Path downloadArtifact(ResolvedDependency dependency) { return null; } return mavenArtifactCache.computeArtifact(dependency, () -> { - try { - String uri = requireNonNull(dependency.getRepository(), - String.format("Repository for dependency '%s' was null.", dependency)).getUri() + "/" + - dependency.getGroupId().replace('.', '/') + '/' + - dependency.getArtifactId() + '/' + - dependency.getVersion() + '/' + - dependency.getArtifactId() + '-' + - (dependency.getDatedSnapshotVersion() == null ? dependency.getVersion() : dependency.getDatedSnapshotVersion()) + - ".jar"; + String uri = requireNonNull(dependency.getRepository(), + String.format("Repository for dependency '%s' was null.", dependency)).getUri() + "/" + + dependency.getGroupId().replace('.', '/') + '/' + + dependency.getArtifactId() + '/' + + dependency.getVersion() + '/' + + dependency.getArtifactId() + '-' + + (dependency.getDatedSnapshotVersion() == null ? dependency.getVersion() : dependency.getDatedSnapshotVersion()) + + ".jar"; - InputStream bodyStream; + InputStream bodyStream; - if (uri.startsWith("~")) { - bodyStream = Files.newInputStream(Paths.get(System.getProperty("user.home") + uri.substring(1))); - } else if ("file".equals(URI.create(uri).getScheme())) { - bodyStream = Files.newInputStream(Paths.get(URI.create(uri))); - } else { - HttpSender.Request.Builder request = applyAuthentication(dependency.getRepository(), httpSender.get(uri)); - try (HttpSender.Response response = sendRequest.apply(request.build()); - InputStream body = response.getBody()) { - if (!response.isSuccessful() || body == null) { - onError.accept(new MavenDownloadingException(String.format("Unable to download dependency %s:%s:%s. Response was %d", - dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), response.getCode()), null, - dependency.getRequested().getGav())); - return null; - } - bodyStream = new ByteArrayInputStream(readAllBytes(body)); - } catch (Throwable t) { - throw new MavenDownloadingException("Unable to download dependency", t, - dependency.getRequested().getGav()); + if (uri.startsWith("~")) { + bodyStream = Files.newInputStream(Paths.get(System.getProperty("user.home") + uri.substring(1))); + } else if ("file".equals(URI.create(uri).getScheme())) { + bodyStream = Files.newInputStream(Paths.get(URI.create(uri))); + } else { + HttpSender.Request.Builder request = applyAuthentication(dependency.getRepository(), httpSender.get(uri)); + try (HttpSender.Response response = sendRequest.apply(request.build()); + InputStream body = response.getBody()) { + if (!response.isSuccessful() || body == null) { + onError.accept(new MavenDownloadingException(String.format("Unable to download dependency %s:%s:%s. Response was %d", + dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), response.getCode()), null, + dependency.getRequested().getGav())); + return null; } + bodyStream = new ByteArrayInputStream(readAllBytes(body)); + } catch (Throwable t) { + throw new MavenDownloadingException("Unable to download dependency", t, + dependency.getRequested().getGav()); } - return bodyStream; - } catch (Throwable t) { - onError.accept(t); } - return null; + return bodyStream; }, onError); } From fcfe4733c19f36818b37bf570997cc5ddb96db9a Mon Sep 17 00:00:00 2001 From: Surav Shrestha <98219089+suravshresth@users.noreply.github.com> Date: Wed, 18 Oct 2023 22:18:00 +0545 Subject: [PATCH 323/447] fix typo in ImportLayoutStyle.java comment (#3630) --- .../main/java/org/openrewrite/java/style/ImportLayoutStyle.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/style/ImportLayoutStyle.java b/rewrite-java/src/main/java/org/openrewrite/java/style/ImportLayoutStyle.java index ad34b98063a..c3b020d8aef 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/style/ImportLayoutStyle.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/style/ImportLayoutStyle.java @@ -280,7 +280,7 @@ public List<JRightPadded<J.Import>> addImport(List<JRightPadded<J.Import>> origi null; } else if (finalAfter != null && anImport.getElement().isScope(finalAfter.getElement())) { if (starFold.get()) { - // The added import is always folded, and is the first package occurence in the imports. + // The added import is always folded, and is the first package occurrence in the imports. if (starFoldFrom.get() == starFoldTo.get()) { return Arrays.asList(finalToAdd, finalAfter); } else { From 0b56a1da2d491a02d3a6125edd19c27416120fd4 Mon Sep 17 00:00:00 2001 From: Tracey Yoshima <tracey.yoshima@gmail.com> Date: Wed, 18 Oct 2023 12:40:52 -0600 Subject: [PATCH 324/447] Minor fix: added multiline scalar condition back into YamlParser with test. (#3633) fixes #3632 --- .../main/java/org/openrewrite/yaml/YamlParser.java | 3 +++ .../org/openrewrite/yaml/tree/MappingTest.java | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index 6245fcf1799..bd60f386c5b 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -241,6 +241,9 @@ private Yaml.Documents parseFromInput(Path sourceFile, EncodingDetectingInputStr case PLAIN: default: style = Yaml.Scalar.Style.PLAIN; + if (!scalarValue.startsWith("@") && event.getStartMark().getIndex() >= reader.getBufferIndex()) { + scalarValue = reader.readStringFromBuffer(event.getStartMark().getIndex(), event.getEndMark().getIndex() - 1); + } break; } BlockBuilder builder = blockStack.isEmpty() ? null : blockStack.peek(); diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java index 972e3fd2a36..5252ce05d24 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/MappingTest.java @@ -229,6 +229,20 @@ void suffixBeforeColon() { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/3632") + @Test + void multilineScalar() { + rewriteRun( + yaml( + """ + data: &anc | + @this is a long string@ + bar: *anc + """ + ) + ); + } + @Test void literals() { rewriteRun( From a9e347e2de62f38dcc2a20d262e6880ebb59fbaa Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 20:52:00 +0200 Subject: [PATCH 325/447] Simplify `SimplifyBooleanExpressionVisitor` --- .../SimplifyBooleanExpressionVisitorTest.java | 2 +- .../SimplifyBooleanExpressionVisitor.java | 39 ++----------------- 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java index 49c15245cb7..0829d278da2 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java @@ -315,7 +315,7 @@ void autoFormatIsConditionallyApplied() { public class A { { boolean a=true; - boolean i=(a!=true); + boolean i=a!=true; } } """ diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java index 4be59e179b4..bbfb51e0752 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java @@ -15,7 +15,6 @@ */ package org.openrewrite.java.cleanup; -import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.Tree; import org.openrewrite.internal.lang.Nullable; @@ -52,49 +51,37 @@ public J visitBinary(J.Binary binary, ExecutionContext ctx) { if (asBinary.getOperator() == J.Binary.Type.And) { if (isLiteralFalse(asBinary.getLeft())) { - maybeUnwrapParentheses(); j = asBinary.getLeft(); } else if (isLiteralFalse(asBinary.getRight())) { - maybeUnwrapParentheses(); j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); } else if (isLiteralTrue(asBinary.getLeft())) { - maybeUnwrapParentheses(); j = asBinary.getRight(); } else if (isLiteralTrue(asBinary.getRight())) { - maybeUnwrapParentheses(); j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace("")); } else if (removeAllSpace(asBinary.getLeft()).printTrimmed(getCursor()) .equals(removeAllSpace(asBinary.getRight()).printTrimmed(getCursor()))) { - maybeUnwrapParentheses(); j = asBinary.getLeft(); } } else if (asBinary.getOperator() == J.Binary.Type.Or) { if (isLiteralTrue(asBinary.getLeft())) { - maybeUnwrapParentheses(); j = asBinary.getLeft(); } else if (isLiteralTrue(asBinary.getRight())) { - maybeUnwrapParentheses(); j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); } else if (isLiteralFalse(asBinary.getLeft())) { - maybeUnwrapParentheses(); j = asBinary.getRight(); } else if (isLiteralFalse(asBinary.getRight())) { - maybeUnwrapParentheses(); j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace("")); } else if (removeAllSpace(asBinary.getLeft()).printTrimmed(getCursor()) .equals(removeAllSpace(asBinary.getRight()).printTrimmed(getCursor()))) { - maybeUnwrapParentheses(); j = asBinary.getLeft(); } } else if (asBinary.getOperator() == J.Binary.Type.Equal) { if (isLiteralTrue(asBinary.getLeft())) { if (shouldSimplifyEqualsOn(asBinary.getRight())) { - maybeUnwrapParentheses(); j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); } } else if (isLiteralTrue(asBinary.getRight())) { if (shouldSimplifyEqualsOn(asBinary.getLeft())) { - maybeUnwrapParentheses(); j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace(" ")); } } else { @@ -103,12 +90,10 @@ public J visitBinary(J.Binary binary, ExecutionContext ctx) { } else if (asBinary.getOperator() == J.Binary.Type.NotEqual) { if (isLiteralFalse(asBinary.getLeft())) { if (shouldSimplifyEqualsOn(asBinary.getRight())) { - maybeUnwrapParentheses(); j = asBinary.getRight().withPrefix(asBinary.getRight().getPrefix().withWhitespace("")); } } else if (isLiteralFalse(asBinary.getRight())) { if (shouldSimplifyEqualsOn(asBinary.getLeft())) { - maybeUnwrapParentheses(); j = asBinary.getLeft().withPrefix(asBinary.getLeft().getPrefix().withWhitespace(" ")); } } else { @@ -124,6 +109,9 @@ public J visitBinary(J.Binary binary, ExecutionContext ctx) { @Override public J postVisit(J tree, ExecutionContext ctx) { J j = super.postVisit(tree, ctx); + if (j instanceof J.Parentheses) { + j = new UnwrapParentheses<>((J.Parentheses<?>) j).visit(j, ctx, getCursor().getParentOrThrow()); + } if (j != null && getCursor().pollMessage(MAYBE_AUTO_FORMAT_ME) != null) { j = autoFormat(j, ctx); } @@ -137,13 +125,10 @@ public J visitUnary(J.Unary unary, ExecutionContext ctx) { if (asUnary.getOperator() == J.Unary.Type.Not) { if (isLiteralTrue(asUnary.getExpression())) { - maybeUnwrapParentheses(); j = ((J.Literal) asUnary.getExpression()).withValue(false).withValueSource("false"); } else if (isLiteralFalse(asUnary.getExpression())) { - maybeUnwrapParentheses(); j = ((J.Literal) asUnary.getExpression()).withValue(true).withValueSource("true"); } else if (asUnary.getExpression() instanceof J.Unary && ((J.Unary) asUnary.getExpression()).getOperator() == J.Unary.Type.Not) { - maybeUnwrapParentheses(); j = ((J.Unary) asUnary.getExpression()).getExpression(); } } @@ -163,27 +148,11 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext execu if (isEmpty.matches(asMethod) && select instanceof J.Literal && select.getType() == JavaType.Primitive.String) { - maybeUnwrapParentheses(); return booleanLiteral(method, J.Literal.isLiteralValue(select, "")); } return j; } - /** - * Specifically for removing immediately-enclosing parentheses on Identifiers and Literals. - * This queues a potential unwrap operation for the next visit. After unwrapping something, it's possible - * there are more Simplifications this recipe can identify and perform, which is why visitCompilationUnit - * checks for any changes to the entire Compilation Unit, and if so, queues up another SimplifyBooleanExpression - * recipe call. This convergence loop eventually reconciles any remaining Boolean Expression Simplifications - * the recipe can perform. - */ - private void maybeUnwrapParentheses() { - Cursor c = getCursor().getParentOrThrow().getParentTreeCursor(); - if (c.getValue() instanceof J.Parentheses) { - doAfterVisit(new UnwrapParentheses<>(c.getValue())); - } - } - private boolean isLiteralTrue(@Nullable Expression expression) { return expression instanceof J.Literal && ((J.Literal) expression).getValue() == Boolean.valueOf(true); } @@ -207,13 +176,11 @@ private J maybeReplaceCompareWithNull(J.Binary asBinary, boolean valueIfEqual) { boolean leftIsNull = isNullLiteral(left); boolean rightIsNull = isNullLiteral(right); if (leftIsNull && rightIsNull) { - maybeUnwrapParentheses(); return booleanLiteral(asBinary, valueIfEqual); } boolean leftIsNonNullLiteral = isNonNullLiteral(left); boolean rightIsNonNullLiteral = isNonNullLiteral(right); if ((leftIsNull && rightIsNonNullLiteral) || (rightIsNull && leftIsNonNullLiteral)) { - maybeUnwrapParentheses(); return booleanLiteral(asBinary, !valueIfEqual); } From b0dd92436068f716f12f0fb3437ade9e2fbce4f9 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 21:12:13 +0200 Subject: [PATCH 326/447] Support simplifying negated binary expressions --- .../SimplifyBooleanExpressionVisitorTest.java | 56 +++++++++++++++++++ .../SimplifyBooleanExpressionVisitor.java | 44 ++++++++++++--- 2 files changed, 91 insertions(+), 9 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java index 0829d278da2..4888697b3ed 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitorTest.java @@ -123,6 +123,62 @@ public class A { ); } + @Test + void doubleNegationWithParentheses() { + rewriteRun( + java( + """ + public class A { + { + boolean a = !!(!(!true)); + boolean b = !(!a); + } + } + """, + """ + public class A { + { + boolean a = true; + boolean b = a; + } + } + """ + ) + ); + } + + @Test + void doubleNegatedBinaryWithParentheses() { + rewriteRun( + java( + """ + public class A { + { + boolean a1 = !(1 == 1); + boolean a2 = !(1 != 1); + boolean a3 = !(1 < 1); + boolean a4 = !(1 <= 1); + boolean a5 = !(1 > 1); + boolean a6 = !(1 >= 1); + } + } + """, + """ + public class A { + { + boolean a1 = 1 != 1; + boolean a2 = 1 == 1; + boolean a3 = 1 >= 1; + boolean a4 = 1 > 1; + boolean a5 = 1 <= 1; + boolean a6 = 1 < 1; + } + } + """ + ) + ); + } + @Test void simplifyEqualsLiteralTrue() { rewriteRun( diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java index bbfb51e0752..11af27c4f31 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java @@ -108,7 +108,7 @@ public J visitBinary(J.Binary binary, ExecutionContext ctx) { @Override public J postVisit(J tree, ExecutionContext ctx) { - J j = super.postVisit(tree, ctx); + J j = tree; if (j instanceof J.Parentheses) { j = new UnwrapParentheses<>((J.Parentheses<?>) j).visit(j, ctx, getCursor().getParentOrThrow()); } @@ -124,12 +124,19 @@ public J visitUnary(J.Unary unary, ExecutionContext ctx) { J.Unary asUnary = (J.Unary) j; if (asUnary.getOperator() == J.Unary.Type.Not) { - if (isLiteralTrue(asUnary.getExpression())) { - j = ((J.Literal) asUnary.getExpression()).withValue(false).withValueSource("false"); - } else if (isLiteralFalse(asUnary.getExpression())) { - j = ((J.Literal) asUnary.getExpression()).withValue(true).withValueSource("true"); - } else if (asUnary.getExpression() instanceof J.Unary && ((J.Unary) asUnary.getExpression()).getOperator() == J.Unary.Type.Not) { - j = ((J.Unary) asUnary.getExpression()).getExpression(); + Expression expr = asUnary.getExpression(); + if (isLiteralTrue(expr)) { + j = ((J.Literal) expr).withValue(false).withValueSource("false"); + } else if (isLiteralFalse(expr)) { + j = ((J.Literal) expr).withValue(true).withValueSource("true"); + } else if (expr instanceof J.Unary && ((J.Unary) expr).getOperator() == J.Unary.Type.Not) { + j = ((J.Unary) expr).getExpression(); + } else if (expr instanceof J.Parentheses && ((J.Parentheses<?>) expr).getTree() instanceof J.Binary) { + J.Binary binary = (J.Binary) ((J.Parentheses<?>) expr).getTree(); + J.Binary.Type negated = negate(binary.getOperator()); + if (negated != binary.getOperator()) { + j = binary.withOperator(negated); + } } } if (asUnary != j) { @@ -138,6 +145,25 @@ public J visitUnary(J.Unary unary, ExecutionContext ctx) { return j; } + private J.Binary.Type negate(J.Binary.Type operator) { + switch (operator) { + case LessThan: + return J.Binary.Type.GreaterThanOrEqual; + case GreaterThan: + return J.Binary.Type.LessThanOrEqual; + case LessThanOrEqual: + return J.Binary.Type.GreaterThan; + case GreaterThanOrEqual: + return J.Binary.Type.LessThan; + case Equal: + return J.Binary.Type.NotEqual; + case NotEqual: + return J.Binary.Type.Equal; + default: + return operator; + } + } + private final MethodMatcher isEmpty = new MethodMatcher("java.lang.String isEmpty()"); @Override @@ -210,10 +236,10 @@ public Space visitSpace(Space space, Space.Location loc, Integer integer) { /** * Override this method to disable simplification of equals expressions, * specifically for Kotlin while that is not yet part of the OpenRewrite/rewrite. - * + * <p> * Comparing Kotlin nullable type `?` with tree/false can not be simplified, * e.g. `X?.fun() == true` is not equivalent to `X?.fun()` - * + * <p> * Subclasses will want to check if the `org.openrewrite.kotlin.marker.IsNullSafe` * marker is present. * From 674b8692daf18b12155abf107a546136a238010d Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 21:52:24 +0200 Subject: [PATCH 327/447] Further simplify `SimplifyBooleanExpressionVisitor` --- .../SimplifyBooleanExpressionVisitor.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java index 11af27c4f31..15b367c5c59 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java @@ -22,28 +22,16 @@ import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.UnwrapParentheses; -import org.openrewrite.java.tree.*; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; import java.util.Collections; -import static java.util.Objects.requireNonNull; - public class SimplifyBooleanExpressionVisitor extends JavaVisitor<ExecutionContext> { private static final String MAYBE_AUTO_FORMAT_ME = "MAYBE_AUTO_FORMAT_ME"; - @Override - public J visit(@Nullable Tree tree, ExecutionContext ctx) { - if (tree instanceof JavaSourceFile) { - JavaSourceFile cu = (JavaSourceFile) requireNonNull(super.visit(tree, ctx)); - if (tree != cu) { - // recursive simplification - cu = (JavaSourceFile) visitNonNull(cu, ctx); - } - return cu; - } - return super.visit(tree, ctx); - } - @Override public J visitBinary(J.Binary binary, ExecutionContext ctx) { J j = super.visitBinary(binary, ctx); From 5e08ca489750ab5723644e2de9afbfde9e72115e Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 21:52:39 +0200 Subject: [PATCH 328/447] Add Groovy tests for `SimplifyBooleanExpressionVisitor` --- .../SimplifyBooleanExpressionVisitorTest.java | 479 ++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 rewrite-groovy/src/test/java/org/openrewrite/groovy/cleanup/SimplifyBooleanExpressionVisitorTest.java diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/cleanup/SimplifyBooleanExpressionVisitorTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/cleanup/SimplifyBooleanExpressionVisitorTest.java new file mode 100644 index 00000000000..6dbef65be72 --- /dev/null +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/cleanup/SimplifyBooleanExpressionVisitorTest.java @@ -0,0 +1,479 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.groovy.cleanup; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.java.cleanup.SimplifyBooleanExpressionVisitor; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.groovy.Assertions.groovy; +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.RewriteTest.toRecipe; + +@SuppressWarnings("ALL") +class SimplifyBooleanExpressionVisitorTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(toRecipe(() -> new SimplifyBooleanExpressionVisitor())); + } + + @DocumentExample + @Test + void simplifyEqualsLiteralTrueIf() { + rewriteRun( + groovy( + """ + class A { + boolean a + def m() { + if (true == a) { + } + } + } + """, + """ + class A { + boolean a + def m() { + if (a) { + } + } + } + """ + ) + ); + } + + @Test + void simplifyBooleanExpressionComprehensive() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean a = !false + boolean b = (a == true) + boolean c = b || true + boolean d = c || c + boolean e = d && d + boolean f = (e == true) || e + boolean g = f && false + boolean h = g + boolean i = (a != false) + } + } + """, + """ + class A { + def m() { + boolean a = true + boolean b = a + boolean c = true + boolean d = c + boolean e = d + boolean f = e + boolean g = false + boolean h = g + boolean i = a + } + } + """ + ) + ); + } + + @Test + void simplifyInvertedBooleanLiteral() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean a = !false + boolean b = !true + } + } + """, + """ + class A { + def m() { + boolean a = true + boolean b = false + } + } + """ + ) + ); + } + + @Test + void doubleNegationWithParentheses() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean a = !!(!(!true)) + boolean b = !(!a) + } + } + """, + """ + class A { + def m() { + boolean a = true + boolean b = a + } + } + """ + ) + ); + } + + @Test + void doubleNegatedBinaryWithParentheses() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean a1 = !(1 == 1) + boolean a2 = !(1 != 1) + boolean a3 = !(1 < 1) + boolean a4 = !(1 <= 1) + boolean a5 = !(1 > 1) + boolean a6 = !(1 >= 1) + } + } + """, + """ + class A { + def m() { + boolean a1 = 1 != 1 + boolean a2 = 1 == 1 + boolean a3 = 1 >= 1 + boolean a4 = 1 > 1 + boolean a5 = 1 <= 1 + boolean a6 = 1 < 1 + } + } + """ + ) + ); + } + + @Test + void simplifyEqualsLiteralTrue() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean a = true + boolean b = (a == true) + } + } + """, + """ + class A { + def m() { + boolean a = true + boolean b = a + } + } + """ + ) + ); + } + + @Test + void simplifyOrLiteralTrue() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean b = true + boolean c = b || true + } + } + """, + """ + class A { + def m() { + boolean b = true + boolean c = true + } + } + """ + ) + ); + } + + @Test + void simplifyOrAlwaysTrue() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean c = true + boolean d = c || c + } + } + """, + """ + class A { + def m() { + boolean c = true + boolean d = c + } + } + """ + ) + ); + } + + @Test + void simplifyAndAlwaysTrue() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean d = true + boolean e = d && d + } + } + """, + """ + class A { + def m() { + boolean d = true + boolean e = d + } + } + """ + ) + ); + } + + @Test + void simplifyEqualsLiteralTrueAlwaysTrue() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean e = true + boolean f = (e == true) || e + } + } + """, + """ + class A { + def m() { + boolean e = true + boolean f = e + } + } + """ + ) + ); + } + + @Test + void simplifyLiteralFalseAlwaysFalse() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean f = true + boolean g = f && false + } + } + """, + """ + class A { + def m() { + boolean f = true + boolean g = false + } + } + """ + ) + ); + } + + @Test + void simplifyDoubleNegation() { + rewriteRun( + groovy( + """ + class A { + def doubleNegation(boolean g) { + boolean h = g + } + } + """ + ) + ); + } + + @Test + void simplifyNotEqualsFalse() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean a = true + boolean i = (a != false) + } + } + """, + """ + class A { + def m() { + boolean a = true + boolean i = a + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite/issues/502") + @Test + void autoFormatIsConditionallyApplied() { + rewriteRun( + groovy( + """ + class A { + def m() { + boolean a=true + boolean i=a!=true + } + } + """ + ) + ); + } + + @Test + void binaryOrBothFalse() { + rewriteRun( + groovy( + """ + class A { + def m() { + if (!true || !true) { + System.out.println("") + } + } + } + """, + """ + class A { + def m() { + if (false) { + System.out.println("") + } + } + } + """ + ) + ); + } + + @ParameterizedTest + @Issue("https://github.com/openrewrite/rewrite-templating/issues/28") + // Mimic what would be inserted by a Refaster template using two nullable parameters, with the second one a literal + @CsvSource(delimiterString = "//", textBlock = """ + a == null || a.isEmpty() // a == null || a.isEmpty() + a == null || !a.isEmpty() // a == null || !a.isEmpty() + a != null && a.isEmpty() // a != null && a.isEmpty() + a != null && !a.isEmpty() // a != null && !a.isEmpty() + + "" == null || "".isEmpty() // true + "" == null || !"".isEmpty() // false + "" != null && "".isEmpty() // true + "" != null && !"".isEmpty() // false + + "b" == null || "b".isEmpty() // false + "b" == null || !"b".isEmpty() // true + "b" != null && "b".isEmpty() // false + "b" != null && !"b".isEmpty() // true + + a == null || a.isEmpty() || "" == null || "".isEmpty() // true + a == null || a.isEmpty() || "" == null || !"".isEmpty() // a == null || a.isEmpty() + a == null || a.isEmpty() || "" != null && "".isEmpty() // true + a == null || a.isEmpty() || "" != null && !"".isEmpty() // a == null || a.isEmpty() + a == null || a.isEmpty() && "" == null || "".isEmpty() // true + a == null || a.isEmpty() && "" == null || !"".isEmpty() // a == null + a == null || a.isEmpty() && "" != null && "".isEmpty() // a == null || a.isEmpty() + a == null || a.isEmpty() && "" != null && !"".isEmpty() // a == null + a == null || !a.isEmpty() || "" == null || "".isEmpty() // true + a == null || !a.isEmpty() || "" == null || !"".isEmpty() // a == null || !a.isEmpty() + a == null || !a.isEmpty() || "" != null && "".isEmpty() // true + a == null || !a.isEmpty() || "" != null && !"".isEmpty() // a == null || !a.isEmpty() + a == null || !a.isEmpty() && "" == null || "".isEmpty() // true + a == null || !a.isEmpty() && "" == null || !"".isEmpty() // a == null + a == null || !a.isEmpty() && "" != null && "".isEmpty() // a == null || !a.isEmpty() + a == null || !a.isEmpty() && "" != null && !"".isEmpty() // a == null + + a == null || a.isEmpty() || "b" == null || "b".isEmpty() // a == null || a.isEmpty() + a == null || a.isEmpty() || "b" == null || !"b".isEmpty() // true + a == null || a.isEmpty() || "b" != null && "b".isEmpty() // a == null || a.isEmpty() + a == null || a.isEmpty() || "b" != null && !"b".isEmpty() // true + a == null || a.isEmpty() && "b" == null || "b".isEmpty() // a == null + a == null || a.isEmpty() && "b" == null || !"b".isEmpty() // true + a == null || a.isEmpty() && "b" != null && "b".isEmpty() // a == null + a == null || a.isEmpty() && "b" != null && !"b".isEmpty() // a == null || a.isEmpty() + a == null || !a.isEmpty() || "b" == null || "b".isEmpty() // a == null || !a.isEmpty() + a == null || !a.isEmpty() || "b" == null || !"b".isEmpty() // true + a == null || !a.isEmpty() || "b" != null && "b".isEmpty() // a == null || !a.isEmpty() + a == null || !a.isEmpty() || "b" != null && !"b".isEmpty() // true + a == null || !a.isEmpty() && "b" == null || "b".isEmpty() // a == null + a == null || !a.isEmpty() && "b" == null || !"b".isEmpty() // true + a == null || !a.isEmpty() && "b" != null && "b".isEmpty() // a == null + a == null || !a.isEmpty() && "b" != null && !"b".isEmpty() // a == null || !a.isEmpty() + """) + void simplifyLiteralNull(String before, String after) { + //language=java + String template = """ + class A { + void foo(String a) { + boolean c = %s; + } + } + """; + String beforeJava = template.formatted(before); + if (before.equals(after)) { + rewriteRun(groovy(beforeJava)); + } else { + rewriteRun(groovy(beforeJava, template.formatted(after))); + } + } +} From 2dc4e97e8fc1ba715e36d247172cc968432a481e Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 21:53:01 +0200 Subject: [PATCH 329/447] Improve parsing of Groovy unary expressions --- .../org/openrewrite/groovy/GroovyParserVisitor.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index dc5e58c76f5..989938b26db 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -1199,10 +1199,12 @@ Markers.EMPTY, emptyList(), statement.getLabel(), null, null)) @Override public void visitNotExpression(NotExpression expression) { - Space fmt = sourceBefore("!"); - JLeftPadded<J.Unary.Type> op = padLeft(EMPTY, J.Unary.Type.Not); - Expression expr = visit(expression.getExpression()); - queue.add(new J.Unary(randomId(), fmt, Markers.EMPTY, op, expr, typeMapping.type(expression.getType()))); + queue.add(insideParentheses(expression, fmt -> { + skip("!"); + JLeftPadded<J.Unary.Type> op = padLeft(EMPTY, J.Unary.Type.Not); + Expression expr = visit(expression.getExpression()); + return new J.Unary(randomId(), fmt, Markers.EMPTY, op, expr, typeMapping.type(expression.getType())); + })); } @Override From 62645557507240f575cbc6e2626d1882d8907b6d Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 22:17:56 +0200 Subject: [PATCH 330/447] Fix failing `InvertConditionTest` --- .../src/test/java/org/openrewrite/java/InvertConditionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/InvertConditionTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/InvertConditionTest.java index 8fbf94939b3..01b9fc6dbea 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/InvertConditionTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/InvertConditionTest.java @@ -67,7 +67,7 @@ boolean test() { if(!(b || a)) {} if(1 >= 2) {} if(!b) {} - if(!(b)) {} + if(!b) {} if(!test()) {} if(!this.test()) {} return true; From d2364d74399bf4fadcb0a3d6913eb95b4eef8add Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 23:03:28 +0200 Subject: [PATCH 331/447] Use `UnnecessaryParenthesesVisitor` `UnwrapParentheses` doesn't perform all the necessary checks. Not sure why this hasn't shown up before. --- .../cleanup/UnnecessaryParenthesesTest.java | 23 +++++++++++++++++++ .../SimplifyBooleanExpressionVisitor.java | 11 ++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java index 529f8807f39..11d6d08c5b7 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java @@ -950,4 +950,27 @@ boolean test(String s) { ) ); } + + @Test + @SuppressWarnings("SimplifiableConditionalExpression") + void requiredCast() { + rewriteRun( + java( + """ + class Test { + int test(Object o) { + return ((int[]) o).length; + } + } + """, + """ + class Test { + int test(Object o) { + return ((int[]) o).length; + } + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java index 15b367c5c59..57145f33c93 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/cleanup/SimplifyBooleanExpressionVisitor.java @@ -21,7 +21,6 @@ import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.UnwrapParentheses; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -98,7 +97,7 @@ public J visitBinary(J.Binary binary, ExecutionContext ctx) { public J postVisit(J tree, ExecutionContext ctx) { J j = tree; if (j instanceof J.Parentheses) { - j = new UnwrapParentheses<>((J.Parentheses<?>) j).visit(j, ctx, getCursor().getParentOrThrow()); + j = new UnnecessaryParenthesesVisitor<>().visit(j, ctx, getCursor().getParentOrThrow()); } if (j != null && getCursor().pollMessage(MAYBE_AUTO_FORMAT_ME) != null) { j = autoFormat(j, ctx); @@ -123,7 +122,13 @@ public J visitUnary(J.Unary unary, ExecutionContext ctx) { J.Binary binary = (J.Binary) ((J.Parentheses<?>) expr).getTree(); J.Binary.Type negated = negate(binary.getOperator()); if (negated != binary.getOperator()) { - j = binary.withOperator(negated); + j = binary.withOperator(negated).withPrefix(j.getPrefix()); + } + } else if (expr instanceof J.Parentheses && ((J.Parentheses<?>) expr).getTree() instanceof J.Unary) { + J.Unary unary1 = (J.Unary) ((J.Parentheses<?>) expr).getTree(); + J.Unary.Type operator = unary1.getOperator(); + if (operator == J.Unary.Type.Not) { + j = unary1.getExpression().withPrefix(j.getPrefix()); } } } From 08b330fb163c08e130a7d53bc8e741f98fe0ae92 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 18 Oct 2023 23:05:30 +0200 Subject: [PATCH 332/447] Simplify new test case --- .../java/cleanup/UnnecessaryParenthesesTest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java index 11d6d08c5b7..63c7ae7d71e 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/UnnecessaryParenthesesTest.java @@ -952,17 +952,9 @@ boolean test(String s) { } @Test - @SuppressWarnings("SimplifiableConditionalExpression") void requiredCast() { rewriteRun( java( - """ - class Test { - int test(Object o) { - return ((int[]) o).length; - } - } - """, """ class Test { int test(Object o) { From 71a07c37da35898fc0fcb1b0d2ca6006d6ef9ec1 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 18 Oct 2023 17:22:35 -0700 Subject: [PATCH 333/447] Delete early-access workflow since we now use java 21 by default --- .github/workflows/ci-early-access.yml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/ci-early-access.yml diff --git a/.github/workflows/ci-early-access.yml b/.github/workflows/ci-early-access.yml deleted file mode 100644 index be053c25485..00000000000 --- a/.github/workflows/ci-early-access.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: ci-early-access - -on: - workflow_dispatch: {} - -concurrency: - group: ci-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - uses: openrewrite/gh-automation/.github/workflows/ci-gradle.yml@main - with: - java_version: 21-ea - secrets: - gradle_enterprise_access_key: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} - gradle_enterprise_cache_username: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} - gradle_enterprise_cache_password: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} - ossrh_username: ${{ secrets.OSSRH_USERNAME }} - ossrh_token: ${{ secrets.OSSRH_TOKEN }} - ossrh_signing_key: ${{ secrets.OSSRH_SIGNING_KEY }} - ossrh_signing_password: ${{ secrets.OSSRH_SIGNING_PASSWORD }} From 1f48029b9dfe4a3400d194e65fccfd22b170038d Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 18 Oct 2023 17:22:47 -0700 Subject: [PATCH 334/447] Cleanup warnings --- .../java/org/openrewrite/gradle/AddDependencyTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java index 1615f9dfed6..13664d53840 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java @@ -36,6 +36,7 @@ import static org.openrewrite.java.Assertions.*; import static org.openrewrite.properties.Assertions.properties; +@SuppressWarnings("GroovyUnusedAssignment") class AddDependencyTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { @@ -1117,12 +1118,12 @@ void addDynamicVersionDependency() { srcMainGroovy( groovy( """ - import java.util.*; - + import java.util.* + class MyClass { static void main(String[] args) { - Date date = new Date(); - System.out.println("Hello world"); + Date date = new Date() + System.out.println("Hello world") } } """ From 94a178639a1ddda66f2e4cf42136d53d9fc19823 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 18 Oct 2023 17:23:30 -0700 Subject: [PATCH 335/447] IncrementProjectVersion also updates parent references within the same multi-module project --- .../maven/IncrementProjectVersion.java | 114 ++++++++++++------ .../maven/IncrementProjectVersionTest.java | 49 ++++++++ 2 files changed, 124 insertions(+), 39 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java index 7d5480157e1..259f82aca95 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java @@ -19,12 +19,13 @@ import lombok.Value; import org.openrewrite.*; import org.openrewrite.maven.marker.AlreadyIncremented; +import org.openrewrite.maven.tree.GroupArtifact; import org.openrewrite.maven.tree.ResolvedPom; import org.openrewrite.xml.ChangeTagValue; import org.openrewrite.xml.XPathMatcher; import org.openrewrite.xml.tree.Xml; -import java.util.Optional; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -33,7 +34,7 @@ @Value @EqualsAndHashCode(callSuper = true) -public class IncrementProjectVersion extends Recipe { +public class IncrementProjectVersion extends ScanningRecipe<Map<GroupArtifact, String>> { @Override public String getDisplayName() { @@ -69,21 +70,25 @@ public enum SemverDigit { } private static final Pattern SEMVER_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)\\.?(\\d+)?(-.+)?$"); + private static final XPathMatcher PROJECT_MATCHER = new XPathMatcher("/project"); + private static final XPathMatcher PARENT_MATCHER = new XPathMatcher("/project/parent"); @Override - public TreeVisitor<?, ExecutionContext> getVisitor() { - return new MavenIsoVisitor<ExecutionContext>() { - private final XPathMatcher PROJECT_MATCHER = new XPathMatcher("/project"); + public Map<GroupArtifact, String> getInitialValue(ExecutionContext ctx) { + return new HashMap<>(); + } + @Override + public TreeVisitor<?, ExecutionContext> getScanner(Map<GroupArtifact, String> acc) { + return new MavenIsoVisitor<ExecutionContext>() { @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { Xml.Tag t = super.visitTag(tag, ctx); - if (!PROJECT_MATCHER.matches(getCursor()) || t.getMarkers().findFirst(AlreadyIncremented.class).isPresent()) { + if (!PROJECT_MATCHER.matches(getCursor())) { return t; } ResolvedPom resolvedPom = getResolutionResult().getPom(); - if (!(matchesGlob(resolvedPom.getValue(t.getChildValue("groupId").orElse(null)), groupId) && matchesGlob(resolvedPom.getValue(t.getChildValue("artifactId").orElse(null)), artifactId))) { return t; @@ -97,44 +102,75 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { if(oldVersion == null) { return t; } - - Matcher m = SEMVER_PATTERN.matcher(oldVersion); - if(!m.matches()) { + String newVersion = incrementSemverDigit(oldVersion); + if(newVersion.equals(oldVersion)) { return t; } - String major = m.group(1); - String minor = m.group(2); - String patch = m.group(3); - // Semver does not have a concept of a fourth number, but it is common enough to support - String fourth = m.group(4); - String extra = m.group(5); - switch (digit) { - case MAJOR: - major = String.valueOf(Integer.parseInt(major) + 1); - minor = "0"; - patch = "0"; - break; - case MINOR: - minor = String.valueOf(Integer.parseInt(minor) + 1); - patch = "0"; - break; - case PATCH: - patch = String.valueOf(Integer.parseInt(patch) + 1); - break; - } - if(fourth == null) { - fourth = ""; - } else { - fourth = ".0"; + acc.put(new GroupArtifact( + t.getChildValue("groupId").orElse(null), t.getChildValue("artifactId").orElse(null)), + newVersion); + return t; + } + }; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor(Map<GroupArtifact, String> acc) { + return new MavenIsoVisitor<ExecutionContext>() { + + @Override + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + + if ((!(PROJECT_MATCHER.matches(getCursor()) || PARENT_MATCHER.matches(getCursor()))) + || t.getMarkers().findFirst(AlreadyIncremented.class).isPresent()) { + return t; } - if(extra == null) { - extra = ""; + String newVersion = acc.get(new GroupArtifact( + t.getChildValue("groupId").orElse(null), t.getChildValue("artifactId").orElse(null))); + if(newVersion == null || newVersion.equals(t.getChildValue("version").orElse(null))) { + return t; } - String newVersion = major + "." + minor + "." + patch + fourth + extra; - t = (Xml.Tag) new ChangeTagValue("version", null, newVersion).getVisitor() + t = t.withMarkers(t.getMarkers().add(new AlreadyIncremented(randomId()))); + return (Xml.Tag) new ChangeTagValue("version", null, newVersion).getVisitor() .visitNonNull(t, ctx); - return t.withMarkers(t.getMarkers().add(new AlreadyIncremented(randomId()))); } }; } + + private String incrementSemverDigit(String oldVersion) { + Matcher m = SEMVER_PATTERN.matcher(oldVersion); + if(!m.matches()) { + return oldVersion; + } + String major = m.group(1); + String minor = m.group(2); + String patch = m.group(3); + // Semver does not have a concept of a fourth number, but it is common enough to support + String fourth = m.group(4); + String extra = m.group(5); + switch (digit) { + case MAJOR: + major = String.valueOf(Integer.parseInt(major) + 1); + minor = "0"; + patch = "0"; + break; + case MINOR: + minor = String.valueOf(Integer.parseInt(minor) + 1); + patch = "0"; + break; + case PATCH: + patch = String.valueOf(Integer.parseInt(patch) + 1); + break; + } + if(fourth == null) { + fourth = ""; + } else { + fourth = ".0"; + } + if(extra == null) { + extra = ""; + } + return major + "." + minor + "." + patch + fourth + extra; + } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java index f6f3f5423da..c881ce229f9 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/IncrementProjectVersionTest.java @@ -19,6 +19,7 @@ import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import static org.openrewrite.java.Assertions.mavenProject; import static org.openrewrite.maven.Assertions.pomXml; public class IncrementProjectVersionTest implements RewriteTest { @@ -71,4 +72,52 @@ void extraFourthDigit() { ) ); } + + @Test + void incrementParentVersion() { + rewriteRun( + pomXml( + """ + <project> + <groupId>com.mycompany</groupId> + <artifactId>my-parent</artifactId> + <version>1.0.0</version> + </project> + """, + """ + <project> + <groupId>com.mycompany</groupId> + <artifactId>my-parent</artifactId> + <version>1.1.0</version> + </project> + """ + ), + mavenProject("my-child", + pomXml( + """ + <project> + <parent> + <groupId>com.mycompany</groupId> + <artifactId>my-parent</artifactId> + <version>1.0.0</version> + </parent> + <groupId>com.mycompany</groupId> + <artifactId>my-child</artifactId> + </project> + """, + """ + <project> + <parent> + <groupId>com.mycompany</groupId> + <artifactId>my-parent</artifactId> + <version>1.1.0</version> + </parent> + <groupId>com.mycompany</groupId> + <artifactId>my-child</artifactId> + </project> + """ + ) + ) + ); + } } From 95ca7fa1650a16d52bffbdea32fb1fac7af08810 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 19 Oct 2023 11:49:16 +0200 Subject: [PATCH 336/447] Simplify `SpacesVisitor` code with `updateSpace()` --- .../java/format/SpacesVisitor.java | 224 ++++-------------- 1 file changed, 47 insertions(+), 177 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java index ff73aaadd8e..323813817a6 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java @@ -68,7 +68,7 @@ <T extends J> T spaceBefore(T j, boolean spaceBefore) { } } - <T> JContainer<T> spaceBefore( JContainer<T> container, boolean spaceBefore ) { + <T> JContainer<T> spaceBefore(JContainer<T> container, boolean spaceBefore) { if (!container.getBefore().getComments().isEmpty()) { // Perform the space rule for the suffix of the last comment only. Same as IntelliJ. List<Comment> comments = spaceLastCommentSuffix(container.getBefore().getComments(), spaceBefore); @@ -124,7 +124,7 @@ <T extends J> JRightPadded<T> spaceAfter(JRightPadded<T> container, boolean spac private static List<Comment> spaceLastCommentSuffix(List<Comment> comments, boolean spaceSuffix) { return ListUtils.mapLast(comments, - comment -> spaceSuffix(comment, spaceSuffix)); + comment -> spaceSuffix(comment, spaceSuffix)); } private static Comment spaceSuffix(Comment comment, boolean spaceSuffix) { @@ -137,8 +137,23 @@ private static Comment spaceSuffix(Comment comment, boolean spaceSuffix) { } } + Space updateSpace(Space s, boolean haveSpace) { + if (!s.getComments().isEmpty()) { + return s; + } + + if (haveSpace && notSingleSpace(s.getWhitespace())) { + return s.withWhitespace(" "); + } else if (!haveSpace && onlySpacesAndNotEmpty(s.getWhitespace())) { + return s.withWhitespace(""); + } else { + return s; + } + } + /** * Checks if a string only contains spaces or tabs (excluding newline characters). + * * @return true if contains spaces or tabs only, or true for empty string. */ private static boolean onlySpaces(String s) { @@ -167,26 +182,10 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, P if (c.getBody().getStatements().isEmpty()) { if (c.getKind() != J.ClassDeclaration.Kind.Type.Enum) { boolean withinCodeBraces = style.getWithin().getCodeBraces(); - if (withinCodeBraces && StringUtils.isNullOrEmpty(c.getBody().getEnd().getWhitespace())) { - c = c.withBody( - c.getBody().withEnd( - c.getBody().getEnd().withWhitespace(" ") - ) - ); - } else if (!withinCodeBraces && c.getBody().getEnd().getWhitespace().equals(" ")) { - c = c.withBody( - c.getBody().withEnd( - c.getBody().getEnd().withWhitespace("") - ) - ); - } + c = c.withBody(c.getBody().withEnd(updateSpace(c.getBody().getEnd(), withinCodeBraces))); } else { boolean spaceInsideOneLineEnumBraces = style.getOther().getInsideOneLineEnumBraces(); - if (spaceInsideOneLineEnumBraces && StringUtils.isNullOrEmpty(c.getBody().getEnd().getWhitespace())) { - c = c.withBody(c.getBody().withEnd(c.getBody().getEnd().withWhitespace(" "))); - } else if (!spaceInsideOneLineEnumBraces && c.getBody().getEnd().getWhitespace().equals(" ")) { - c = c.withBody(c.getBody().withEnd(c.getBody().getEnd().withWhitespace(""))); - } + c = c.withBody(c.getBody().withEnd(updateSpace(c.getBody().getEnd(), spaceInsideOneLineEnumBraces))); } } } @@ -417,12 +416,12 @@ public J.ForLoop visitForLoop(J.ForLoop forLoop, P p) { control = spaceBefore(control, style.getBeforeParentheses().getForParentheses()); Boolean padEmptyForInitializer = null; - if(emptyForInitializerPadStyle != null) { + if (emptyForInitializerPadStyle != null) { padEmptyForInitializer = emptyForInitializerPadStyle.getSpace(); } boolean spaceWithinForParens = style.getWithin().getForParentheses(); boolean shouldPutSpaceOnInit; - if(padEmptyForInitializer != null && f.getControl().getInit().get(0) instanceof J.Empty) { + if (padEmptyForInitializer != null && f.getControl().getInit().get(0) instanceof J.Empty) { shouldPutSpaceOnInit = padEmptyForInitializer; } else { shouldPutSpaceOnInit = spaceWithinForParens; @@ -445,7 +444,7 @@ public J.ForLoop visitForLoop(J.ForLoop forLoop, P p) { ); int updateStatementsSize = f.getControl().getUpdate().size(); Boolean padEmptyForIterator = (emptyForIteratorPadStyle == null) ? null : emptyForIteratorPadStyle.getSpace(); - if(padEmptyForIterator != null && updateStatementsSize == 1 && f.getControl().getUpdate().get(0) instanceof J.Empty) { + if (padEmptyForIterator != null && updateStatementsSize == 1 && f.getControl().getUpdate().get(0) instanceof J.Empty) { control = control.getPadding().withUpdate( ListUtils.map(control.getPadding().getUpdate(), (index, elemContainer) -> { elemContainer = elemContainer.withElement( @@ -687,19 +686,7 @@ public J.AssignmentOperation visitAssignmentOperation(J.AssignmentOperation assi J.AssignmentOperation.Padding padding = a.getPadding(); JLeftPadded<J.AssignmentOperation.Type> operator = padding.getOperator(); String operatorBeforeWhitespace = operator.getBefore().getWhitespace(); - if (style.getAroundOperators().getAssignment() && StringUtils.isNullOrEmpty(operatorBeforeWhitespace)) { - a = padding.withOperator( - operator.withBefore( - operator.getBefore().withWhitespace(" ") - ) - ); - } else if (!style.getAroundOperators().getAssignment() && " ".equals(operatorBeforeWhitespace)) { - a = padding.withOperator( - operator.withBefore( - operator.getBefore().withWhitespace("") - ) - ); - } + a = padding.withOperator(operator.withBefore(updateSpace(operator.getBefore(), style.getAroundOperators().getAssignment()))); a = a.withAssignment(spaceBefore(a.getAssignment(), style.getAroundOperators().getAssignment())); return a; } @@ -767,37 +754,10 @@ public J.Binary visitBinary(J.Binary binary, P p) { private J.Binary applyBinarySpaceAround(J.Binary binary, boolean useSpaceAround) { J.Binary.Padding padding = binary.getPadding(); JLeftPadded<J.Binary.Type> operator = padding.getOperator(); - if (useSpaceAround) { - if (StringUtils.isNullOrEmpty(operator.getBefore().getWhitespace())) { - binary = padding.withOperator( - operator.withBefore( - operator.getBefore().withWhitespace(" ") - ) - ); - } - if (StringUtils.isNullOrEmpty(binary.getRight().getPrefix().getWhitespace())) { - binary = binary.withRight( - binary.getRight().withPrefix( - binary.getRight().getPrefix().withWhitespace(" ") - ) - ); - } - } else { - if (operator.getBefore().getWhitespace().equals(" ")) { - binary = padding.withOperator( - operator.withBefore( - operator.getBefore().withWhitespace("") - ) - ); - } - if (binary.getRight().getPrefix().getWhitespace().equals(" ")) { - binary = binary.withRight( - binary.getRight().withPrefix( - binary.getRight().getPrefix().withWhitespace("") - ) - ); - } - } + binary = padding.withOperator( + operator.withBefore(updateSpace(operator.getBefore(), useSpaceAround)) + ); + binary = binary.withRight(spaceBefore(binary.getRight(), useSpaceAround)); return binary; } @@ -823,38 +783,14 @@ public J.Unary visitUnary(J.Unary unary, P p) { } private J.Unary applyUnaryOperatorExprSpace(J.Unary unary, boolean useAroundUnaryOperatorSpace) { - if (useAroundUnaryOperatorSpace && StringUtils.isNullOrEmpty(unary.getExpression().getPrefix().getWhitespace())) { - unary = unary.withExpression( - unary.getExpression().withPrefix( - unary.getExpression().getPrefix().withWhitespace(" ") - ) - ); - } else if (!useAroundUnaryOperatorSpace && unary.getExpression().getPrefix().getWhitespace().equals(" ")) { - unary = unary.withExpression( - unary.getExpression().withPrefix( - unary.getExpression().getPrefix().withWhitespace("") - ) - ); - } + unary = unary.withExpression(spaceBefore(unary.getExpression(), useAroundUnaryOperatorSpace)); return unary; } private J.Unary applyUnaryOperatorBeforeSpace(J.Unary u, boolean useAroundUnaryOperatorSpace) { J.Unary.Padding padding = u.getPadding(); JLeftPadded<J.Unary.Type> operator = padding.getOperator(); - if (useAroundUnaryOperatorSpace && StringUtils.isNullOrEmpty(operator.getBefore().getWhitespace())) { - u = padding.withOperator( - operator.withBefore( - operator.getBefore().withWhitespace(" ") - ) - ); - } else if (!useAroundUnaryOperatorSpace && operator.getBefore().getWhitespace().equals(" ")) { - u = padding.withOperator( - operator.withBefore( - operator.getBefore().withWhitespace("") - ) - ); - } + u = padding.withOperator(operator.withBefore(updateSpace(operator.getBefore(), useAroundUnaryOperatorSpace))); return u; } @@ -862,15 +798,7 @@ private J.Unary applyUnaryOperatorBeforeSpace(J.Unary u, boolean useAroundUnaryO public J.Lambda visitLambda(J.Lambda lambda, P p) { J.Lambda l = super.visitLambda(lambda, p); boolean useSpaceAroundLambdaArrow = style.getAroundOperators().getLambdaArrow(); - if (useSpaceAroundLambdaArrow && StringUtils.isNullOrEmpty(l.getArrow().getWhitespace())) { - l = l.withArrow( - l.getArrow().withWhitespace(" ") - ); - } else if (!useSpaceAroundLambdaArrow && l.getArrow().getWhitespace().equals(" ")) { - l = l.withArrow( - l.getArrow().withWhitespace("") - ); - } + l = l.withArrow(updateSpace(l.getArrow(), useSpaceAroundLambdaArrow)); l = l.withBody(spaceBefore(l.getBody(), style.getAroundOperators().getLambdaArrow())); if (!(l.getParameters().getParameters().isEmpty() || l.getParameters().getParameters().iterator().next() instanceof J.Empty)) { int parametersSize = l.getParameters().getParameters().size(); @@ -965,90 +893,32 @@ public J.NewArray visitNewArray(J.NewArray newArray, P p) { public J.ArrayAccess visitArrayAccess(J.ArrayAccess arrayAccess, P p) { J.ArrayAccess a = super.visitArrayAccess(arrayAccess, p); boolean useSpaceWithinBrackets = style.getWithin().getBrackets(); - if (useSpaceWithinBrackets) { - if (StringUtils.isNullOrEmpty(a.getDimension().getPadding().getIndex().getElement().getPrefix().getWhitespace())) { - a = a.withDimension( - a.getDimension().getPadding().withIndex( - a.getDimension().getPadding().getIndex().withElement( - a.getDimension().getPadding().getIndex().getElement().withPrefix( - a.getDimension().getPadding().getIndex().getElement().getPrefix().withWhitespace(" ") - ) + a = a.withDimension( + a.getDimension().getPadding().withIndex( + a.getDimension().getPadding().getIndex().withElement( + a.getDimension().getPadding().getIndex().getElement().withPrefix( + updateSpace(a.getDimension().getPadding().getIndex().getElement().getPrefix(), useSpaceWithinBrackets) ) + ).withAfter( + updateSpace(a.getDimension().getPadding().getIndex().getAfter(), useSpaceWithinBrackets) ) - ); - } - if (StringUtils.isNullOrEmpty(a.getDimension().getPadding().getIndex().getAfter().getWhitespace())) { - a = a.withDimension( - a.getDimension().getPadding().withIndex( - a.getDimension().getPadding().getIndex().withAfter( - a.getDimension().getPadding().getIndex().getAfter().withWhitespace(" ") - ) - ) - ); - } - } else { - if (a.getDimension().getPadding().getIndex().getElement().getPrefix().getWhitespace().equals(" ")) { - a = a.withDimension( - a.getDimension().getPadding().withIndex( - a.getDimension().getPadding().getIndex().withElement( - a.getDimension().getPadding().getIndex().getElement().withPrefix( - a.getDimension().getPadding().getIndex().getElement().getPrefix().withWhitespace("") - ) - ) - ) - ); - } - if (a.getDimension().getPadding().getIndex().getAfter().getWhitespace().equals(" ")) { - a = a.withDimension( - a.getDimension().getPadding().withIndex( - a.getDimension().getPadding().getIndex().withAfter( - a.getDimension().getPadding().getIndex().getAfter().withWhitespace("") - ) - ) - ); - } - } + ) + ); return a; } @Override public <T extends J> J.Parentheses<T> visitParentheses(J.Parentheses<T> parens, P p) { J.Parentheses<T> p2 = super.visitParentheses(parens, p); - if (style.getWithin().getGroupingParentheses()) { - if (StringUtils.isNullOrEmpty(p2.getPadding().getTree().getElement().getPrefix().getWhitespace())) { - p2 = p2.getPadding().withTree( - p2.getPadding().getTree().withElement( - p2.getPadding().getTree().getElement().withPrefix( - p2.getPadding().getTree().getElement().getPrefix().withWhitespace(" ") - ) - ) - ); - } - if (StringUtils.isNullOrEmpty(p2.getPadding().getTree().getAfter().getWhitespace())) { - p2 = p2.getPadding().withTree( - p2.getPadding().getTree().withAfter( - p2.getPadding().getTree().getAfter().withWhitespace(" ") - ) - ); - } - } else { - if (p2.getPadding().getTree().getElement().getPrefix().getWhitespace().equals(" ")) { - p2 = p2.getPadding().withTree( - p2.getPadding().getTree().withElement( - p2.getPadding().getTree().getElement().withPrefix( - p2.getPadding().getTree().getElement().getPrefix().withWhitespace("") - ) - ) - ); - } - if (p2.getPadding().getTree().getAfter().getWhitespace().equals(" ")) { - p2 = p2.getPadding().withTree( - p2.getPadding().getTree().withAfter( - p2.getPadding().getTree().getAfter().withWhitespace("") + p2 = p2.getPadding().withTree( + p2.getPadding().getTree().withElement( + p2.getPadding().getTree().getElement().withPrefix( + updateSpace(p2.getPadding().getTree().getElement().getPrefix(), style.getWithin().getGroupingParentheses()) ) - ); - } - } + ).withAfter( + updateSpace(p2.getPadding().getTree().getAfter(), style.getWithin().getGroupingParentheses()) + ) + ); return p2; } From 576b7f94a3c5005b2db9b826c99eba4a0162ae83 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Thu, 19 Oct 2023 13:17:30 +0200 Subject: [PATCH 337/447] RemoveUnusedImports conflicts with Record (#3629) * Replicate how RemoveUnusedImports conflicts with Record For https://github.com/openrewrite/rewrite/issues/3607 * Do not remove explicit import when wildcard import conflicts with java.lang * Use a simple list of classes, enums and interfaces in java.lang * Expand to 21, limit to java.lang and sort as suggested in review --- .../java/RemoveUnusedImportsTest.java | 113 +++++++++++++++++ .../openrewrite/java/RemoveUnusedImports.java | 116 +++++++++++++++++- 2 files changed, 228 insertions(+), 1 deletion(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java index 2c570fd759f..45595b3883c 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveUnusedImportsTest.java @@ -1632,4 +1632,117 @@ void method() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite/issues/3607") + void conflictWithRecord() { + rewriteRun( + spec -> spec.parser(JavaParser.fromJavaVersion().dependsOn( + """ + package pzrep.p1; + + public class Record { + public A theOne() { return new A(); } + public B0 theOther0() { return new B0(); } + public B1 theOther1() { return new B1(); } + public B2 theOther2() { return new B2(); } + public B3 theOther3() { return new B3(); } + public B4 theOther4() { return new B4(); } + public B5 theOther5() { return new B5(); } + public B6 theOther6() { return new B6(); } + public B7 theOther7() { return new B7(); } + public B8 theOther8() { return new B8(); } + public B9 theOther9() { return new B9(); } + } + """, """ + package pzrep.p1; + + public class A { + public void f() {} + } + """, """ + package pzrep.p1; + + public class B0 { + public void f() {} + } + """, """ + package pzrep.p1; + + public class B1 { + public void f() {} + } + """, """ + package pzrep.p1; + + public class B2 { + public void f() {} + } + """, """ + package pzrep.p1; + + public class B3 { + public void f() {} + } + """, """ + package pzrep.p1; + + public class B4 { + public void f() {} + } + """, """ + package pzrep.p1; + + public class B5 { + public void f() {} + } + """, """ + package pzrep.p1; + + public class B6 { + public void f() {} + } + """, """ + package pzrep.p1; + + public class B7 { + public void f() {} + } + """, """ + package pzrep.p1; + + public class B8 { + public void f() {} + } + """, """ + package pzrep.p1; + + public class B9 { + public void f() {} + } + """ + )), + java(""" + package pzrep.p2; + + import pzrep.p1.Record; + import pzrep.p1.*; + + class Client2 { + void f(Record r) { + A a = r.theOne(); + B0 b0 = r.theOther0(); + B1 b1 = r.theOther1(); + B2 b2 = r.theOther2(); + B3 b3 = r.theOther3(); + B4 b4 = r.theOther4(); + B5 b5 = r.theOther5(); + B6 b6 = r.theOther6(); + B7 b7 = r.theOther7(); + B8 b8 = r.theOther8(); + B9 b9 = r.theOther9(); + } + } + """)); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java index 2dc88645105..828dfdba7e7 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveUnusedImports.java @@ -261,7 +261,7 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon changed = true; } } else { - if (usedWildcardImports.size() == 1 && usedWildcardImports.contains(elem.getPackageName()) && !elem.getTypeName().contains("$")) { + if (usedWildcardImports.size() == 1 && usedWildcardImports.contains(elem.getPackageName()) && !elem.getTypeName().contains("$") && !conflictsWithJavaLang(elem)) { anImport.used = false; changed = true; } @@ -297,6 +297,120 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon return cu; } + + private static final Set<String> JAVA_LANG_CLASS_NAMES = new HashSet<>(Arrays.asList( + "AbstractMethodError", + "Appendable", + "ArithmeticException", + "ArrayIndexOutOfBoundsException", + "ArrayStoreException", + "AssertionError", + "AutoCloseable", + "Boolean", + "BootstrapMethodError", + "Byte", + "Character", + "CharSequence", + "Class", + "ClassCastException", + "ClassCircularityError", + "ClassFormatError", + "ClassLoader", + "ClassNotFoundException", + "ClassValue", + "Cloneable", + "CloneNotSupportedException", + "Comparable", + "Deprecated", + "Double", + "Enum", + "EnumConstantNotPresentException", + "Error", + "Exception", + "ExceptionInInitializerError", + "Float", + "FunctionalInterface", + "IllegalAccessError", + "IllegalAccessException", + "IllegalArgumentException", + "IllegalCallerException", + "IllegalMonitorStateException", + "IllegalStateException", + "IllegalThreadStateException", + "IncompatibleClassChangeError", + "IndexOutOfBoundsException", + "InheritableThreadLocal", + "InstantiationError", + "InstantiationException", + "Integer", + "InternalError", + "InterruptedException", + "Iterable", + "LayerInstantiationException", + "LinkageError", + "Long", + "MatchException", + "Math", + "Module", + "ModuleLayer", + "NegativeArraySizeException", + "NoClassDefFoundError", + "NoSuchFieldError", + "NoSuchFieldException", + "NoSuchMethodError", + "NoSuchMethodException", + "NullPointerException", + "Number", + "NumberFormatException", + "Object", + "OutOfMemoryError", + "Override", + "Package", + "Process", + "ProcessBuilder", + "ProcessHandle", + "Readable", + "Record", + "ReflectiveOperationException", + "Runnable", + "Runtime", + "RuntimeException", + "RuntimePermission", + "SafeVarargs", + "ScopedValue", + "SecurityException", + "SecurityManager", + "Short", + "StackOverflowError", + "StackTraceElement", + "StackWalker", + "StrictMath", + "String", + "StringBuffer", + "StringBuilder", + "StringIndexOutOfBoundsException", + "StringTemplate", + "SuppressWarnings", + "System", + "Thread", + "ThreadDeath", + "ThreadGroup", + "ThreadLocal", + "Throwable", + "TypeNotPresentException", + "UnknownError", + "UnsatisfiedLinkError", + "UnsupportedClassVersionError", + "UnsupportedOperationException", + "VerifyError", + "VirtualMachineError", + "Void", + "WrongThreadException" + )); + + private static boolean conflictsWithJavaLang(J.Import elem) { + return JAVA_LANG_CLASS_NAMES.contains(elem.getClassName()); + } } private static class ImportUsage { From 68067ef54e3afd2f1c0c341f846cd48721555230 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Thu, 19 Oct 2023 14:34:38 +0200 Subject: [PATCH 338/447] Fixed property usage pattern matching search results (#3634) * Fixed property usage pattern matching search results * format * use actual regular expression for propertyPattern * added javadoc * fixed wrong usage of tag parameter instead of super visitor return * fixed tests with regular expression --- .../maven/search/FindProperties.java | 23 +++++---- .../maven/search/FindPropertiesTest.java | 49 ++++++++++++++++++- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindProperties.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindProperties.java index bb6a24bd0c3..09eec492c2f 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindProperties.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/FindProperties.java @@ -38,7 +38,7 @@ public class FindProperties extends Recipe { @Option(displayName = "Property pattern", description = "Regular expression pattern used to match property tag names.", - example = "guava*") + example = "guava.*") String propertyPattern; UUID searchId = randomId(); @@ -55,18 +55,18 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - Pattern propertyMatcher = Pattern.compile(propertyPattern.replace(".", "\\.") - .replace("*", ".*")); + Pattern propertyMatcher = Pattern.compile(propertyPattern); + Pattern propertyUsageMatcher = Pattern.compile(".*\\$\\{" + propertyMatcher.pattern() + "}.*"); return new MavenVisitor<ExecutionContext>() { @Override public Xml visitTag(Xml.Tag tag, ExecutionContext context) { Xml.Tag t = (Xml.Tag) super.visitTag(tag, context); - if (isPropertyTag() && propertyMatcher.matcher(tag.getName()).matches()) { + if (isPropertyTag() && propertyMatcher.matcher(t.getName()).matches()) { t = SearchResult.found(t); } - Optional<String> value = tag.getValue(); - if (t.getContent() != null && value.isPresent() && value.get().contains("${")) { + Optional<String> value = t.getValue(); + if (value.isPresent() && propertyUsageMatcher.matcher(value.get()).matches()) { //noinspection unchecked t = t.withContent(ListUtils.mapFirst((List<Content>) t.getContent(), v -> SearchResult.found(v, getResolutionResult().getPom().getValue(value.get())))); @@ -76,11 +76,16 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext context) { }; } + /** + * + * @param xml The xml document of the pom.xml + * @param propertyPattern Regular expression pattern used to match property tag names + * @return Set of Maven project property tags that matches the {@code propertyPattern} within a pom.xml + */ public static Set<Xml.Tag> find(Xml.Document xml, String propertyPattern) { - Pattern propertyMatcher = Pattern.compile(propertyPattern.replace(".", "\\.") - .replace("*", ".*")); + Pattern propertyMatcher = Pattern.compile(propertyPattern); Set<Xml.Tag> found = new HashSet<>(); - new MavenVisitor<Set<Xml.Tag>>(){ + new MavenVisitor<Set<Xml.Tag>>() { @Override public Xml visitTag(Xml.Tag tag, Set<Xml.Tag> tags) { Xml.Tag t = (Xml.Tag) super.visitTag(tag, tags); diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindPropertiesTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindPropertiesTest.java index 1a9d6891aa8..e1a620e6a00 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindPropertiesTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/search/FindPropertiesTest.java @@ -27,7 +27,7 @@ class FindPropertiesTest implements RewriteTest { @Test void findProperty() { rewriteRun( - spec -> spec.recipe(new FindProperties("guava*")), + spec -> spec.recipe(new FindProperties("guava.*")), pomXml( """ <project> @@ -68,4 +68,51 @@ void findProperty() { ) ); } + + @Test + void doesNotMatchOtherPropertyUsages() { + rewriteRun( + spec -> spec.recipe(new FindProperties("guava.*")), + pomXml( + """ + <project> + <properties> + <someNullProp/> + <guava.version>28.2-jre</guava.version> + <other.property>guava</other.property> + </properties> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <dependencies> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>${other.property}</artifactId> + <version>${guava.version}</version> + </dependency> + </dependencies> + </project> + """, + """ + <project> + <properties> + <someNullProp/> + <!--~~>--><guava.version>28.2-jre</guava.version> + <other.property>guava</other.property> + </properties> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <dependencies> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>${other.property}</artifactId> + <version><!--~~(28.2-jre)~~>-->${guava.version}</version> + </dependency> + </dependencies> + </project> + """ + ) + ); + } } From ffc4639ab21541d0f2292f195e9f652cfde23e88 Mon Sep 17 00:00:00 2001 From: Tracey Yoshima <tracey.yoshima@gmail.com> Date: Fri, 20 Oct 2023 08:43:08 -0600 Subject: [PATCH 339/447] Check for null anchor in yaml printer. (#3640) --- .../main/java/org/openrewrite/yaml/internal/YamlPrinter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java index 8c56c1752cd..37944719b54 100755 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/internal/YamlPrinter.java @@ -157,7 +157,9 @@ public Yaml visitAnchor(Yaml.Anchor anchor, PrintOutputCapture<P> p) { public Yaml visitAlias(Yaml.Alias alias, PrintOutputCapture<P> p) { beforeSyntax(alias, p); p.append("*"); - p.append(alias.getAnchor().getKey()); + if (alias.getAnchor() != null) { + p.append(alias.getAnchor().getKey()); + } afterSyntax(alias, p); return alias; } From 867f626d47b06b9b5b6e3b725e676032ce58e994 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sun, 22 Oct 2023 10:06:43 +0200 Subject: [PATCH 340/447] Refactor `RewriteTest` to also work without JGit In the `rewrite` repo we still have an issue with JGit which causes test assertions to fail with different `LinkageError`s. This commit refactors the test harness to be safer against that type of problem. --- .../org/openrewrite/test/RewriteTest.java | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java index 83ea21ed8e6..cdc4f8f7fa5 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java @@ -159,9 +159,9 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) .isNotNull(); if (!(recipe instanceof AdHocRecipe) && !(recipe instanceof CompositeRecipe) && - !(recipe.equals(Recipe.noop())) && - testClassSpec.serializationValidation && - testMethodSpec.serializationValidation) { + !(recipe.equals(Recipe.noop())) && + testClassSpec.serializationValidation && + testMethodSpec.serializationValidation) { RecipeSerializer recipeSerializer = new RecipeSerializer(); assertThat(recipeSerializer.read(recipeSerializer.write(recipe))) .as("Recipe must be serializable/deserializable") @@ -290,9 +290,9 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) if (j++ == i && !(sourceFile instanceof Quark)) { assertThat(sourceFile.printAll(out.clone())) .as("When parsing and printing the source code back to text without modifications, " + - "the printed source didn't match the original source code. This means there is a bug in the " + - "parser implementation itself. Please open an issue to report this, providing a sample of the " + - "code that generated this error!") + "the printed source didn't match the original source code. This means there is a bug in the " + + "parser implementation itself. Please open an issue to report this, providing a sample of the " + + "code that generated this error!") .isEqualTo(StringUtils.readFully(input.getSource(executionContext), parser.getCharset(executionContext))); } } @@ -380,7 +380,7 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) expectedNewResults.add(result); assertThat(result.getBefore()) .as("Expected a new file for the source path but there was an existing file already present: " + - sourceSpec.getSourcePath()) + sourceSpec.getSourcePath()) .isNull(); String actual = result.getAfter().printAll(out.clone()); actual = sourceSpec.noTrim ? actual : actual.trim(); @@ -453,20 +453,20 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) for (Result result : allResults) { if ((result.getBefore() == null && source == null) || - (result.getBefore() != null && result.getBefore().getId().equals(source.getId()))) { + (result.getBefore() != null && result.getBefore().getId().equals(source.getId()))) { if (result.getAfter() != null) { + String actualAfter = result.getAfter().printAll(out.clone()); String expectedAfter = sourceSpec.after == null ? null : - sourceSpec.after.apply(result.getAfter().printAll(out.clone())); + sourceSpec.after.apply(actualAfter); if (expectedAfter != null) { - String actual = result.getAfter().printAll(out.clone()); String expected = sourceSpec.noTrim ? expectedAfter : trimIndentPreserveCRLF(expectedAfter); - assertContentEquals(result.getAfter(), expected, actual, "Unexpected result in"); + assertContentEquals(result.getAfter(), expected, actualAfter, "Unexpected result in"); sourceSpec.eachResult.accept(result.getAfter(), testMethodSpec, testClassSpec); } else { boolean isRemote = result.getAfter() instanceof Remote; - if (result.diff().isEmpty() && !isRemote) { + if (!isRemote && Objects.equals(sourceSpec.before, actualAfter)) { fail("An empty diff was generated. The recipe incorrectly changed a reference without changing its contents."); } @@ -474,7 +474,7 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) if (isRemote) { // TODO: Verify that the remote URI is correct } else { - assertThat(result.getAfter().printAll(out.clone())) + assertThat(actualAfter) .as("The recipe must not make changes to \"" + result.getBefore().getSourcePath() + "\"") .isEqualTo(result.getBefore().printAll(out.clone())); } @@ -540,31 +540,32 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) for (Result result : allResults) { if (result.getBefore() == null - && !(result.getAfter() instanceof Remote) - && !expectedNewResults.contains(result) - && testMethodSpec.afterRecipes.isEmpty() + && !(result.getAfter() instanceof Remote) + && !expectedNewResults.contains(result) + && testMethodSpec.afterRecipes.isEmpty() ) { assertThat(result.getAfter()).isNotNull(); // falsely added files detected. fail("The recipe added a source file \"" + result.getAfter().getSourcePath() - + "\" that was not expected."); + + "\" that was not expected."); } } } static void assertContentEquals(SourceFile sourceFile, String expected, String actual, String errorMessagePrefix) { - try (InMemoryDiffEntry diffEntry = new InMemoryDiffEntry( - sourceFile.getSourcePath(), - sourceFile.getSourcePath(), - null, - expected, - actual, - Collections.emptySet() - )) { - assertThat(actual) - .as(errorMessagePrefix + " \"%s\":\n%s", sourceFile.getSourcePath(), diffEntry.getDiff()) - .isEqualTo(expected); - + try { + try (InMemoryDiffEntry diffEntry = new InMemoryDiffEntry( + sourceFile.getSourcePath(), + sourceFile.getSourcePath(), + null, + expected, + actual, + Collections.emptySet() + )) { + assertThat(actual) + .as(errorMessagePrefix + " \"%s\":\n%s", sourceFile.getSourcePath(), diffEntry.getDiff()) + .isEqualTo(expected); + } } catch (LinkageError e) { // in case JGit fails to load properly assertThat(actual) From bb04cd135862ea714fd2c876cd16e2442b1ee445 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sun, 22 Oct 2023 14:02:52 +0200 Subject: [PATCH 341/447] Does Groovy parser trim final line terminator? --- .../src/main/java/org/openrewrite/test/RewriteTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java index cdc4f8f7fa5..7d5250d0ff7 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java @@ -455,6 +455,7 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) if ((result.getBefore() == null && source == null) || (result.getBefore() != null && result.getBefore().getId().equals(source.getId()))) { if (result.getAfter() != null) { + String before = result.getBefore() == null ? null : result.getBefore().printAll(out.clone()); String actualAfter = result.getAfter().printAll(out.clone()); String expectedAfter = sourceSpec.after == null ? null : sourceSpec.after.apply(actualAfter); @@ -466,7 +467,8 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) sourceSpec.eachResult.accept(result.getAfter(), testMethodSpec, testClassSpec); } else { boolean isRemote = result.getAfter() instanceof Remote; - if (!isRemote && Objects.equals(sourceSpec.before, actualAfter)) { + if (!isRemote && Objects.equals(result.getBefore().getSourcePath(), result.getAfter().getSourcePath()) && + Objects.equals(before, actualAfter)) { fail("An empty diff was generated. The recipe incorrectly changed a reference without changing its contents."); } @@ -476,7 +478,7 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) } else { assertThat(actualAfter) .as("The recipe must not make changes to \"" + result.getBefore().getSourcePath() + "\"") - .isEqualTo(result.getBefore().printAll(out.clone())); + .isEqualTo(before); } } } else { From e3521a1f1dd199bb87496c38ea0e91ca0829a572 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 23 Oct 2023 11:39:01 -0700 Subject: [PATCH 342/447] Sort out classloader issues on saas relating to static state on this recipe --- .../maven/IncrementProjectVersion.java | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java index 259f82aca95..8f924eb8182 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/IncrementProjectVersion.java @@ -69,10 +69,6 @@ public enum SemverDigit { PATCH } - private static final Pattern SEMVER_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)\\.?(\\d+)?(-.+)?$"); - private static final XPathMatcher PROJECT_MATCHER = new XPathMatcher("/project"); - private static final XPathMatcher PARENT_MATCHER = new XPathMatcher("/project/parent"); - @Override public Map<GroupArtifact, String> getInitialValue(ExecutionContext ctx) { return new HashMap<>(); @@ -80,6 +76,9 @@ public Map<GroupArtifact, String> getInitialValue(ExecutionContext ctx) { @Override public TreeVisitor<?, ExecutionContext> getScanner(Map<GroupArtifact, String> acc) { + final XPathMatcher PROJECT_MATCHER = new XPathMatcher("/project"); + final Pattern SEMVER_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)\\.?(\\d+)?(-.+)?$"); + return new MavenIsoVisitor<ExecutionContext>() { @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { @@ -111,12 +110,51 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { newVersion); return t; } + + private String incrementSemverDigit(String oldVersion) { + Matcher m = SEMVER_PATTERN.matcher(oldVersion); + if(!m.matches()) { + return oldVersion; + } + String major = m.group(1); + String minor = m.group(2); + String patch = m.group(3); + // Semver does not have a concept of a fourth number, but it is common enough to support + String fourth = m.group(4); + String extra = m.group(5); + switch (digit) { + case MAJOR: + major = String.valueOf(Integer.parseInt(major) + 1); + minor = "0"; + patch = "0"; + break; + case MINOR: + minor = String.valueOf(Integer.parseInt(minor) + 1); + patch = "0"; + break; + case PATCH: + patch = String.valueOf(Integer.parseInt(patch) + 1); + break; + } + if(fourth == null) { + fourth = ""; + } else { + fourth = ".0"; + } + if(extra == null) { + extra = ""; + } + return major + "." + minor + "." + patch + fourth + extra; + } }; + } @Override public TreeVisitor<?, ExecutionContext> getVisitor(Map<GroupArtifact, String> acc) { return new MavenIsoVisitor<ExecutionContext>() { + final XPathMatcher PARENT_MATCHER = new XPathMatcher("/project/parent"); + final XPathMatcher PROJECT_MATCHER = new XPathMatcher("/project"); @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { @@ -137,40 +175,4 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { } }; } - - private String incrementSemverDigit(String oldVersion) { - Matcher m = SEMVER_PATTERN.matcher(oldVersion); - if(!m.matches()) { - return oldVersion; - } - String major = m.group(1); - String minor = m.group(2); - String patch = m.group(3); - // Semver does not have a concept of a fourth number, but it is common enough to support - String fourth = m.group(4); - String extra = m.group(5); - switch (digit) { - case MAJOR: - major = String.valueOf(Integer.parseInt(major) + 1); - minor = "0"; - patch = "0"; - break; - case MINOR: - minor = String.valueOf(Integer.parseInt(minor) + 1); - patch = "0"; - break; - case PATCH: - patch = String.valueOf(Integer.parseInt(patch) + 1); - break; - } - if(fourth == null) { - fourth = ""; - } else { - fourth = ".0"; - } - if(extra == null) { - extra = ""; - } - return major + "." + minor + "." + patch + fourth + extra; - } } From cd592b539b259d65140f43466c2002bcc42ae351 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:07:08 -0300 Subject: [PATCH 343/447] Remove unused annotation parameter imports (#3638) * Remove unused annotation parameter imports * Add more test cases --------- Co-authored-by: Adriano Machado <admachad@redhat.com> --- .../java/RemoveAnnotationTest.java | 208 ++++++++++++++++++ .../java/RemoveAnnotationVisitor.java | 57 +++++ .../org/openrewrite/java/tree/Expression.java | 2 +- 3 files changed, 266 insertions(+), 1 deletion(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveAnnotationTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveAnnotationTest.java index b17bc947d70..effa9a9f0db 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveAnnotationTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/RemoveAnnotationTest.java @@ -19,6 +19,7 @@ import org.openrewrite.DocumentExample; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.openrewrite.java.Assertions.java; @@ -538,4 +539,211 @@ public class RemoveAnnotation { ) ); } + + @Test + void unusedAnnotationParameterImports() { + rewriteRun( + spec -> spec.recipe(new RemoveAnnotation("@javax.ejb.TransactionAttribute")), + java( + """ + package javax.ejb; + + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + + /** + * The transaction attribute annotation. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD,ElementType.TYPE}) + public @interface TransactionAttribute { + TransactionAttributeType value() default TransactionAttributeType.REQUIRED; + } + """ + ), + java( + """ + package javax.ejb; + + /** + * The Transaction annotations + */ + public enum TransactionAttributeType { + MANDATORY, + REQUIRED, + REQUIRES_NEW, + SUPPORTS, + NOT_SUPPORTED, + NEVER, + } + """ + ), + java( + """ + import javax.ejb.TransactionAttributeType; + import javax.ejb.TransactionAttribute; + + @TransactionAttribute(TransactionAttributeType.NEVER) + public class ClassAnnotatedTransactionalService { + public void doWork() {} + } + """, + """ + public class ClassAnnotatedTransactionalService { + public void doWork() {} + } + """ + ), + java( + """ + import javax.ejb.TransactionAttributeType; + import javax.ejb.TransactionAttribute; + + public class MethodAnnotatedTransactionalService { + @TransactionAttribute(TransactionAttributeType.NEVER) + public void doWork() {} + } + """, + """ + public class MethodAnnotatedTransactionalService { + public void doWork() {} + } + """ + ) + ); + } + + @Test + void unusedPrimitiveOrArraysOrConstantsParameters() { + rewriteRun( + spec -> spec.recipe(new RemoveAnnotation("@annotations.pkg.TestAnnotation")) + .typeValidationOptions(TypeValidation.builder().build()), + java( + """ + package constants.pkg; + + public class TestConstants { + public static final String CONSTANT_1 = "Test"; + public static final String CONSTANT_2 = "Test"; + } + """ + ), + java( + """ + package annotations.pkg; + + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + + import constants.pkg.TestConstants; + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) + public @interface TestAnnotation { + Class<?> clazz() default String.class; + int[] ints() default {1, 2, 3}; + long longValue() default 1L; + String text() default ""; + Class<?>[] classArray() default {}; + } + """ + ), + java( + """ + package sample.pkg; + + public class TestArrayClass { + } + """ + ), + java( + """ + package another.pkg; + + import annotations.pkg.TestAnnotation; + import constants.pkg.TestConstants; + import sample.pkg.TestArrayClass; + + @TestAnnotation(clazz = String.class, ints = {1, 2, 3}, longValue = 1L, text = TestConstants.CONSTANT_1, classArray = {TestArrayClass.class}) + public class AnnotatedClass { + + @TestAnnotation(clazz = String.class, ints = {1, 2, 3}, longValue = 1L, text = TestConstants.CONSTANT_2) + String testField; + } + """, + """ + package another.pkg; + + public class AnnotatedClass { + + String testField; + } + """ + ) + ); + } + + @Test + void unusedStaticImportsFromAnnotationParameters() { + rewriteRun( + spec -> spec.recipe(new RemoveAnnotation("@annotations.pkg.TestAnnotation")) + .typeValidationOptions(TypeValidation.builder().build()), + java( + """ + package constants.pkg; + + public class TestConstants { + public static final String CONSTANT_1 = "Test"; + public static final String CONSTANT_2 = "Test"; + } + """ + ), + java( + """ + package annotations.pkg; + + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE}) + public @interface TestAnnotation { + String text() default ""; + } + """ + ), + java( + """ + package another.pkg; + + import annotations.pkg.TestAnnotation; + + import static constants.pkg.TestConstants.CONSTANT_1; + import static constants.pkg.TestConstants.CONSTANT_2; + + @TestAnnotation(text = CONSTANT_1) + public class AnnotatedClass { + + String constant = CONSTANT_2; + } + """, + """ + package another.pkg; + + import static constants.pkg.TestConstants.CONSTANT_2; + + public class AnnotatedClass { + + String constant = CONSTANT_2; + } + """ + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RemoveAnnotationVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/RemoveAnnotationVisitor.java index 93dfb9335b4..17b1b98292b 100755 --- a/rewrite-java/src/main/java/org/openrewrite/java/RemoveAnnotationVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RemoveAnnotationVisitor.java @@ -18,7 +18,11 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.ExecutionContext; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.NonNull; +import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.Space; import org.openrewrite.java.tree.TypeUtils; @@ -111,6 +115,7 @@ public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ct if (annotationMatcher.matches(annotation)) { getCursor().getParentOrThrow().putMessage("annotationRemoved", annotation); maybeRemoveImport(TypeUtils.asFullyQualified(annotation.getType())); + maybeRemoveAnnotationParameterImports(annotation); //noinspection ConstantConditions return null; } @@ -134,4 +139,56 @@ private List<J.Annotation> removeAnnotationOrEmpty(List<J.Annotation> leadingAnn } return newLeadingAnnotations; } + + /** + * If the annotation has parameters, then the imports for the parameter types may need to be removed. + * + * @param annotation the annotation to check + */ + private void maybeRemoveAnnotationParameterImports(@NonNull J.Annotation annotation) { + if (ListUtils.nullIfEmpty(annotation.getArguments()) == null) { + return; + } + + List<Expression> arguments = annotation.getArguments(); + + arguments.forEach(argument -> { + if (argument instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) argument; + Expression expression = assignment.getAssignment(); + maybeRemoveImportFromExpression(expression); + } else { + maybeRemoveImport(TypeUtils.asFullyQualified(argument.getType())); + } + }); + } + + private void maybeRemoveImportFromExpression(Expression expression) { + if (expression instanceof J.NewArray) { + maybeRemoveAnnotationFromArray((J.NewArray) expression); + } else if (expression instanceof J.FieldAccess) { + maybeRemoveAnnotationFromFieldAccess((J.FieldAccess) expression); + } else if (expression instanceof J.Identifier) { + JavaType.Variable fieldType = ((J.Identifier) expression).getFieldType(); + if (fieldType != null) { + maybeRemoveImport(TypeUtils.asFullyQualified(fieldType.getOwner())); + } + } else { + maybeRemoveImport(TypeUtils.asFullyQualified(expression.getType())); + } + } + + private void maybeRemoveAnnotationFromArray(@NonNull J.NewArray newArray) { + List<Expression> initializer = newArray.getInitializer(); + if (ListUtils.nullIfEmpty(initializer) != null) { + initializer.forEach(this::maybeRemoveImportFromExpression); + } + } + + private void maybeRemoveAnnotationFromFieldAccess(@NonNull J.FieldAccess fa) { + JavaType.Variable fieldType = fa.getName().getFieldType(); + if (fieldType != null && fieldType.getOwner() != null) { + maybeRemoveImport(TypeUtils.asFullyQualified(fieldType.getOwner())); + } + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Expression.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Expression.java index 1e2e09bb3c5..c5af1209a9c 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Expression.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Expression.java @@ -29,7 +29,7 @@ public interface Expression extends J { <T extends J> T withType(@Nullable JavaType type); /** - * @return A list of the side effects emitted by the statement, if the statement was decomposed. + * @return A list of the side effects emitted by the statement if the statement was decomposed. * So for a binary operation, there are up to two potential side effects (the left and right side) and as * few as zero if both sides of the expression are something like constants or variable references. */ From c374dd8b15e5e196db8f65ffc6bc372b8994009d Mon Sep 17 00:00:00 2001 From: Peter Streef <peter@moderne.io> Date: Tue, 24 Oct 2023 13:54:51 +0200 Subject: [PATCH 344/447] Ignore local pom if the jar file does not exist. (#3636) * Ignore local pom if the jar file does not exist. * Update rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java Co-authored-by: Knut Wannheden <knut@moderne.io> * Update rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java Co-authored-by: Knut Wannheden <knut@moderne.io> * Update rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java * fixed tests & check for size 0 & added tests --------- Co-authored-by: Knut Wannheden <knut@moderne.io> Co-authored-by: Joan Viladrosa <joan@moderne.io> --- .../maven/internal/MavenPomDownloader.java | 8 ++ .../maven/MavenDependencyFailuresTest.java | 2 + .../internal/MavenPomDownloaderTest.java | 79 +++++++++++++++++-- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index c0217d1a360..177d86c165d 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -516,6 +516,14 @@ public Pom download(GroupArtifactVersion gav, RawPom rawPom = RawPom.parse(fis, Objects.equals(versionMaybeDatedSnapshot, gav.getVersion()) ? null : versionMaybeDatedSnapshot); Pom pom = rawPom.toPom(inputPath, repo).withGav(resolvedGav); + if (pom.getPackaging() == null || "jar".equals(pom.getPackaging())) { + File jar = f.toPath().resolveSibling(gav.getArtifactId() + '-' + versionMaybeDatedSnapshot + ".jar").toFile(); + if (!jar.exists() || jar.length() == 0) { + // The jar has not been downloaded, making this dependency unusable. + continue; + } + } + // so that the repository path is the same regardless of username pom = pom.withRepository(MavenRepository.MAVEN_LOCAL_USER_NEUTRAL); diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenDependencyFailuresTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenDependencyFailuresTest.java index c6addd04f5f..031ce40d56c 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenDependencyFailuresTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenDependencyFailuresTest.java @@ -139,6 +139,8 @@ void unresolvableTransitiveDependency(@TempDir Path localRepository) throws IOEx </project> """ ); + Path localJar = localRepository.resolve("com/bad/bad-artifact/1/bad-artifact-1.jar"); + Files.writeString(localJar, "dummy"); MavenRepository mavenLocal = MavenRepository.builder().id("local").uri(localRepository.toUri().toString()) .snapshots(false).knownToExist(true).build(); diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java index 7891db4c1b7..883d5e13ab7 100755 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java @@ -51,7 +51,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; @SuppressWarnings({"NullableProblems", "HttpUrlsUsage"}) class MavenPomDownloaderTest { @@ -269,12 +269,12 @@ public MockResponse dispatch(RecordedRequest recordedRequest) { new MockResponse().setResponseCode(200).setBody( //language=xml """ - <project> - <groupId>org.springframework.cloud</groupId> - <artifactId>spring-cloud-dataflow-build</artifactId> - <version>2.10.0-SNAPSHOT</version> - </project> - """) : + <project> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-dataflow-build</artifactId> + <version>2.10.0-SNAPSHOT</version> + </project> + """) : new MockResponse().setResponseCode(401).setBody(""); } }); @@ -485,4 +485,69 @@ void mergeMetadata() throws IOException { assertThat(merged.getVersioning().getSnapshotVersions().get(0).getValue()).isNotNull(); assertThat(merged.getVersioning().getSnapshotVersions().get(0).getUpdated()).isNotNull(); } + + @Test + void skipsLocalInvalidArtifactsMissingJar(@TempDir Path localRepository) throws IOException { + Path localArtifact = localRepository.resolve("com/bad/bad-artifact"); + assertThat(localArtifact.toFile().mkdirs()).isTrue(); + Files.createDirectories(localArtifact.resolve("1")); + + Path localPom = localRepository.resolve("com/bad/bad-artifact/1/bad-artifact-1.pom"); + Files.writeString(localPom, + //language=xml + """ + <project> + <groupId>com.bad</groupId> + <artifactId>bad-artifact</artifactId> + <version>1</version> + </project> + """ + ); + + MavenRepository mavenLocal = MavenRepository.builder() + .id("local") + .uri(localRepository.toUri().toString()) + .snapshots(false) + .knownToExist(true) + .build(); + + // Does not return invalid dependency. + assertThrows(MavenDownloadingException.class, () -> + new MavenPomDownloader(emptyMap(), new InMemoryExecutionContext()) + .download(new GroupArtifactVersion("com.bad", "bad-artifact", "1"), null, null, List.of(mavenLocal))); + } + + @Test + void skipsLocalInvalidArtifactsEmptyJar(@TempDir Path localRepository) throws IOException { + Path localArtifact = localRepository.resolve("com/bad/bad-artifact"); + assertThat(localArtifact.toFile().mkdirs()).isTrue(); + Files.createDirectories(localArtifact.resolve("1")); + + Path localPom = localRepository.resolve("com/bad/bad-artifact/1/bad-artifact-1.pom"); + Files.writeString(localPom, + //language=xml + """ + <project> + <groupId>com.bad</groupId> + <artifactId>bad-artifact</artifactId> + <version>1</version> + </project> + """ + ); + Path localJar = localRepository.resolve("com/bad/bad-artifact/1/bad-artifact-1.jar"); + Files.writeString(localJar, ""); + + MavenRepository mavenLocal = MavenRepository.builder() + .id("local") + .uri(localRepository.toUri().toString()) + .snapshots(false) + .knownToExist(true) + .build(); + + // Does not return invalid dependency. + assertThrows(MavenDownloadingException.class, () -> + new MavenPomDownloader(emptyMap(), new InMemoryExecutionContext()) + .download(new GroupArtifactVersion("com.bad", "bad-artifact", "1"), null, null, List.of(mavenLocal))); + } + } From 615f5554253b1c93d78ad4da1f5f8bf6ee0b2a62 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Wed, 25 Oct 2023 14:50:57 +0200 Subject: [PATCH 345/447] Add explicit Maven plugin version for reproducibility (#3592) * Add explicit Maven plugin version for reproducibility Fixes https://github.com/openrewrite/rewrite/issues/2735 * Add ExplicitPluginVersion class to prevent existing upgrades * Ensure version tag is placed before plugin configuration * Do not override parent managed plugin version * Shorten ExplicitPluginVersion and match artifactId fallback too * Polish * Apply suggestions from code review --- .../maven/MavenTagInsertionComparator.java | 1 + .../maven/UpgradePluginVersion.java | 71 ++++----- .../maven/cleanup/ExplicitPluginVersion.java | 58 ++++++++ .../java/org/openrewrite/maven/tree/Pom.java | 2 +- .../openrewrite/maven/tree/ResolvedPom.java | 59 +++++++- .../main/resources/META-INF/rewrite/maven.yml | 2 +- .../maven/UpgradePluginVersionTest.java | 65 ++++++++- .../cleanup/ExplicitPluginVersionTest.java | 136 ++++++++++++++++++ 8 files changed, 355 insertions(+), 39 deletions(-) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginVersion.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ExplicitPluginVersionTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenTagInsertionComparator.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenTagInsertionComparator.java index 52ef101ceea..dea87f57f90 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenTagInsertionComparator.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenTagInsertionComparator.java @@ -59,6 +59,7 @@ public class MavenTagInsertionComparator implements Comparator<Content> { "repositories", "pluginRepositories", "build", + "configuration", "reports", "reporting", "profiles" diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradePluginVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradePluginVersion.java index 99511151f5d..136a65a20ab 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradePluginVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradePluginVersion.java @@ -22,8 +22,10 @@ import org.openrewrite.maven.search.FindPlugin; import org.openrewrite.maven.table.MavenMetadataFailures; import org.openrewrite.maven.tree.MavenMetadata; +import org.openrewrite.maven.tree.ResolvedPom; import org.openrewrite.semver.Semver; import org.openrewrite.semver.VersionComparator; +import org.openrewrite.xml.AddToTagVisitor; import org.openrewrite.xml.ChangeTagValueVisitor; import org.openrewrite.xml.tree.Xml; @@ -80,6 +82,12 @@ public class UpgradePluginVersion extends Recipe { @Nullable Boolean trustParent; + @Option(displayName = "Add version if missing", + description = "If the plugin is missing a version, add the latest release. Defaults to false.", + required = false) + @Nullable + Boolean addVersionIfMissing; + @SuppressWarnings("ConstantConditions") @Override public Validated<Object> validate() { @@ -110,29 +118,28 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { if (isPluginTag(groupId, artifactId)) { Optional<Xml.Tag> versionTag = tag.getChild("version"); Optional<String> maybeVersionValue = versionTag.flatMap(Xml.Tag::getValue); - if (maybeVersionValue.isPresent()) { - String versionValue = maybeVersionValue.get(); - String versionLookup = versionValue.startsWith("${") - ? super.getResolutionResult().getPom().getValue(versionValue.trim()) - : versionValue; - - if (versionLookup != null) { - try { - String tagGroupId = getResolutionResult().getPom().getValue( - tag.getChildValue("groupId").orElse(groupId) - ); - String tagArtifactId = getResolutionResult().getPom().getValue( - tag.getChildValue("artifactId").orElse(artifactId) - ); - assert tagGroupId != null; - assert tagArtifactId != null; - findNewerDependencyVersion(tagGroupId, tagArtifactId, versionLookup, ctx).ifPresent(newer -> { - ChangePluginVersionVisitor changeDependencyVersion = new ChangePluginVersionVisitor(tagGroupId, tagArtifactId, newer); - doAfterVisit(changeDependencyVersion); - }); - } catch (MavenDownloadingException e) { - return e.warn(tag); - } + if (maybeVersionValue.isPresent() || Boolean.TRUE.equals(addVersionIfMissing)) { + final String versionLookup; + if (maybeVersionValue.isPresent()) { + String versionValue = maybeVersionValue.get(); + versionLookup = versionValue.startsWith("${") + ? super.getResolutionResult().getPom().getValue(versionValue.trim()) + : versionValue; + } else { + versionLookup = "0.0.0"; + } + + try { + ResolvedPom resolvedPom = getResolutionResult().getPom(); + String tagGroupId = resolvedPom.getValue(tag.getChildValue("groupId").orElse(groupId)); + String tagArtifactId = resolvedPom.getValue(tag.getChildValue("artifactId").orElse(artifactId)); + assert tagGroupId != null; + assert tagArtifactId != null; + findNewerDependencyVersion(tagGroupId, tagArtifactId, versionLookup, ctx).ifPresent(newer -> + doAfterVisit(new ChangePluginVersionVisitor(tagGroupId, tagArtifactId, newer, Boolean.TRUE.equals(addVersionIfMissing))) + ); + } catch (MavenDownloadingException e) { + return e.warn(tag); } } return tag; @@ -154,16 +161,12 @@ private Optional<String> findNewerDependencyVersion(String groupId, String artif }); } + @Value private static class ChangePluginVersionVisitor extends MavenVisitor<ExecutionContext> { - private final String groupId; - private final String artifactId; - private final String newVersion; - - private ChangePluginVersionVisitor(String groupId, String artifactId, String newVersion) { - this.groupId = groupId; - this.artifactId = artifactId; - this.newVersion = newVersion; - } + String groupId; + String artifactId; + String newVersion; + boolean addVersionIfMissing; @Override public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { @@ -178,9 +181,11 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { doAfterVisit(new ChangeTagValueVisitor<>(versionTag.get(), newVersion)); } } + } else if (addVersionIfMissing) { + Xml.Tag newTag = Xml.Tag.build("<version>" + newVersion + "</version>"); + doAfterVisit(new AddToTagVisitor<>(tag, newTag, new MavenTagInsertionComparator(tag.getChildren()))); } } - return super.visitTag(tag, ctx); } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginVersion.java new file mode 100644 index 00000000000..6917951d44d --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/cleanup/ExplicitPluginVersion.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.cleanup; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.maven.MavenVisitor; +import org.openrewrite.maven.UpgradePluginVersion; +import org.openrewrite.maven.tree.ResolvedPom; +import org.openrewrite.xml.tree.Xml; + +public class ExplicitPluginVersion extends Recipe { + @Override + public String getDisplayName() { + return "Add explicit plugin versions"; + } + + @Override + public String getDescription() { + return "Add explicit plugin versions to POMs for reproducibility, as [MNG-4173](https://issues.apache.org/jira/browse/MNG-4173) removes automatic version resolution for POM plugins."; + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new MavenVisitor<ExecutionContext>() { + @Override + public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = (Xml.Tag) super.visitTag(tag, ctx); + if (isPluginTag() && !t.getChild("version").isPresent()) { + ResolvedPom resolvedPom = getResolutionResult().getPom(); + String groupId = resolvedPom.getValue(tag.getChildValue("groupId").orElse("org.apache.maven.plugins")); + String artifactId = resolvedPom.getValue(tag.getChildValue("artifactId").orElse("*")); + // Do not override parent plugin versions + if (resolvedPom.getPluginManagement().stream() + .noneMatch(p -> groupId.equals(p.getGroupId()) && StringUtils.matchesGlob(p.getArtifactId(), artifactId))) { + doAfterVisit(new UpgradePluginVersion(groupId, artifactId, "latest.release", null, true, true).getVisitor()); + } + } + return t; + } + }; + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Pom.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Pom.java index 8d896705280..1b306f799f4 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Pom.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Pom.java @@ -105,7 +105,7 @@ public ResolvedPom resolve(Iterable<String> activeProfiles, MavenPomDownloader d } public ResolvedPom resolve(Iterable<String> activeProfiles, MavenPomDownloader downloader, List<MavenRepository> initialRepositories, ExecutionContext ctx) throws MavenDownloadingException { - return new ResolvedPom(this, activeProfiles, emptyMap(), emptyList(), initialRepositories, emptyList(), emptyList()).resolve(ctx, downloader); + return new ResolvedPom(this, activeProfiles, emptyMap(), emptyList(), initialRepositories, emptyList(), emptyList(), emptyList()).resolve(ctx, downloader); } @Nullable diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java index f4ac08ed39c..8f6375cae43 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java @@ -61,11 +61,11 @@ public class ResolvedPom { Iterable<String> activeProfiles; public ResolvedPom(Pom requested, Iterable<String> activeProfiles) { - this(requested, activeProfiles, emptyMap(), emptyList(), null, emptyList(), emptyList()); + this(requested, activeProfiles, emptyMap(), emptyList(), null, emptyList(), emptyList(), emptyList()); } @JsonCreator - ResolvedPom(Pom requested, Iterable<String> activeProfiles, Map<String, String> properties, List<ResolvedManagedDependency> dependencyManagement, @Nullable List<MavenRepository> initialRepositories, List<MavenRepository> repositories, List<Dependency> requestedDependencies) { + ResolvedPom(Pom requested, Iterable<String> activeProfiles, Map<String, String> properties, List<ResolvedManagedDependency> dependencyManagement, @Nullable List<MavenRepository> initialRepositories, List<MavenRepository> repositories, List<Dependency> requestedDependencies, List<Plugin> pluginManagement) { this.requested = requested; this.activeProfiles = activeProfiles; this.properties = properties; @@ -73,6 +73,7 @@ public ResolvedPom(Pom requested, Iterable<String> activeProfiles) { this.initialRepositories = initialRepositories; this.repositories = repositories; this.requestedDependencies = requestedDependencies; + this.pluginManagement = pluginManagement; } @NonFinal @@ -90,6 +91,9 @@ public ResolvedPom(Pom requested, Iterable<String> activeProfiles) { @NonFinal List<Dependency> requestedDependencies; + @NonFinal + List<Plugin> pluginManagement; + /** * Deduplicate dependencies and dependency management dependencies * @@ -140,6 +144,7 @@ public ResolvedPom resolve(ExecutionContext ctx, MavenPomDownloader downloader) emptyList(), initialRepositories, emptyList(), + emptyList(), emptyList() ).resolver(ctx, downloader).resolve(); @@ -180,6 +185,16 @@ public ResolvedPom resolve(ExecutionContext ctx, MavenPomDownloader downloader) } } + List<Plugin> resolvedPlugins = resolved.getPluginManagement(); + if (pluginManagement.size() != resolvedPlugins.size()) { + return resolved; + } + for (int i = 0; i < resolvedPlugins.size(); i++) { + if (!pluginManagement.get(i).equals(resolvedPlugins.get(i))) { + return resolved; + } + } + return this; } @@ -331,6 +346,8 @@ void resolveParentsRecursively(Pom requested) throws MavenDownloadingException { pomAncestry.clear(); pomAncestry.add(requested); resolveParentDependenciesRecursively(pomAncestry); + + resolveParentPluginManagementRecursively(pomAncestry); } private void resolveParentPropertiesAndRepositoriesRecursively(List<Pom> pomAncestry) throws MavenDownloadingException { @@ -399,6 +416,30 @@ private void resolveParentDependenciesRecursively(List<Pom> pomAncestry) throws } } + private void resolveParentPluginManagementRecursively(List<Pom> pomAncestry) throws MavenDownloadingException { + Pom pom = pomAncestry.get(0); + + mergePluginManagement(pom.getPluginManagement()); + + if (pom.getParent() != null) { + Pom parentPom = resolveParentPom(pom); + + MavenExecutionContextView.view(ctx) + .getResolutionListener() + .parent(parentPom, pom); + + for (Pom ancestor : pomAncestry) { + if (ancestor.getGav().equals(parentPom.getGav())) { + // parent cycle + return; + } + } + + pomAncestry.add(0, parentPom); + resolveParentPluginManagementRecursively(pomAncestry); + } + } + private Pom resolveParentPom(Pom pom) throws MavenDownloadingException { @SuppressWarnings("DataFlowIssue") GroupArtifactVersion gav = getValues(pom.getParent().getGav()); if (gav.getVersion() == null) { @@ -429,6 +470,18 @@ private void mergeRequestedDependencies(List<Dependency> incomingRequestedDepend } } + private void mergePluginManagement(List<Plugin> incomingPlugins) { + if (!incomingPlugins.isEmpty()) { + if (pluginManagement == null || pluginManagement.isEmpty()) { + //It is possible for the plugins to be an empty, immutable list. + //If it's empty, we ensure to create a mutable list. + pluginManagement = new ArrayList<>(incomingPlugins); + } else { + pluginManagement.addAll(incomingPlugins); + } + } + } + private void mergeRepositories(List<MavenRepository> incomingRepositories) { if (!incomingRepositories.isEmpty()) { if (repositories == null || repositories.isEmpty()) { @@ -601,7 +654,7 @@ public List<ResolvedDependency> resolveDependencies(Scope scope, Map<GroupArtifa ResolvedPom resolvedPom = cache.getResolvedDependencyPom(dPom.getGav()); if (resolvedPom == null) { resolvedPom = new ResolvedPom(dPom, getActiveProfiles(), emptyMap(), - emptyList(), initialRepositories, emptyList(), emptyList()); + emptyList(), initialRepositories, emptyList(), emptyList(), emptyList()); resolvedPom.resolver(ctx, downloader).resolveParentsRecursively(dPom); cache.putResolvedDependencyPom(dPom.getGav(), resolvedPom); } diff --git a/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml b/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml index 3021cda4ef5..91884ec60f5 100644 --- a/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml +++ b/rewrite-maven/src/main/resources/META-INF/rewrite/maven.yml @@ -20,8 +20,8 @@ displayName: Apache Maven best practices description: Applies best practices to Maven POMs. recipeList: - org.openrewrite.maven.cleanup.ExplicitPluginGroupId + - org.openrewrite.maven.cleanup.ExplicitPluginVersion - org.openrewrite.maven.cleanup.PrefixlessExpressions - --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.maven.cleanup.PrefixlessExpressions diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradePluginVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradePluginVersionTest.java index e9acd9edbb8..589acd5ceef 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradePluginVersionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradePluginVersionTest.java @@ -35,6 +35,7 @@ void upgradeToExactVersion() { "quarkus-maven-plugin", "1.13.5.Final", null, + null, null )), pomXml( @@ -89,6 +90,7 @@ void handlesPropertyResolution() { "quarkus-maven-plugin", "1.13.5.Final", null, + null, null )), pomXml( @@ -147,6 +149,7 @@ void ignorePluginWithoutExplicitVersionDeclared() { "quarkus-maven-plugin", "1.13.5.Final", null, + null, null )), pomXml( @@ -180,6 +183,7 @@ void upgradeVersionDynamicallyUsingPattern() { "rewrite-maven-plugin", "~4.2", null, + null, null )), pomXml( @@ -233,6 +237,7 @@ void upgradeVersionIgnoringParent() { "rewrite-maven-plugin", "4.2.x", null, + null, null )), pomXml( @@ -317,7 +322,8 @@ void trustParent() { "rewrite-maven-plugin", "4.2.x", null, - true + true, + null )), pomXml( """ @@ -405,6 +411,7 @@ void upgradePluginInParent() { "rewrite-maven-plugin", "4.2.3", null, + null, null )), pomXml( @@ -477,6 +484,7 @@ void upgradeMultiplePluginsWithDifferentVersions() { "*", "2.4.x", null, + null, null )), pomXml( @@ -540,6 +548,7 @@ void upgradePluginsVersionOnProperties() { "*", "2.4.x", null, + null, null )), pomXml( @@ -605,4 +614,58 @@ void upgradePluginsVersionOnProperties() { ); } + @Test + void shouldAddVersionInOrder() { + rewriteRun( + spec -> spec.recipe(new UpgradePluginVersion( + "org.apache.maven.plugins", + "maven-compiler-plugin", + "3.11.0", + null, + null, + true + )), + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + </project> + """, + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.11.0</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + </project> + """ + ) + ); + } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ExplicitPluginVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ExplicitPluginVersionTest.java new file mode 100644 index 00000000000..35475418e21 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/cleanup/ExplicitPluginVersionTest.java @@ -0,0 +1,136 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.cleanup; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.maven.Assertions.pomXml; + +class ExplicitPluginVersionTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ExplicitPluginVersion()); + } + + private static final String BEFORE = """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + </project> + """; + + private static final String AFTER = """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.11.0</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + </project> + """; + + @Test + @DocumentExample + @Issue("https://github.com/openrewrite/rewrite/issues/2735") + void shouldAddLatest() { + rewriteRun(pomXml(BEFORE, AFTER)); + } + + @Test + void shouldNotUpgradeExisting() { + rewriteRun( + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <!-- version already present, not upgraded --> + <version>3.8.0</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + </project> + """ + ) + ); + } + + @Test + void shouldNotOverrideParentPluginVersion() { + rewriteRun( + pomXml( + """ + <project> + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.5.4</version> + </parent> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + </project> + """ + ) + ); + } +} \ No newline at end of file From e352d0a00350d593b90a0c553ed45ed3d1e07e04 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 25 Oct 2023 11:40:46 -0700 Subject: [PATCH 346/447] Make onlyIfUsing parameter of AddDependency recipes optional --- .../org/openrewrite/gradle/AddDependency.java | 19 +++++++++++++++---- .../org/openrewrite/maven/AddDependency.java | 6 ++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java index 2e57d403a3a..aea80fa54c5 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java @@ -82,7 +82,9 @@ public class AddDependency extends ScanningRecipe<AddDependency.Scanned> { @Option(displayName = "Only if using", description = "Used to determine if the dependency will be added and in which scope it should be placed.", - example = "org.junit.jupiter.api.*") + example = "org.junit.jupiter.api.*", + required = false) + @Nullable String onlyIfUsing; @Option(displayName = "Classifier", @@ -135,7 +137,7 @@ public Validated<Object> validate() { return validated; } - static class Scanned { + public static class Scanned { boolean usingType; Map<JavaProject, Set<String>> configurationsByProject = new HashMap<>(); } @@ -149,14 +151,23 @@ public Scanned getInitialValue(ExecutionContext ctx) { public TreeVisitor<?, ExecutionContext> getScanner(Scanned acc) { return new TreeVisitor<Tree, ExecutionContext>() { - final UsesType<ExecutionContext> usesType = new UsesType<>(onlyIfUsing, true); + UsesType<ExecutionContext> usesType; + private boolean usesType(SourceFile sourceFile, ExecutionContext ctx) { + if(onlyIfUsing == null) { + return true; + } + if(usesType == null) { + usesType = new UsesType<>(onlyIfUsing, true); + } + return usesType.isAcceptable(sourceFile, ctx) && usesType.visit(sourceFile, ctx) != sourceFile; + } @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { SourceFile sourceFile = (SourceFile) requireNonNull(tree); sourceFile.getMarkers().findFirst(JavaProject.class).ifPresent(javaProject -> sourceFile.getMarkers().findFirst(JavaSourceSet.class).ifPresent(sourceSet -> { - if (usesType.isAcceptable(sourceFile, ctx) && usesType.visit(sourceFile, ctx) != sourceFile) { + if (usesType(sourceFile, ctx)) { acc.usingType = true; Set<String> configurations = acc.configurationsByProject.computeIfAbsent(javaProject, ignored -> new HashSet<>()); configurations.add("main".equals(sourceSet.getName()) ? "implementation" : sourceSet.getName() + "Implementation"); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java index cec503f5647..3065b04ff9f 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java @@ -91,7 +91,9 @@ public class AddDependency extends ScanningRecipe<AddDependency.Scanned> { @Option(displayName = "Only if using", description = "Used to determine if the dependency will be added and in which scope it should be placed.", - example = "org.junit.jupiter.api.*") + example = "org.junit.jupiter.api.*", + required = false) + @Nullable String onlyIfUsing; @Option(displayName = "Type", @@ -171,7 +173,7 @@ public TreeVisitor<?, ExecutionContext> getScanner(Scanned acc) { public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { SourceFile sourceFile = (SourceFile) requireNonNull(tree); if (tree instanceof JavaSourceFile) { - boolean sourceFileUsesType = sourceFile != new UsesType<>(onlyIfUsing, true).visit(sourceFile, ctx); + boolean sourceFileUsesType = onlyIfUsing == null || sourceFile != new UsesType<>(onlyIfUsing, true).visit(sourceFile, ctx); acc.usingType |= sourceFileUsesType; sourceFile.getMarkers().findFirst(JavaProject.class).ifPresent(javaProject -> sourceFile.getMarkers().findFirst(JavaSourceSet.class).ifPresent(sourceSet -> { From 21d9cf7ae36ff774d3b561c0b43cb8b2977b5d3e Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 25 Oct 2023 21:33:22 +0200 Subject: [PATCH 347/447] Exclude `ResolvedDependency#dependencies` in `hashCode()` (#3644) --- .../main/java/org/openrewrite/maven/tree/ResolvedDependency.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java index 75885933462..61d0b9b5f12 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java @@ -49,6 +49,7 @@ public class ResolvedDependency implements Serializable { * Direct dependencies only that survived conflict resolution and exclusion. */ @NonFinal + @EqualsAndHashCode.Exclude List<ResolvedDependency> dependencies; List<License> licenses; From db2cd0265fb5b4dd3bd66c8707dfde918d5745c8 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 26 Oct 2023 21:30:02 +0200 Subject: [PATCH 348/447] Optimize `TreeVisitor#visitMarkers()` --- .../src/main/java/org/openrewrite/TreeVisitor.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java b/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java index e30faca6de2..a3b441ab0ae 100644 --- a/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java +++ b/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java @@ -362,9 +362,13 @@ protected final <T2 extends T> T2 visitAndCast(@Nullable Tree tree, P p) { } public Markers visitMarkers(@Nullable Markers markers, P p) { - return markers == null || markers == Markers.EMPTY ? - Markers.EMPTY : - markers.withMarkers(ListUtils.map(markers.getMarkers(), marker -> this.visitMarker(marker, p))); + if (markers == null || markers == Markers.EMPTY) { + return Markers.EMPTY; + } else if (markers.getMarkers().isEmpty()) { + // avoid unnecessary method handle allocation + return markers; + } + return markers.withMarkers(ListUtils.map(markers.getMarkers(), marker -> this.visitMarker(marker, p))); } public <M extends Marker> M visitMarker(Marker marker, P p) { From 51b576e14c03274315aadcc407bbe66f1328ef27 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 26 Oct 2023 21:33:52 +0200 Subject: [PATCH 349/447] Performance optimization for `JavaVisitor#visitIdentifier()` This method is called so often, so it pays of to optimize it a bit at a low level. --- .../src/main/java/org/openrewrite/java/JavaVisitor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index 07b456c7cd5..60f03727dce 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -644,7 +644,10 @@ public J visitForControl(J.ForLoop.Control control, P p) { public J visitIdentifier(J.Identifier ident, P p) { J.Identifier i = ident; - i = i.withAnnotations(ListUtils.map(i.getAnnotations(), a -> visitAndCast(a, p))); + if (!i.getAnnotations().isEmpty()) { + // performance optimization + i = i.withAnnotations(ListUtils.map(i.getAnnotations(), a -> visitAndCast(a, p))); + } i = i.withPrefix(visitSpace(i.getPrefix(), Space.Location.IDENTIFIER_PREFIX, p)); i = i.withMarkers(visitMarkers(i.getMarkers(), p)); Expression temp = (Expression) visitExpression(i, p); From 42eb7d215d94302b04a7183c9bebe26ce8c296dd Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 26 Oct 2023 21:44:36 +0200 Subject: [PATCH 350/447] Also optimize `JavaVisitor#visitSpace()` --- .../org/openrewrite/java/JavaVisitor.java | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index 60f03727dce..3ec39e0056b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -163,19 +163,23 @@ public J visitStatement(Statement statement, P p) { @SuppressWarnings("unused") public Space visitSpace(Space space, Space.Location loc, P p) { //noinspection ConstantValue - return space == Space.EMPTY || space == Space.SINGLE_SPACE || space == null ? space : - space.withComments(ListUtils.map(space.getComments(), comment -> { - if (comment instanceof Javadoc) { - if (javadocVisitor == null) { - javadocVisitor = getJavadocVisitor(); - } - Cursor previous = javadocVisitor.getCursor(); - Comment c = (Comment) javadocVisitor.visit((Javadoc) comment, p, getCursor()); - javadocVisitor.setCursor(previous); - return c; - } - return comment; - })); + if (space == Space.EMPTY || space == Space.SINGLE_SPACE || space == null) { + return space; + } else if (space.getComments().isEmpty()) { + return space; + } + return space.withComments(ListUtils.map(space.getComments(), comment -> { + if (comment instanceof Javadoc) { + if (javadocVisitor == null) { + javadocVisitor = getJavadocVisitor(); + } + Cursor previous = javadocVisitor.getCursor(); + Comment c = (Comment) javadocVisitor.visit((Javadoc) comment, p, getCursor()); + javadocVisitor.setCursor(previous); + return c; + } + return comment; + })); } @Nullable From 0e0edf019938a1293436c6471d75e8511d5f96b0 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 26 Oct 2023 15:13:27 -0700 Subject: [PATCH 351/447] If Snappy cannot be loaded, fall back on not using it to optimize type cache access --- .../org/openrewrite/java/internal/JavaTypeCache.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaTypeCache.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaTypeCache.java index 216a88777d4..bf10a2a2962 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaTypeCache.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaTypeCache.java @@ -31,6 +31,7 @@ public class JavaTypeCache implements Cloneable { // although also note that a String object has a 24 bytes overhead vs. the 16 bytes of a BytesKey object public static final int COMPRESSION_THRESHOLD = 50; + @SuppressWarnings("ClassCanBeRecord") @Value private static class BytesKey { byte[] data; @@ -48,12 +49,18 @@ public void put(String signature, Object o) { typeCache.put(key(signature), o); } + @Nullable + private static boolean snappyUsable = true; + private Object key(String signature) { - if (signature.length() > COMPRESSION_THRESHOLD) { + if (signature.length() > COMPRESSION_THRESHOLD && snappyUsable) { try { return new BytesKey(Snappy.compress(signature.getBytes(StandardCharsets.UTF_8))); } catch (IOException e) { throw new UncheckedIOException(e); + } catch (NoClassDefFoundError e) { + // Some systems fail to load Snappy native components, so fall back to not compressing + snappyUsable = false; } } return signature; From 494c6915373935339bb755672267ea50424bd97c Mon Sep 17 00:00:00 2001 From: Nick McKinney <mckinneynicholas@gmail.com> Date: Thu, 26 Oct 2023 18:32:49 -0400 Subject: [PATCH 352/447] `RecipeSpec` methods `recipeFromYaml`, `recipeFromResource`, and `recipeFromResources` now memoize Recipes to avoid repeated redundant Environment instantiation / classpath scanning (#3609) Co-authored-by: Tim te Beek <tim@moderne.io> --- .../java/org/openrewrite/test/RecipeSpec.java | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java index 6f24dfc6679..bf9c6e6141c 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java @@ -113,27 +113,42 @@ public RecipeSpec recipes(Recipe... recipes) { } public RecipeSpec recipe(InputStream yaml, String... activeRecipes) { - return recipe(Environment.builder() - .load(new YamlResourceLoader(yaml, URI.create("rewrite.yml"), new Properties())) - .build() - .activateRecipes(activeRecipes)); + return recipe(recipeFromInputStream(yaml, activeRecipes)); } public RecipeSpec recipeFromYaml(@Language("yaml") String yaml, String... activeRecipes) { - return recipe(new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)), activeRecipes); + return recipe(RECIPE_CACHE.computeIfAbsent(key("recipeFromYaml", yaml, key(activeRecipes)), + k -> recipeFromInputStream( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)), activeRecipes))); } public RecipeSpec recipeFromResource(String yamlResource, String... activeRecipes) { - return recipe(Objects.requireNonNull(RecipeSpec.class.getResourceAsStream(yamlResource)), activeRecipes); + return recipe(RECIPE_CACHE.computeIfAbsent(key("recipeFromResource", yamlResource, key(activeRecipes)), + k -> recipeFromInputStream( + Objects.requireNonNull(RecipeSpec.class.getResourceAsStream(yamlResource)), activeRecipes))); } public RecipeSpec recipeFromResources(String... activeRecipes) { - return recipe(Environment.builder() - .scanYamlResources() + return recipe(RECIPE_CACHE.computeIfAbsent(key("recipeFromResources", key(activeRecipes)), + k -> Environment.builder() + .scanYamlResources() + .build() + .activateRecipes(activeRecipes))); + } + + private static Recipe recipeFromInputStream(InputStream yaml, String... activeRecipes) { + return Environment.builder() + .load(new YamlResourceLoader(yaml, URI.create("rewrite.yml"), new Properties())) .build() - .activateRecipes(activeRecipes)); + .activateRecipes(activeRecipes); + } + + private static String key(String... s) { + return String.join("-", s); } + private static final Map<String, Recipe> RECIPE_CACHE = new HashMap<>(); + /** * @param parser The parser supplier to use when a matching source file is found. * @return The current recipe spec. From 3a85771b9bd7bd7ab91e463453b42b234c37d791 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Fri, 27 Oct 2023 00:33:52 +0200 Subject: [PATCH 353/447] fixed inner class (#3646) * fixed inner class * Don't add imports for nested types in same CU --------- Co-authored-by: Knut Wannheden <knut@moderne.io> --- .../org/openrewrite/java/ChangeTypeTest.java | 37 +++++++++++++++++++ .../java/org/openrewrite/java/ChangeType.java | 7 +++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java index 054e297eaf9..87e6e654249 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java @@ -1765,4 +1765,41 @@ public void test(String s) { ) ); } + + @Test + void doesNotModifyInnerClassesIfIgnoreDefinitionTrue() { + rewriteRun( + spec -> spec.recipe(new ChangeType("Test.InnerA", "Test.InnerB", true)), + java( + """ + + public class Test { + private class InnerA { + } + + private class InnerB { + } + + public void test(String s) { + InnerA a = new InnerA(); + } + } + """, + """ + public class Test { + private class InnerA { + } + + private class InnerB { + } + + public void test(String s) { + InnerB a = new InnerB(); + } + } + """ + ) + ); + + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java index 8016c440819..55248729715 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java @@ -192,7 +192,7 @@ private void addImport(JavaType.FullyQualified owningClass) { JavaType.FullyQualified fullyQualifiedTarget = TypeUtils.asFullyQualified(targetType); if (fullyQualifiedTarget != null) { JavaType.FullyQualified owningClass = fullyQualifiedTarget.getOwningClass(); - if (!(owningClass != null && topLevelClassnames.contains(getTopLevelClassName(fullyQualifiedTarget).getFullyQualifiedName()))) { + if (!topLevelClassnames.contains(getTopLevelClassName(fullyQualifiedTarget).getFullyQualifiedName())) { if (owningClass != null && !"java.lang".equals(fullyQualifiedTarget.getPackageName())) { addImport(owningClass); } @@ -259,6 +259,11 @@ public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) { @Override public J visitIdentifier(J.Identifier ident, ExecutionContext ctx) { + // Do not modify the identifier if it's on a inner class definition. + if (Boolean.TRUE.equals(ignoreDefinition) && getCursor().getParent() != null && + getCursor().getParent().getValue() instanceof J.ClassDeclaration) { + return super.visitIdentifier(ident, ctx); + } // if the ident's type is equal to the type we're looking for, and the classname of the type we're looking for is equal to the ident's string representation // Then transform it, otherwise leave it alone if (TypeUtils.isOfClassType(ident.getType(), originalType.getFullyQualifiedName())) { From 6b454357a47b64bd045e4a5c2e06f25529ee2c1c Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 26 Oct 2023 17:15:24 -0700 Subject: [PATCH 354/447] Fix java parsers not actually running any of the tck tests --- rewrite-java-11/build.gradle.kts | 6 ++++++ rewrite-java-17/build.gradle.kts | 11 +++++++++++ rewrite-java-21/build.gradle.kts | 8 ++++++++ rewrite-java-8/build.gradle.kts | 8 +++++++- 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/rewrite-java-11/build.gradle.kts b/rewrite-java-11/build.gradle.kts index d629806b642..d23d1858811 100644 --- a/rewrite-java-11/build.gradle.kts +++ b/rewrite-java-11/build.gradle.kts @@ -58,7 +58,10 @@ testing { register("compatibilityTest", JvmTestSuite::class) { dependencies { implementation(project()) + implementation(project(":rewrite-test")) implementation(project(":rewrite-java-tck")) + implementation(project(":rewrite-java-test")) + implementation("org.assertj:assertj-core:latest.release") } targets { @@ -67,6 +70,9 @@ testing { useJUnitPlatform { excludeTags("java17", "java21") } + project(":rewrite-java-tck").layout.buildDirectory.dir("classes/java/main").let { + testClassesDirs += files(it) + } jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) } diff --git a/rewrite-java-17/build.gradle.kts b/rewrite-java-17/build.gradle.kts index e041d3e8e2e..b74b4f3ba29 100644 --- a/rewrite-java-17/build.gradle.kts +++ b/rewrite-java-17/build.gradle.kts @@ -1,3 +1,8 @@ +@file:Suppress("UnstableApiUsage") + +import org.gradle.internal.impldep.org.junit.platform.launcher.TagFilter.excludeTags + + plugins { id("org.openrewrite.build.language-library") id("jvm-test-suite") @@ -52,7 +57,10 @@ testing { register("compatibilityTest", JvmTestSuite::class) { dependencies { implementation(project()) + implementation(project(":rewrite-test")) implementation(project(":rewrite-java-tck")) + implementation(project(":rewrite-java-test")) + implementation("org.assertj:assertj-core:latest.release") } targets { @@ -61,6 +69,9 @@ testing { useJUnitPlatform { excludeTags("java21") } + project(":rewrite-java-tck").layout.buildDirectory.dir("classes/java/main").let { + testClassesDirs += files(it) + } jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) } diff --git a/rewrite-java-21/build.gradle.kts b/rewrite-java-21/build.gradle.kts index c55a94672b4..1b4dfd8cf3e 100644 --- a/rewrite-java-21/build.gradle.kts +++ b/rewrite-java-21/build.gradle.kts @@ -1,3 +1,5 @@ +@file:Suppress("UnstableApiUsage") + plugins { id("org.openrewrite.build.language-library") id("jvm-test-suite") @@ -58,13 +60,19 @@ testing { register("compatibilityTest", JvmTestSuite::class) { dependencies { implementation(project()) + implementation(project(":rewrite-test")) implementation(project(":rewrite-java-tck")) + implementation(project(":rewrite-java-test")) + implementation("org.assertj:assertj-core:latest.release") } targets { all { testTask.configure { useJUnitPlatform() + project(":rewrite-java-tck").layout.buildDirectory.dir("classes/java/main").let { + testClassesDirs += files(it) + } jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) } diff --git a/rewrite-java-8/build.gradle.kts b/rewrite-java-8/build.gradle.kts index c37644b3a47..3dbb586fe59 100644 --- a/rewrite-java-8/build.gradle.kts +++ b/rewrite-java-8/build.gradle.kts @@ -55,7 +55,10 @@ testing { register("compatibilityTest", JvmTestSuite::class) { dependencies { implementation(project()) + implementation(project(":rewrite-test")) implementation(project(":rewrite-java-tck")) + implementation(project(":rewrite-java-test")) + implementation("org.assertj:assertj-core:latest.release") } targets { @@ -64,6 +67,9 @@ testing { useJUnitPlatform { excludeTags("java11", "java17", "java21") } + project(":rewrite-java-tck").layout.buildDirectory.dir("classes/java/main").let { + testClassesDirs += files(it) + } jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) } @@ -75,4 +81,4 @@ testing { tasks.named("check") { dependsOn(testing.suites.named("compatibilityTest")) -} \ No newline at end of file +} From 58fa5d0681c0b6cb0dedb15a99ddcc58446ca709 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 26 Oct 2023 19:55:06 -0700 Subject: [PATCH 355/447] Use more idiomatic, configuration-cache compatible tck configuration. Thanks Shannon --- rewrite-java-11/build.gradle.kts | 8 +++++--- rewrite-java-17/build.gradle.kts | 8 +++++--- rewrite-java-21/build.gradle.kts | 8 +++++--- rewrite-java-8/build.gradle.kts | 8 +++++--- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/rewrite-java-11/build.gradle.kts b/rewrite-java-11/build.gradle.kts index d23d1858811..e897cf9f491 100644 --- a/rewrite-java-11/build.gradle.kts +++ b/rewrite-java-11/build.gradle.kts @@ -3,6 +3,9 @@ plugins { id("jvm-test-suite") } +val javaTck = configurations.create("javaTck") { + isTransitive = false +} dependencies { api(project(":rewrite-core")) api(project(":rewrite-java")) @@ -14,6 +17,7 @@ dependencies { implementation("org.ow2.asm:asm:latest.release") testImplementation(project(":rewrite-test")) + "javaTck"(project(":rewrite-java-tck")) } java { @@ -70,9 +74,7 @@ testing { useJUnitPlatform { excludeTags("java17", "java21") } - project(":rewrite-java-tck").layout.buildDirectory.dir("classes/java/main").let { - testClassesDirs += files(it) - } + testClassesDirs += files(javaTck.files.map { zipTree(it) }) jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) } diff --git a/rewrite-java-17/build.gradle.kts b/rewrite-java-17/build.gradle.kts index b74b4f3ba29..e95a2005b7a 100644 --- a/rewrite-java-17/build.gradle.kts +++ b/rewrite-java-17/build.gradle.kts @@ -8,6 +8,9 @@ plugins { id("jvm-test-suite") } +val javaTck = configurations.create("javaTck") { + isTransitive = false +} dependencies { api(project(":rewrite-core")) api(project(":rewrite-java")) @@ -19,6 +22,7 @@ dependencies { implementation("org.ow2.asm:asm:latest.release") testImplementation(project(":rewrite-test")) + "javaTck"(project(":rewrite-java-tck")) } tasks.withType<JavaCompile> { @@ -69,9 +73,7 @@ testing { useJUnitPlatform { excludeTags("java21") } - project(":rewrite-java-tck").layout.buildDirectory.dir("classes/java/main").let { - testClassesDirs += files(it) - } + testClassesDirs += files(javaTck.files.map { zipTree(it) }) jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) } diff --git a/rewrite-java-21/build.gradle.kts b/rewrite-java-21/build.gradle.kts index 1b4dfd8cf3e..143abb855fc 100644 --- a/rewrite-java-21/build.gradle.kts +++ b/rewrite-java-21/build.gradle.kts @@ -10,6 +10,9 @@ java { languageVersion.set(JavaLanguageVersion.of(21)) } } +val javaTck = configurations.create("javaTck") { + isTransitive = false +} dependencies { api(project(":rewrite-core")) @@ -22,6 +25,7 @@ dependencies { implementation("org.ow2.asm:asm:latest.release") testImplementation(project(":rewrite-test")) + "javaTck"(project(":rewrite-java-tck")) } tasks.withType<JavaCompile> { @@ -70,9 +74,7 @@ testing { all { testTask.configure { useJUnitPlatform() - project(":rewrite-java-tck").layout.buildDirectory.dir("classes/java/main").let { - testClassesDirs += files(it) - } + testClassesDirs += files(javaTck.files.map { zipTree(it) }) jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) } diff --git a/rewrite-java-8/build.gradle.kts b/rewrite-java-8/build.gradle.kts index 3dbb586fe59..f8f1659f8af 100644 --- a/rewrite-java-8/build.gradle.kts +++ b/rewrite-java-8/build.gradle.kts @@ -9,6 +9,9 @@ val compiler = javaToolchains.compilerFor { val tools = compiler.get().metadata.installationPath.file("lib/tools.jar") +val javaTck = configurations.create("javaTck") { + isTransitive = false +} dependencies { compileOnly(files(tools)) compileOnly("org.slf4j:slf4j-api:1.7.+") @@ -22,6 +25,7 @@ dependencies { testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:latest.release") testImplementation(project(":rewrite-test")) + "javaTck"(project(":rewrite-java-tck")) } java { @@ -67,9 +71,7 @@ testing { useJUnitPlatform { excludeTags("java11", "java17", "java21") } - project(":rewrite-java-tck").layout.buildDirectory.dir("classes/java/main").let { - testClassesDirs += files(it) - } + testClassesDirs += files(javaTck.files.map { zipTree(it) }) jvmArgs = listOf("-XX:+UnlockDiagnosticVMOptions", "-XX:+ShowHiddenFrames") shouldRunAfter(test) } From 6042cd82b882b59142f2ed3396420c2831808a5d Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 27 Oct 2023 09:17:02 +0200 Subject: [PATCH 356/447] `J.Identifier#annotations` is sometimes `null` The root cause of this still needs to be identified. --- .../src/main/java/org/openrewrite/java/JavaVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index 3ec39e0056b..7d28636c23f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -648,7 +648,7 @@ public J visitForControl(J.ForLoop.Control control, P p) { public J visitIdentifier(J.Identifier ident, P p) { J.Identifier i = ident; - if (!i.getAnnotations().isEmpty()) { + if (i.getAnnotations() != null && !i.getAnnotations().isEmpty()) { // performance optimization i = i.withAnnotations(ListUtils.map(i.getAnnotations(), a -> visitAndCast(a, p))); } From 30e21cff72809b4731e3ef9d0c0ffcb96971c2e0 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 27 Oct 2023 09:59:07 +0200 Subject: [PATCH 357/447] Add `@JsonPropertyOrder` annotation to `Recipe` --- rewrite-core/src/main/java/org/openrewrite/Recipe.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rewrite-core/src/main/java/org/openrewrite/Recipe.java b/rewrite-core/src/main/java/org/openrewrite/Recipe.java index b8eb5a64526..bf840391de4 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Recipe.java +++ b/rewrite-core/src/main/java/org/openrewrite/Recipe.java @@ -16,6 +16,7 @@ package org.openrewrite; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonTypeInfo; import lombok.Setter; import org.intellij.lang.annotations.Language; @@ -54,6 +55,7 @@ * returns a list of {@link Result results} for each modified {@link SourceFile} */ @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@c") +@JsonPropertyOrder({"@c"}) // serialize type info first public abstract class Recipe implements Cloneable { public static final String PANIC = "__AHHH_PANIC!!!__"; From 3e333fe66c51a89646b2a1d706e42f6055722ac8 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Fri, 27 Oct 2023 11:56:08 +0200 Subject: [PATCH 358/447] Document JGit 5.x is necessary for Java 8 --- rewrite-core/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-core/build.gradle.kts b/rewrite-core/build.gradle.kts index 1616491549a..065c16dd1da 100644 --- a/rewrite-core/build.gradle.kts +++ b/rewrite-core/build.gradle.kts @@ -6,6 +6,7 @@ plugins { } dependencies { + // Pin to 5.x for Java 8, as 6.x requires Java 11 compileOnly("org.eclipse.jgit:org.eclipse.jgit:5.13.+") implementation("org.openrewrite.tools:java-object-diff:latest.release") From 0d884d4d6a0c15591bae6111b166029053e34f05 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sat, 28 Oct 2023 00:23:29 +0200 Subject: [PATCH 359/447] Type validation for before sources in `RewriteTest` (#3564) * Apply type validations to before sources Rename `SourceSpec.EachResult` to `SourceSpec.ValidateSource` as it now applies to before sources as well. Currently, the before source validation can only be completely disabled (no fine-grained options independent of the validation of the after sources). * Fix failing `TabsAndIndents` test case * Use separate options to validate before source * Default the `before` validations to the `after` validations To avoid many "regressions". * Make factory methods package private * Correct more type attribution errors and adjust some tests * Also fix tzpe attribution in other Javadoc parsers * Rename `beforeTypeValidationOptions()` to `typeValidationOptions()` ... and `typeValidationOptions()` to `afterTypeValidationOptions()`. * Follow through with `RecipeSpec` method rename * Exclude 3 test cases from Java 8 * Correct `@MinimumJava11` annotation Was annotated as `@Tag("java17")` rather than `@Tag("java11")`. --- .../openrewrite/gradle/AddDependencyTest.java | 13 +- .../org/openrewrite/gradle/tree/TaskTest.java | 4 +- .../ReloadableJava11JavadocVisitor.java | 6 +- .../ReloadableJava17JavadocVisitor.java | 6 +- .../ReloadableJava21JavadocVisitor.java | 6 +- .../java/ReloadableJava8JavadocVisitor.java | 6 +- .../java/JavaParserTypeMappingTest.java | 8 +- .../org/openrewrite/java/MinimumJava11.java | 2 +- .../org/openrewrite/java/tree/BinaryTest.java | 2 + .../java/tree/ForEachLoopTest.java | 4 +- .../openrewrite/java/tree/ForLoopTest.java | 2 +- .../openrewrite/java/tree/InstanceOfTest.java | 2 +- .../openrewrite/java/tree/JavadocTest.java | 7 + .../org/openrewrite/java/tree/LambdaTest.java | 2 +- .../java/tree/MemberReferenceTest.java | 8 +- .../openrewrite/java/tree/NewClassTest.java | 3 + .../tree/TypeParameterAndWildcardTest.java | 3 + .../java/tree/VariableDeclarationsTest.java | 2 + .../org/openrewrite/java/AddImportTest.java | 10 +- .../java/JavaTemplateInstanceOfTest.java | 2 +- .../openrewrite/java/MethodMatcherTest.java | 2 + .../java/UnwrapParenthesesTest.java | 2 + .../java/format/MethodParamPadTest.java | 6 +- .../java/format/NoWhitespaceBeforeTest.java | 4 +- .../java/format/TabsAndIndentsTest.java | 6 +- .../MigrateJavaTemplateToRewrite8Test.java | 2 + .../MigrateMarkersSearchResultTest.java | 2 + .../UpdateStaticAnalysisPackageTest.java | 3 +- .../java/style/AutodetectTest.java | 240 +++++++++--------- .../java/org/openrewrite/java/Assertions.java | 15 +- .../java/search/FindMissingTypes.java | 20 +- .../org/openrewrite/maven/Assertions.java | 4 +- .../java/org/openrewrite/test/RecipeSpec.java | 8 + .../org/openrewrite/test/RewriteTest.java | 5 +- .../java/org/openrewrite/test/SourceSpec.java | 12 +- .../org/openrewrite/test/TypeValidation.java | 17 ++ 36 files changed, 269 insertions(+), 177 deletions(-) diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java index 13664d53840..9d4b897a0dc 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/AddDependencyTest.java @@ -26,6 +26,7 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import java.util.Optional; @@ -189,7 +190,8 @@ void onlyIfUsingCompileScope(String onlyIfUsing) { @ValueSource(strings = {"com.google.common.math.*", "com.google.common.math.IntMath"}) void onlyIfUsingMultipleScopes(String onlyIfUsing) { rewriteRun( - spec -> spec.recipe(addDependency("com.google.guava:guava:29.0-jre", onlyIfUsing)), + spec -> spec.recipe(addDependency("com.google.guava:guava:29.0-jre", onlyIfUsing)) + .typeValidationOptions(TypeValidation.none()), mavenProject("project", srcMainJava( java(usingGuavaIntMath) @@ -230,7 +232,8 @@ void onlyIfUsingMultipleScopes(String onlyIfUsing) { void usedInMultipleSourceSetsUsingExplicitSourceSet(String onlyIfUsing) { AddDependency addDep = new AddDependency("com.google.guava", "guava", "29.0-jre", null, null, onlyIfUsing, null, null, null, Boolean.TRUE); rewriteRun( - spec -> spec.recipe(addDep), + spec -> spec.recipe(addDep) + .typeValidationOptions(TypeValidation.none()), mavenProject("project", srcMainJava( java(usingGuavaIntMath) @@ -287,7 +290,8 @@ void usedInMultipleSourceSetsUsingExplicitSourceSet(String onlyIfUsing) { void usedInTransitiveSourceSet() { AddDependency addDep = new AddDependency("com.google.guava", "guava", "29.0-jre", null, null, "com.google.common.math.IntMath", null, null, null, Boolean.TRUE); rewriteRun( - spec -> spec.recipe(addDep), + spec -> spec.recipe(addDep) + .typeValidationOptions(TypeValidation.none()), mavenProject("project", srcSmokeTestJava( java(usingGuavaIntMath) @@ -341,7 +345,8 @@ void usedInTransitiveSourceSet() { void addDependencyIfNotUsedInATransitive() { AddDependency addDep = new AddDependency("com.google.guava", "guava", "29.0-jre", null, null, "com.google.common.math.IntMath", null, null, null, Boolean.TRUE); rewriteRun( - spec -> spec.recipe(addDep), + spec -> spec.recipe(addDep) + .typeValidationOptions(TypeValidation.none()), mavenProject("project", srcSmokeTestJava( java(usingGuavaIntMath) diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/tree/TaskTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/tree/TaskTest.java index 939daef1d49..d52ed0f61cd 100755 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/tree/TaskTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/tree/TaskTest.java @@ -28,7 +28,7 @@ class TaskTest implements RewriteTest { @Test void declareTaskOldStyle() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.none()), + spec -> spec.afterTypeValidationOptions(TypeValidation.none()), buildGradle( """ task(testWithCloud, type: Test) { @@ -44,7 +44,7 @@ void declareTaskOldStyle() { @Test void testDsl() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.none()), + spec -> spec.afterTypeValidationOptions(TypeValidation.none()), buildGradle( """ plugins { diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java index c0b59decf81..be568dbbbf2 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java @@ -686,7 +686,11 @@ private JavaType.Method methodReferenceType(DCTree.DCReference ref, @Nullable Ja for (JavaType testParamType : method.getParameterTypes()) { Type paramType = attr.attribType(param, symbol); if (testParamType instanceof JavaType.GenericTypeVariable) { - for (JavaType bound : ((JavaType.GenericTypeVariable) testParamType).getBounds()) { + List<JavaType> bounds = ((JavaType.GenericTypeVariable) testParamType).getBounds(); + if (bounds.isEmpty() && paramType.tsym != null && "java.lang.Object".equals(paramType.tsym.getQualifiedName().toString())) { + return method; + } + for (JavaType bound : bounds) { if (paramTypeMatches(bound, paramType)) { return method; } diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java index 4db1407edb8..8a6fa49bd6d 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java @@ -690,7 +690,11 @@ private JavaType.Method methodReferenceType(DCTree.DCReference ref, @Nullable Ja for (JavaType testParamType : method.getParameterTypes()) { Type paramType = attr.attribType(param, symbol); if (testParamType instanceof JavaType.GenericTypeVariable) { - for (JavaType bound : ((JavaType.GenericTypeVariable) testParamType).getBounds()) { + List<JavaType> bounds = ((JavaType.GenericTypeVariable) testParamType).getBounds(); + if (bounds.isEmpty() && paramType.tsym != null && "java.lang.Object".equals(paramType.tsym.getQualifiedName().toString())) { + return method; + } + for (JavaType bound : bounds) { if (paramTypeMatches(bound, paramType)) { return method; } diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java index d05a6d1ce07..464a1aa9511 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java @@ -690,7 +690,11 @@ private JavaType.Method methodReferenceType(DCTree.DCReference ref, @Nullable Ja for (JavaType testParamType : method.getParameterTypes()) { Type paramType = attr.attribType(param, symbol); if (testParamType instanceof JavaType.GenericTypeVariable) { - for (JavaType bound : ((JavaType.GenericTypeVariable) testParamType).getBounds()) { + List<JavaType> bounds = ((JavaType.GenericTypeVariable) testParamType).getBounds(); + if (bounds.isEmpty() && paramType.tsym != null && "java.lang.Object".equals(paramType.tsym.getQualifiedName().toString())) { + return method; + } + for (JavaType bound : bounds) { if (paramTypeMatches(bound, paramType)) { return method; } diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java index 6c393d58a9d..0c641775262 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java @@ -648,7 +648,11 @@ private JavaType.Method methodReferenceType(DCTree.DCReference ref, @Nullable Ja for (JavaType testParamType : method.getParameterTypes()) { Type paramType = attr.attribType(param, symbol); if (testParamType instanceof JavaType.GenericTypeVariable) { - for (JavaType bound : ((JavaType.GenericTypeVariable) testParamType).getBounds()) { + List<JavaType> bounds = ((JavaType.GenericTypeVariable) testParamType).getBounds(); + if (bounds.isEmpty() && paramType.tsym != null && "java.lang.Object".equals(paramType.tsym.getQualifiedName().toString())) { + return method; + } + for (JavaType bound : bounds) { if (paramTypeMatches(bound, paramType)) { return method; } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/JavaParserTypeMappingTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/JavaParserTypeMappingTest.java index d5712c1a9dc..82c8ae86d4c 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/JavaParserTypeMappingTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/JavaParserTypeMappingTest.java @@ -25,6 +25,7 @@ import org.openrewrite.java.tree.JavaType.Parameterized; import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import java.util.concurrent.atomic.AtomicReference; @@ -140,8 +141,10 @@ class TypeC<T extends String> extends java.util.ArrayList<T> { @Issue("https://github.com/openrewrite/rewrite/issues/1762") @Test + @MinimumJava11 void methodInvocationWithUnknownTypeSymbol() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().constructorInvocations(false).build()), java( """ import java.util.ArrayList; @@ -174,6 +177,7 @@ List<Parent> method(List<Parent> values) { @Test void methodInvocationOnUnknownType() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).build()), java( """ import java.util.ArrayList; @@ -237,7 +241,7 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations import java.util.List; import java.util.stream.Collectors; - @SuppressWarningsWarnings("ALL") + @SuppressWarnings("ALL") class MakeEasyToFind { void method(List<MultiMap> multiMaps) { List<Integer> ints; @@ -302,7 +306,7 @@ public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext executionContext) import java.util.List; import java.util.stream.Collectors; - @SuppressWarningsWarnings("ALL") + @SuppressWarnings("ALL") class MakeEasyToFind { void method(List<MultiMap> multiMaps) { Object obj = multiMaps.stream() diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/MinimumJava11.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/MinimumJava11.java index 9869b248b3d..89ed1976357 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/MinimumJava11.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/MinimumJava11.java @@ -26,6 +26,6 @@ @Retention(RUNTIME) @Target({TYPE, METHOD}) -@Tag("java17") +@Tag("java11") public @interface MinimumJava11 { } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/BinaryTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/BinaryTest.java index b38e0c2c969..1d062eee570 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/BinaryTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/BinaryTest.java @@ -63,6 +63,8 @@ void endOfLineBreaks() { rewriteRun( java( """ + import java.util.Objects; + class Test { void test() { boolean b = Objects.equals(1, 2) // diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ForEachLoopTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ForEachLoopTest.java index c07788e2a86..39430b714da 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ForEachLoopTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ForEachLoopTest.java @@ -45,9 +45,11 @@ void statementTerminatorForSingleLineForLoops() { java( """ class Test { - void test() { + void test(int[] n) { for(Integer i : n) test(); } + void test() { + } } """ ) diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ForLoopTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ForLoopTest.java index 54a4d68a7d3..d6a06277e3d 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ForLoopTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/ForLoopTest.java @@ -110,7 +110,7 @@ void formatLoopNoInit() { java( """ class Test { - void test() { + void test(int i) { for ( ; i < 10 ; i++ ) {} } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/InstanceOfTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/InstanceOfTest.java index 27c6bff2dc8..ca986bd4289 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/InstanceOfTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/InstanceOfTest.java @@ -31,7 +31,7 @@ void instanceOf() { java( """ class Test { - void test() { + void test(Object o) { boolean b = o instanceof String; } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java index a233cc6c989..60251b5dd20 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/JavadocTest.java @@ -17,7 +17,9 @@ import org.junit.jupiter.api.Test; import org.openrewrite.Issue; +import org.openrewrite.java.MinimumJava11; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.openrewrite.java.Assertions.java; @@ -389,6 +391,7 @@ class Test { @Test void exception() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).build()), java( """ public class A { @@ -721,6 +724,7 @@ List<String> method() throws Exception { @Test void multipleReferenceParameters() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).build()), java( """ class Test { @@ -1064,6 +1068,7 @@ interface Test { @Test void methodNotFound() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().methodInvocations(false).build()), java( """ interface Test { @@ -1082,6 +1087,7 @@ interface Test { @Test void typeNotFound() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).build()), java( """ interface Test { @@ -1650,6 +1656,7 @@ void arrayTypeLiterals() { @Test @Issue("https://github.com/openrewrite/rewrite/issues/3530") + @MinimumJava11 void arrayTypeLiterals2() { rewriteRun( java("" + diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LambdaTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LambdaTest.java index 7903ec9bb4d..8299f71d333 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LambdaTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/LambdaTest.java @@ -95,7 +95,7 @@ void multipleParameters() { rewriteRun( java( """ - import java.util.function.BiFunction; + import java.util.function.BiConsumer; class Test { void test() { BiConsumer<String, String> a = (s1, s2) -> { }; diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/MemberReferenceTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/MemberReferenceTest.java index 0465415a250..8f5f319ae21 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/MemberReferenceTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/MemberReferenceTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.openrewrite.java.Assertions.java; @@ -28,6 +29,7 @@ class MemberReferenceTest implements RewriteTest { @Test void unknownDeclaringType() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).methodInvocations(false).build()), java( """ package com.company.pkg; @@ -130,11 +132,13 @@ static void func(String s) {} @Test void constructorMethodReference() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().methodInvocations(false).build()), java( """ import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; + import java.util.Set; + import java.util.stream.Stream; + class Test { Stream<Integer> n = Stream.of(1, 2); Set<Integer> n2 = n.collect(HashSet < Integer > :: new, HashSet :: add); diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewClassTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewClassTest.java index a32be176483..9149abb362f 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewClassTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/NewClassTest.java @@ -84,6 +84,9 @@ void anonymousClass() { rewriteRun( java( """ + import java.util.ArrayList; + import java.util.List; + class Test { List<Integer> l = new ArrayList<Integer>() { /** Javadoc */ diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeParameterAndWildcardTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeParameterAndWildcardTest.java index dcc58444067..10fd235ecb5 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeParameterAndWildcardTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeParameterAndWildcardTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.openrewrite.java.Assertions.java; @@ -25,6 +26,7 @@ class TypeParameterAndWildcardTest implements RewriteTest { @Test void annotatedTypeParametersOnWildcardBounds() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).build()), java( """ import java.util.List; @@ -40,6 +42,7 @@ class A { @Test void annotatedTypeParametersOnReturnTypeExpression() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.builder().identifiers(false).build()), java( """ import java.util.List; diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/VariableDeclarationsTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/VariableDeclarationsTest.java index f4d26d77e02..d855d77df6f 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/VariableDeclarationsTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/VariableDeclarationsTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MinimumJava11; import org.openrewrite.java.MinimumJava17; import org.openrewrite.test.RewriteTest; @@ -59,6 +60,7 @@ class Test { } @Test + @MinimumJava11 void finalVar() { rewriteRun( java( diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java index 9636a2879b8..d80733df383 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/AddImportTest.java @@ -840,15 +840,15 @@ void dontAddImportForStaticImportsIndirectlyReferenced() { spec -> spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() { @Override public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { - maybeAddImport("com.fasterxml.jackson.databind.ObjectMapper"); + maybeAddImport("java.io.File"); return super.visitCompilationUnit(cu, ctx); } - })).parser(JavaParser.fromJavaVersion().classpath("jackson-databind")), + })), java( """ - import com.fasterxml.jackson.databind.ObjectMapper; + import java.io.File; class Helper { - static ObjectMapper OBJECT_MAPPER; + static File FILE; } """ ), @@ -856,7 +856,7 @@ class Helper { """ class Test { void test() { - Helper.OBJECT_MAPPER.writer(); + Helper.FILE.exists(); } } """ diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateInstanceOfTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateInstanceOfTest.java index 508c986a46d..e477887d27b 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateInstanceOfTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateInstanceOfTest.java @@ -68,7 +68,7 @@ public J visitLiteral(J.Literal literal, ExecutionContext executionContext) { } })) // custom missing type validation - .typeValidationOptions(TypeValidation.none()) + .afterTypeValidationOptions(TypeValidation.none()) .afterRecipe(run -> run.getChangeset().getAllResults().forEach(r -> assertTypeAttribution((J) r.getAfter()))); } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/MethodMatcherTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/MethodMatcherTest.java index 15979a2a0cd..d9113a61421 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/MethodMatcherTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/MethodMatcherTest.java @@ -23,6 +23,7 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import java.util.regex.Pattern; @@ -430,6 +431,7 @@ void test() { @Test void matcherForUnknownType() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), java( """ class Test { diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/UnwrapParenthesesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/UnwrapParenthesesTest.java index 4316e9267c6..df0b4d03714 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/UnwrapParenthesesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/UnwrapParenthesesTest.java @@ -188,7 +188,9 @@ void keepParenthesesOnUnaryWithWrappedBinary() { rewriteRun( java( """ + import java.util.HashSet; public class A { + static HashSet<String> set = new HashSet<>(); static boolean notEmpty = !(set == null || set.isEmpty()); } """ diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/MethodParamPadTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/MethodParamPadTest.java index 5b89cdde5e2..97186c8bcb9 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/MethodParamPadTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/MethodParamPadTest.java @@ -331,9 +331,11 @@ void recordWithCompactConstructor() { rewriteRun( version(java( """ + import java.util.Objects; + public record HttpClientTrafficLogData( - Request request, - Response response + String request, + String response ) { public HttpClientTrafficLogData diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/NoWhitespaceBeforeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/NoWhitespaceBeforeTest.java index fcd5888a200..b8f099a6941 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/NoWhitespaceBeforeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/NoWhitespaceBeforeTest.java @@ -30,6 +30,7 @@ import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpec; +import org.openrewrite.test.TypeValidation; import java.util.Collections; import java.util.List; @@ -735,7 +736,8 @@ public static void main(String[] args) { @Test void doNotStripAnnotationArguments() { rewriteRun( - spec -> spec.parser(JavaParser.fromJavaVersion().styles(noWhitespaceBeforeStyle())), + spec -> spec.parser(JavaParser.fromJavaVersion().styles(noWhitespaceBeforeStyle())) + .typeValidationOptions(TypeValidation.none()), java( """ import org.graalvm.compiler.core.common.SuppressFBWarnings; diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java index acbd72321f5..8269b77ce67 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java @@ -945,7 +945,7 @@ class Test { @Test void moreAnnotations() { rewriteRun( - spec -> spec.typeValidationOptions(TypeValidation.none()), + spec -> spec.afterTypeValidationOptions(TypeValidation.none()), java( """ import lombok.EqualsAndHashCode; @@ -1678,6 +1678,7 @@ Test method(Function<Test, Test> f) { @Test void failure1() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), java( """ public class Test { @@ -2302,6 +2303,7 @@ void shiftRight() {} @Test void recordComponents() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), java( """ public record RenameRequest( @@ -2320,7 +2322,7 @@ void enumConstants() { java( """ public enum WorkflowStatus { - @Nullable + @SuppressWarnings("ALL") VALUE1 } """ diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateJavaTemplateToRewrite8Test.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateJavaTemplateToRewrite8Test.java index 7d290a84c17..a7bff9a5b60 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateJavaTemplateToRewrite8Test.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateJavaTemplateToRewrite8Test.java @@ -148,6 +148,7 @@ void templaveVariable() { import lombok.Value; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; + import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import java.util.List; @@ -194,6 +195,7 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext context) { import lombok.Value; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; + import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import java.util.List; diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateMarkersSearchResultTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateMarkersSearchResultTest.java index e68efe8c24e..8df746d3668 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateMarkersSearchResultTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateMarkersSearchResultTest.java @@ -20,6 +20,7 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.openrewrite.java.Assertions.java; @@ -38,6 +39,7 @@ public void defaults(RecipeSpec spec) { @Test void migrate() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), java( """ package org.openrewrite.kubernetes.resource; diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateStaticAnalysisPackageTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateStaticAnalysisPackageTest.java index 82f76dc4922..81103058f63 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateStaticAnalysisPackageTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/UpdateStaticAnalysisPackageTest.java @@ -36,7 +36,7 @@ public void defaults(RecipeSpec spec) { .build() .activateRecipes("org.openrewrite.java.upgrade.UpdateStaticAnalysisPackage") ) - .typeValidationOptions(TypeValidation.none()); + .afterTypeValidationOptions(TypeValidation.none()); } @SuppressWarnings("all") @@ -44,6 +44,7 @@ public void defaults(RecipeSpec spec) { @Test void changeCleanUpToStaticanalysisForSpecificClassOnly() { rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), java( """ package org.openrewrite.java.migrate; diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java index 28b18b6bb6d..61cf8cef637 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/style/AutodetectTest.java @@ -427,20 +427,20 @@ public void method() { void rewriteImportLayout() { var cus = jp().parse( """ - import com.fasterxml.jackson.annotation.JsonCreator; - - import org.openrewrite.internal.StringUtils; - import org.openrewrite.internal.ListUtils; - import org.openrewrite.internal.lang.Nullable; - - import java.util.*; - import java.util.stream.Collectors; - - import static java.util.Collections.*; - import static java.util.function.Function.identity; - - public class Test { - } + import com.fasterxml.jackson.annotation.JsonCreator; + + import org.openrewrite.internal.StringUtils; + import org.openrewrite.internal.ListUtils; + import org.openrewrite.internal.lang.Nullable; + + import java.util.*; + import java.util.stream.Collectors; + + import static java.util.Collections.*; + import static java.util.function.Function.identity; + + public class Test { + } """ ); @@ -530,16 +530,16 @@ void staticImports() { void detectStarImport() { var cus = jp().parse( """ - import java.util.*; - - public class Test { - List<Integer> l; - Set<Integer> s; - Map<Integer, Integer> m; - Collection<Integer> c; - LinkedHashMap<Integer, Integer> lhm; - HashSet<Integer> integer; - } + import java.util.*; + + public class Test { + List<Integer> l; + Set<Integer> s; + Map<Integer, Integer> m; + Collection<Integer> c; + LinkedHashMap<Integer, Integer> lhm; + HashSet<Integer> integer; + } """ ); @@ -555,28 +555,28 @@ public class Test { void detectImportCounts() { var cus = jp().parse( """ - import java.util.ArrayList; - import java.util.Collections; - import java.util.HashSet; - import java.util.List; - import java.util.Set; - - import javax.persistence.Entity; - import javax.persistence.FetchType; - import javax.persistence.JoinColumn; - import javax.persistence.JoinTable; - import javax.persistence.ManyToMany; - import javax.persistence.Table; - import javax.xml.bind.annotation.XmlElement; - - public class Test { - List<Integer> l; - Set<Integer> s; - Map<Integer, Integer> m; - Collection<Integer> c; - LinkedHashMap<Integer, Integer> lhm; - HashSet<Integer> integer; - } + import java.util.ArrayList; + import java.util.Collections; + import java.util.HashSet; + import java.util.List; + import java.util.Set; + + import javax.persistence.Entity; + import javax.persistence.FetchType; + import javax.persistence.JoinColumn; + import javax.persistence.JoinTable; + import javax.persistence.ManyToMany; + import javax.persistence.Table; + import javax.xml.bind.annotation.XmlElement; + + public class Test { + List<Integer> l; + Set<Integer> s; + Map<Integer, Integer> m; + Collection<Integer> c; + LinkedHashMap<Integer, Integer> lhm; + HashSet<Integer> integer; + } """ ); @@ -593,11 +593,11 @@ public class Test { void detectMethodArgs() { var cus = jp().parse( """ - class Test { - void i() { - a("a" ,"b" ,"c" ,"d"); - } + class Test { + void i() { + a("a" ,"b" ,"c" ,"d"); } + } """ ); @@ -614,11 +614,11 @@ void i() { void detectMethodArgAfterComma() { var cus = jp().parse( """ - class Test { - void i() { - a("a", "b"); - } + class Test { + void i() { + a("a", "b"); } + } """ ); @@ -636,11 +636,11 @@ void i() { void detectColonInForEachLoop() { var cus = jp().parse( """ - class Test { - void i() { - for (int i : new int[]{}) {} - } + class Test { + void i() { + for (int i : new int[]{}) {} } + } """ ); @@ -656,11 +656,11 @@ void i() { void detectAfterTypeCast() { var cus = jp().parse( """ - class T { - { - String s = (String) getString(); - } + class T { + { + String s = (String) getString(); } + } """ ); @@ -698,11 +698,11 @@ void m() { void detectMethodArgsNoArgs() { var cus = jp().parse( """ - class Test { - void i() { - a(); - } + class Test { + void i() { + a(); } + } """ ); @@ -719,11 +719,11 @@ void i() { void detectMethodArgsNoSpaceForComma() { var cus = jp().parse( """ - class Test { - void i() { - a("a","b","c"); - } + class Test { + void i() { + a("a","b","c"); } + } """ ); @@ -740,11 +740,11 @@ void i() { void detectMethodArgsSpaceForComma() { var cus = jp().parse( """ - class Test { - void i() { - a("a" , "b" , "c"); - } + class Test { + void i() { + a("a" , "b" , "c"); } + } """ ); @@ -761,11 +761,11 @@ void i() { void detectAfterCommaInNewArray() { var cus = jp().parse( """ - class T { - static { - int[] i = new int[]{1, 2, 3, 4}; - } + class T { + static { + int[] i = new int[]{1, 2, 3, 4}; } + } """ ); @@ -783,14 +783,14 @@ class T { void detectAfterCommaShouldIgnoreFirstElement() { var cus = jp().parse( """ - class T { - static { - int[] i0 = new int[]{1, 2}; - int[] i1 = new int[]{2, 3}; - int[] i2 = new int[]{3, 4}; - int[] i3 = new int[]{4,5}; - } + class T { + static { + int[] i0 = new int[]{1, 2}; + int[] i1 = new int[]{2, 3}; + int[] i2 = new int[]{3, 4}; + int[] i3 = new int[]{4,5}; } + } """ ); @@ -808,18 +808,18 @@ class T { void detectAfterCommaBasedOnLambdas() { var cus = jp().parse( """ - import java.util.function.BiConsumer; - - class T { - static { - int[] i0 = new int[]{1,2}; - int[] i1 = new int[]{2,3}; - - BiConsumer<?, ?> c0 = (a, b) -> {}; - BiConsumer<?, ?> c1 = (a, b) -> {}; - BiConsumer<?, ?> c2 = (a, b) -> {}; - } + import java.util.function.BiConsumer; + + class T { + static { + int[] i0 = new int[]{1,2}; + int[] i1 = new int[]{2,3}; + + BiConsumer<?, ?> c0 = (a, b) -> {}; + BiConsumer<?, ?> c1 = (a, b) -> {}; + BiConsumer<?, ?> c2 = (a, b) -> {}; } + } """ ); @@ -837,13 +837,13 @@ class T { void detectNoSpacesWithinMethodCall() { var cus = jp().parse( """ - class Test { - void a(String a, String b, String c) { - } - void i() { - a("a","b","c"); - } + class Test { + void a(String a, String b, String c) { + } + void i() { + a("a","b","c"); } + } """ ); @@ -860,11 +860,11 @@ void i() { void detectSpacesWithinMethodCall() { var cus = jp().parse( """ - class Test { - void i() { - a( "a","b","c" ); - } + class Test { + void i() { + a( "a","b","c" ); } + } """ ); @@ -881,14 +881,14 @@ void i() { void detectElseWithNoNewLine() { var cus = jp().parse( """ - class Test { - void method(int n) { - if (n == 0) { - } else if (n == 1) { - } else { - } + class Test { + void method(int n) { + if (n == 0) { + } else if (n == 1) { + } else { } } + } """ ); @@ -905,16 +905,16 @@ void method(int n) { void detectElseOnNewLine() { var cus = jp().parse( """ - class Test { - void method(int n) { - if (n == 0) { - } - else if (n == 1) { - } - else { - } + class Test { + void method(int n) { + if (n == 0) { + } + else if (n == 1) { + } + else { } } + } """ ); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java index d26bec85398..b4613d39548 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java @@ -52,19 +52,14 @@ static void customizeExecutionContext(ExecutionContext ctx) { } } - // validateTypes and assertValidTypes can be merged into a single function once JavaRecipeTest is removed - static SourceFile validateTypes(SourceFile after, RecipeSpec testMethodSpec, RecipeSpec testClassSpec) { - if (after instanceof JavaSourceFile) { - TypeValidation typeValidation = testMethodSpec.getTypeValidation() != null ? testMethodSpec.getTypeValidation() : testClassSpec.getTypeValidation(); - if (typeValidation == null) { - typeValidation = new TypeValidation(); - } - assertValidTypes(typeValidation, (JavaSourceFile) after); + static SourceFile validateTypes(SourceFile source, TypeValidation typeValidation) { + if (source instanceof JavaSourceFile) { + assertValidTypes(typeValidation, (JavaSourceFile) source); } - return after; + return source; } - public static void assertValidTypes(TypeValidation typeValidation, J sf) { + private static void assertValidTypes(TypeValidation typeValidation, J sf) { if (typeValidation.identifiers() || typeValidation.methodInvocations() || typeValidation.methodDeclarations() || typeValidation.classDeclarations() || typeValidation.constructorInvocations()) { List<FindMissingTypes.MissingTypeResult> missingTypeResults = FindMissingTypes.findMissingTypes(sf); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java index 0d74f97a9a5..8446a636de6 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java @@ -18,10 +18,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.openrewrite.*; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.JavadocVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.java.tree.JavaType; @@ -148,10 +145,19 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu public J.MemberReference visitMemberReference(J.MemberReference memberRef, ExecutionContext ctx) { J.MemberReference mr = super.visitMemberReference(memberRef, ctx); JavaType.Method type = mr.getMethodType(); - if (!isWellFormedType(type, seenTypes)) { - mr = SearchResult.found(mr, "MemberReference type is missing or malformed"); - } else if (!type.getName().equals(mr.getReference().getSimpleName()) && !type.isConstructor()) { - mr = SearchResult.found(mr, "type information has a different method name '" + type.getName() + "'"); + if (type != null) { + if (!isWellFormedType(type, seenTypes)) { + mr = SearchResult.found(mr, "MemberReference type is missing or malformed"); + } else if (!type.getName().equals(mr.getReference().getSimpleName()) && !type.isConstructor()) { + mr = SearchResult.found(mr, "type information has a different method name '" + type.getName() + "'"); + } + } else { + JavaType.Variable variableType = mr.getVariableType(); + if (!isWellFormedType(variableType, seenTypes)) { + mr = SearchResult.found(mr, "MemberReference type is missing or malformed"); + } else if (!variableType.getName().equals(mr.getReference().getSimpleName())) { + mr = SearchResult.found(mr, "type information has a different variable name '" + variableType.getName() + "'"); + } } return mr; } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/Assertions.java b/rewrite-maven/src/main/java/org/openrewrite/maven/Assertions.java index fd9853fdb03..555fa533e36 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/Assertions.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/Assertions.java @@ -41,7 +41,7 @@ public static SourceSpecs pomXml(@Language("xml") @Nullable String before) { public static SourceSpecs pomXml(@Language("xml") @Nullable String before, Consumer<SourceSpec<Xml.Document>> spec) { SourceSpec<Xml.Document> maven = new SourceSpec<>(Xml.Document.class, "maven", MavenParser.builder(), before, - SourceSpec.EachResult.noop, Assertions::customizeExecutionContext); + SourceSpec.ValidateSource.noop, Assertions::customizeExecutionContext); maven.path("pom.xml"); spec.accept(maven); return maven; @@ -55,7 +55,7 @@ public static SourceSpecs pomXml(@Language("xml") @Nullable String before, @Lang public static SourceSpecs pomXml(@Language("xml") @Nullable String before, @Language("xml") @Nullable String after, Consumer<SourceSpec<Xml.Document>> spec) { SourceSpec<Xml.Document> maven = new SourceSpec<>(Xml.Document.class, "maven", MavenParser.builder(), before, - SourceSpec.EachResult.noop, Assertions::customizeExecutionContext).after(s -> after); + SourceSpec.ValidateSource.noop, Assertions::customizeExecutionContext).after(s -> after); maven.path("pom.xml"); spec.accept(maven); return maven; diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java index bf9c6e6141c..90c43fbb0ea 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java @@ -80,6 +80,9 @@ public static RecipeSpec defaults() { @Nullable TypeValidation typeValidation; + @Nullable + TypeValidation afterTypeValidation; + boolean serializationValidation = true; @Nullable @@ -262,6 +265,11 @@ public RecipeSpec typeValidationOptions(TypeValidation typeValidation) { return this; } + public RecipeSpec afterTypeValidationOptions(TypeValidation typeValidation) { + this.afterTypeValidation = typeValidation; + return this; + } + @Nullable ExecutionContext getExecutionContext() { return executionContext; diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java index 7d5250d0ff7..1bea7fb1ebf 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java @@ -284,6 +284,9 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) } sourceFile = sourceFile.withMarkers(markers); + // Validate before source + nextSpec.validateSource.accept(sourceFile, TypeValidation.before(testMethodSpec, testClassSpec)); + // Validate that printing a parsed AST yields the same source text int j = 0; for (Parser.Input input : inputs.values()) { @@ -464,7 +467,7 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) expectedAfter : trimIndentPreserveCRLF(expectedAfter); assertContentEquals(result.getAfter(), expected, actualAfter, "Unexpected result in"); - sourceSpec.eachResult.accept(result.getAfter(), testMethodSpec, testClassSpec); + sourceSpec.validateSource.accept(result.getAfter(), TypeValidation.after(testMethodSpec, testClassSpec)); } else { boolean isRemote = result.getAfter() instanceof Remote; if (!isRemote && Objects.equals(result.getBefore().getSourcePath(), result.getAfter().getSourcePath()) && diff --git a/rewrite-test/src/main/java/org/openrewrite/test/SourceSpec.java b/rewrite-test/src/main/java/org/openrewrite/test/SourceSpec.java index daf1f350cab..45a5a6567b6 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/SourceSpec.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/SourceSpec.java @@ -55,15 +55,15 @@ public class SourceSpec<T extends SourceFile> implements SourceSpecs { UnaryOperator<String> after; /** - * Apply a function to each SourceFile after recipe execution. + * Apply a function to each SourceFile (before and after) recipe execution. * Useful for validating the AST or its metadata. */ - final EachResult eachResult; + final ValidateSource validateSource; - public interface EachResult { - EachResult noop = (sourceFile, testMethodSpec, testClassSpec) -> sourceFile; + public interface ValidateSource { + ValidateSource noop = (sourceFile, typeValidation) -> sourceFile; - SourceFile accept(SourceFile sourceFile, RecipeSpec testMethodSpec, RecipeSpec testClassSpec); + SourceFile accept(SourceFile sourceFile, TypeValidation typeValidation); } final ThrowingConsumer<ExecutionContext> customizeExecutionContext; @@ -75,7 +75,7 @@ public SourceSpec(Class<T> sourceFileType, @Nullable String dsl, this.parser = parser; this.before = before; this.after = after; - this.eachResult = EachResult.noop; + this.validateSource = ValidateSource.noop; this.customizeExecutionContext = (ctx) -> { }; } diff --git a/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java b/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java index 5581a3d2c98..6d2ed291f59 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java @@ -46,7 +46,24 @@ public class TypeValidation { @Builder.Default private boolean constructorInvocations = true; + public static TypeValidation all() { + return new TypeValidation(); + } + public static TypeValidation none() { return new TypeValidation(false,false,false,false,false,false); } + + static TypeValidation before(RecipeSpec testMethodSpec, RecipeSpec testClassSpec) { + TypeValidation typeValidation = testMethodSpec.getTypeValidation() != null ? + testMethodSpec.getTypeValidation() : testClassSpec.getTypeValidation(); + return typeValidation != null ? typeValidation : new TypeValidation(); + } + + static TypeValidation after(RecipeSpec testMethodSpec, RecipeSpec testClassSpec) { + TypeValidation typeValidation = testMethodSpec.getAfterTypeValidation() != null ? + testMethodSpec.getAfterTypeValidation() : testClassSpec.getAfterTypeValidation(); + // after type validation defaults to before type validation; possibly this will become stricter in the future + return typeValidation != null ? typeValidation : before(testMethodSpec, testClassSpec); + } } From 15ecf0265c74ca6873ab83a424b1874f5f9a9055 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 31 Oct 2023 09:55:42 +0100 Subject: [PATCH 360/447] Enable variable declaration type attribution (#3565) The type attribution of variable declarations is now enabled by default. --- .../java/VariableNameUtilsTest.java | 7 ++-- .../RenameJavaDocParamNameVisitorTest.java | 2 ++ .../org/openrewrite/java/ChangeFieldName.java | 33 ++++++------------- .../org/openrewrite/java/RenameVariable.java | 18 ++++++++-- .../java/search/FindMissingTypes.java | 3 ++ .../org/openrewrite/test/TypeValidation.java | 2 +- 6 files changed, 36 insertions(+), 29 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/VariableNameUtilsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/VariableNameUtilsTest.java index 407716661d9..2b9b0efb738 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/VariableNameUtilsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/VariableNameUtilsTest.java @@ -26,6 +26,7 @@ import org.openrewrite.java.tree.J; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import java.util.HashSet; import java.util.List; @@ -444,7 +445,7 @@ public J.Identifier visitIdentifier(J.Identifier identifier, ExecutionContext ex } return identifier; } - })), + })).typeValidationOptions(TypeValidation.builder().variableDeclarations(false).identifiers(false).build()), java( """ @SuppressWarnings("all") @@ -483,7 +484,7 @@ public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext ctx) { } return lambda; } - })), + })).typeValidationOptions(TypeValidation.builder().variableDeclarations(false).identifiers(false).build()), java( """ import java.util.function.Consumer; @@ -532,7 +533,7 @@ public J.Identifier visitIdentifier(J.Identifier identifier, ExecutionContext ex } return identifier; } - })), + })).typeValidationOptions(TypeValidation.builder().variableDeclarations(false).identifiers(false).build()), java( """ @SuppressWarnings("all") diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/RenameJavaDocParamNameVisitorTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/RenameJavaDocParamNameVisitorTest.java index 2950bfe9511..dd023b466f0 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/RenameJavaDocParamNameVisitorTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/cleanup/RenameJavaDocParamNameVisitorTest.java @@ -49,6 +49,8 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations J.VariableDeclarations.NamedVariable v = super.visitVariable(variable, executionContext); if (variable.getSimpleName().equals("oldName")) { v = v.withName(v.getName().withSimpleName("newName")); + v = v.withName(v.getName().withFieldType(v.getName().getFieldType().withName("newName"))); + v = v.withVariableType(v.getVariableType().withName("newName")); } return v; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldName.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldName.java index 7aa16252591..84b4e943d08 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldName.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeFieldName.java @@ -18,10 +18,10 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.tree.*; - -import static java.util.Collections.emptyList; -import static org.openrewrite.Tree.randomId; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JLeftPadded; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; @Value @EqualsAndHashCode(callSuper = false) @@ -32,14 +32,16 @@ public class ChangeFieldName<P> extends JavaIsoVisitor<P> { @Override public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, P p) { - J.VariableDeclarations.NamedVariable v = variable; + J.VariableDeclarations.NamedVariable v = super.visitVariable(variable, p); J.ClassDeclaration enclosingClass = getCursor().firstEnclosing(J.ClassDeclaration.class); - if(enclosingClass == null) { + if (enclosingClass == null) { return v; } if (variable.isField(getCursor()) && matchesClass(enclosingClass.getType()) && variable.getSimpleName().equals(hasName)) { - v = v.withName(v.getName().withSimpleName(toName)); + if (v.getVariableType() != null) { + v = v.withVariableType(v.getVariableType().withName(toName)); + } } if (variable.getPadding().getInitializer() != null) { v = v.getPadding().withInitializer(visitLeftPadded(variable.getPadding().getInitializer(), @@ -68,22 +70,7 @@ public J.Identifier visitIdentifier(J.Identifier ident, P p) { if (varType.getOwner() instanceof JavaType.Method) { return i; } - i = new J.Identifier( - randomId(), - i.getPrefix(), - i.getMarkers(), - emptyList(), - toName, - i.getType(), - new JavaType.Variable( - null, - Flag.flagsToBitMap(varType.getFlags()), - toName, - varType.getOwner(), - varType.getType(), - varType.getAnnotations() - ) - ); + return i.withSimpleName(toName).withFieldType(varType.withName(toName)); } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/RenameVariable.java b/rewrite-java/src/main/java/org/openrewrite/java/RenameVariable.java index 3f23193eada..48410d58fd0 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/RenameVariable.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/RenameVariable.java @@ -83,6 +83,10 @@ public J.Identifier visitIdentifier(J.Identifier ident, P p) { if (ident.getSimpleName().equals(renameVariable.getSimpleName())) { if (parent.getValue() instanceof J.FieldAccess) { if (fieldAccessTargetsVariable(parent.getValue())) { + if (ident.getFieldType() != null) { + ident = ident.withFieldType(ident.getFieldType().withName(newName)); + } + parent.putMessage("renamed", true); return ident.withSimpleName(newName); } } else if (currentNameScope.size() == 1 && isVariableName(parent.getValue(), ident)) { @@ -100,6 +104,10 @@ public J.Identifier visitIdentifier(J.Identifier ident, P p) { } // The size of the stack will be 1 if the identifier is in the right scope. + if (ident.getFieldType() != null) { + ident = ident.withFieldType(ident.getFieldType().withName(newName)); + } + parent.putMessage("renamed", true); return ident.withSimpleName(newName); } } @@ -140,7 +148,8 @@ private boolean fieldAccessTargetsVariable(J.FieldAccess fieldAccess) { @Override public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable namedVariable, P p) { - if (namedVariable.getSimpleName().equals(renameVariable.getSimpleName())) { + boolean nameEquals = namedVariable.getSimpleName().equals(renameVariable.getSimpleName()); + if (nameEquals) { Cursor parentScope = getCursorToParentScope(getCursor()); // The target variable was found and was not declared in a class declaration block. if (currentNameScope.isEmpty()) { @@ -154,7 +163,12 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations } } } - return super.visitVariable(namedVariable, p); + + J.VariableDeclarations.NamedVariable v = super.visitVariable(namedVariable, p); + if (nameEquals && v.getVariableType() != null && Boolean.TRUE.equals(getCursor().pollMessage("renamed"))) { + v = v.withVariableType(v.getVariableType().withName(newName)); + } + return v; } @Override diff --git a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java index 8446a636de6..9b5adb31417 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/search/FindMissingTypes.java @@ -99,6 +99,9 @@ public J.Identifier visitIdentifier(J.Identifier identifier, ExecutionContext ct if (!isWellFormedType(identifier.getType(), seenTypes) && !isAllowedToHaveNullType(identifier)) { identifier = SearchResult.found(identifier, "Identifier type is missing or malformed"); } + if (identifier.getFieldType() != null && !identifier.getSimpleName().equals(identifier.getFieldType().getName())) { + identifier = SearchResult.found(identifier, "type information has a different variable name '" + identifier.getFieldType().getName() + "'"); + } return identifier; } diff --git a/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java b/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java index 6d2ed291f59..4c51773c841 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/TypeValidation.java @@ -38,7 +38,7 @@ public class TypeValidation { private boolean methodDeclarations = true; @Builder.Default - private boolean variableDeclarations = false; // FIXME false until recipes have been corrected + private boolean variableDeclarations = true; @Builder.Default private boolean methodInvocations = true; From 289e80969eb47b3c8768a305dec96c0213189f41 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 31 Oct 2023 14:13:10 +0100 Subject: [PATCH 361/447] Allow for deserialized `JavaType.Unknown` instances These can't be compared with referential equality against `JavaType.Unknown.getInstance()`. --- .../java/internal/DefaultJavaTypeSignatureBuilder.java | 2 +- .../src/main/java/org/openrewrite/java/tree/TypeUtils.java | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/DefaultJavaTypeSignatureBuilder.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/DefaultJavaTypeSignatureBuilder.java index 41ef289941b..3ea3f891025 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/DefaultJavaTypeSignatureBuilder.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/DefaultJavaTypeSignatureBuilder.java @@ -30,7 +30,7 @@ public class DefaultJavaTypeSignatureBuilder implements JavaTypeSignatureBuilder @Override public String signature(@Nullable Object type) { - if (type == null || type == JavaType.Unknown.getInstance()) { + if (type == null || type instanceof JavaType.Unknown) { return "{undefined}"; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 52f9382f0f3..e61b423b2f0 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -381,10 +381,7 @@ public static JavaType.Primitive asPrimitive(@Nullable JavaType type) { @Nullable public static JavaType.FullyQualified asFullyQualified(@Nullable JavaType type) { - if (type instanceof JavaType.FullyQualified) { - if (type == JavaType.Unknown.getInstance()) { - return null; - } + if (type instanceof JavaType.FullyQualified && !(type instanceof JavaType.Unknown)) { return (JavaType.FullyQualified) type; } return null; From aa5f11347205caf7c6164b07615ba60c6a6bf490 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 1 Nov 2023 11:37:50 -0700 Subject: [PATCH 362/447] Tweak name and description of Gradle plugin management recipes --- .../java/org/openrewrite/gradle/plugins/AddBuildPlugin.java | 4 ++-- .../org/openrewrite/gradle/plugins/AddSettingsPlugin.java | 4 ++-- .../org/openrewrite/gradle/plugins/RemoveBuildPlugin.java | 4 ++-- .../org/openrewrite/gradle/plugins/RemoveSettingsPlugin.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java index 6cfe604af40..cca88d64b23 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddBuildPlugin.java @@ -55,12 +55,12 @@ public class AddBuildPlugin extends Recipe { @Override public String getDisplayName() { - return "Add a Gradle build plugin"; + return "Add Gradle plugin"; } @Override public String getDescription() { - return "Add a Gradle build plugin to `build.gradle(.kts)`."; + return "Add a build plugin to a Gradle build file's `plugins` block."; } @Override diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java index cc97a866b31..2d97c0774f0 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddSettingsPlugin.java @@ -57,12 +57,12 @@ public class AddSettingsPlugin extends Recipe { @Override public String getDisplayName() { - return "Add a Gradle settings plugin"; + return "Add Gradle settings plugin"; } @Override public String getDescription() { - return "Add a Gradle settings plugin to `settings.gradle(.kts)`."; + return "Add plugin to Gradle settings file `plugins` block by id."; } @Override diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveBuildPlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveBuildPlugin.java index ce595a2713f..a0a4fb21aef 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveBuildPlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveBuildPlugin.java @@ -31,12 +31,12 @@ public class RemoveBuildPlugin extends Recipe { @Override public String getDisplayName() { - return "Remove plugin from `build.gradle(.kts)`"; + return "Remove Gradle plugin"; } @Override public String getDescription() { - return "Remove plugin from `build.gradle(.kts)`."; + return "Remove plugin from Gradle `plugins` block by its id. Does not remove plugins from the `buildscript` block."; } @Override diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveSettingsPlugin.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveSettingsPlugin.java index deb522bb6b1..89bdb78e631 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveSettingsPlugin.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemoveSettingsPlugin.java @@ -31,12 +31,12 @@ public class RemoveSettingsPlugin extends Recipe { @Override public String getDisplayName() { - return "Remove plugin from `settings.gradle(.kts)`"; + return "Remove Gradle settings plugin"; } @Override public String getDescription() { - return "Remove plugin from `settings.gradle(.kts)`."; + return "Remove plugin from Gradle settings file `plugins` block by id."; } @Override From e343554c248c44a96a934fa2b8614901cb82af6e Mon Sep 17 00:00:00 2001 From: Nelson Osacky <nelson@osacky.com> Date: Wed, 1 Nov 2023 20:49:11 +0100 Subject: [PATCH 363/447] Update Revved up by Develocity badge (#3653) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad6417f1734..9eae616461c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![ci](https://github.com/openrewrite/rewrite/actions/workflows/ci.yml/badge.svg)](https://github.com/openrewrite/rewrite/actions/workflows/ci.yml) [![Apache 2.0](https://img.shields.io/github/license/openrewrite/rewrite.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Maven Central](https://img.shields.io/maven-central/v/org.openrewrite/rewrite-java.svg)](https://mvnrepository.com/artifact/org.openrewrite/rewrite-java) -[![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.openrewrite.org/scans) +[![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.openrewrite.org/scans) [![Contributing Guide](https://img.shields.io/badge/Contributing-Guide-informational)](https://github.com/openrewrite/.github/blob/main/CONTRIBUTING.md) </div> From a5f1772b35c29e98e2d07ba6ab54ed37384b280e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Schn=C3=A9ider?= <jkschneider@gmail.com> Date: Wed, 1 Nov 2023 16:48:40 -0400 Subject: [PATCH 364/447] Info on committer activity (#3654) --- .../org/openrewrite/marker/GitProvenance.java | 59 ++++++++-- .../marker/ci/CircleCiBuildEnvironment.java | 4 +- .../marker/ci/CustomBuildEnvironment.java | 4 +- .../marker/ci/DroneBuildEnvironment.java | 4 +- .../ci/GithubActionsBuildEnvironment.java | 4 +- .../marker/ci/GitlabBuildEnvironment.java | 4 +- .../openrewrite/search/FindCommitters.java | 102 ++++++++++++++++++ .../org/openrewrite/table/CommitsByDay.java | 39 +++++++ .../openrewrite/table/DistinctCommitters.java | 39 +++++++ .../openrewrite/FindGitProvenanceTest.java | 6 +- .../openrewrite/marker/GitProvenanceTest.java | 8 +- .../search/FindCommittersTest.java | 56 ++++++++++ 12 files changed, 310 insertions(+), 19 deletions(-) create mode 100644 rewrite-core/src/main/java/org/openrewrite/search/FindCommitters.java create mode 100644 rewrite-core/src/main/java/org/openrewrite/table/CommitsByDay.java create mode 100644 rewrite-core/src/main/java/org/openrewrite/table/DistinctCommitters.java create mode 100644 rewrite-core/src/test/java/org/openrewrite/search/FindCommittersTest.java diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java b/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java index 0031960f114..c0d49881101 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java @@ -21,9 +21,11 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.*; +import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.treewalk.WorkingTreeOptions; +import org.openrewrite.Incubating; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.ci.BuildEnvironment; import org.openrewrite.marker.ci.IncompleteGitConfigException; @@ -35,10 +37,11 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.util.*; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; @Value @@ -52,6 +55,7 @@ public class GitProvenance implements Marker { @Nullable String branch; + @Nullable String change; @Nullable @@ -60,6 +64,10 @@ public class GitProvenance implements Marker { @Nullable EOL eol; + @Nullable + @Incubating(since = "8.9.0") + List<Committer> committers; + /** * Extract the organization name, including sub-organizations for git hosting services which support such a concept, * from the origin URL. Needs to be supplied with the @@ -140,8 +148,8 @@ public static GitProvenance fromProjectDirectory(Path projectDir) { * determined from a {@link BuildEnvironment} marker if possible. * @return A marker containing git provenance information. */ - public static @Nullable GitProvenance fromProjectDirectory(Path projectDir, @Nullable BuildEnvironment environment) { - + @Nullable + public static GitProvenance fromProjectDirectory(Path projectDir, @Nullable BuildEnvironment environment) { if (environment != null) { if (environment instanceof JenkinsBuildEnvironment) { JenkinsBuildEnvironment jenkinsBuildEnvironment = (JenkinsBuildEnvironment) environment; @@ -182,6 +190,7 @@ private static void printRequireGitDirOrWorkTreeException(Exception e) { } } + @Nullable private static GitProvenance fromGitConfig(Path projectDir) { String branch = null; try (Repository repository = new RepositoryBuilder().findGitDir(projectDir.toFile()).build()) { @@ -198,17 +207,18 @@ private static GitProvenance fromGitConfig(Path projectDir) { } } - private static GitProvenance fromGitConfig(Repository repository, @Nullable String branch, String changeset) { + private static GitProvenance fromGitConfig(Repository repository, @Nullable String branch, @Nullable String changeset) { if (branch == null) { branch = resolveBranchFromGitConfig(repository); } return new GitProvenance(randomId(), getOrigin(repository), branch, changeset, - getAutocrlf(repository), getEOF(repository)); + getAutocrlf(repository), getEOF(repository), + getCommitters(repository)); } @Nullable static String resolveBranchFromGitConfig(Repository repository) { - String branch = null; + String branch; try { try (Git git = Git.open(repository.getDirectory())) { ObjectId commit = repository.resolve(Constants.HEAD); @@ -256,7 +266,7 @@ private static String localBranchName(Repository repository, @Nullable String re List<RemoteConfig> remotes = git.remoteList().call(); for (RemoteConfig remote : remotes) { if (remoteBranch.startsWith(remote.getName()) && - (branch == null || branch.length() > remoteBranch.length() - remote.getName().length() - 1)) { + (branch == null || branch.length() > remoteBranch.length() - remote.getName().length() - 1)) { branch = remoteBranch.substring(remote.getName().length() + 1); // +1 for the forward slash } } @@ -308,6 +318,29 @@ private static EOL getEOF(Repository repository) { } } + private static List<Committer> getCommitters(Repository repository) { + try (Git git = Git.open(repository.getDirectory())) { + ObjectId head = repository.readOrigHead(); + if(head == null) { + return emptyList(); + } + + Map<String, Committer> committers = new TreeMap<>(); + for (RevCommit commit : git.log().add(head).call()) { + PersonIdent who = commit.getAuthorIdent(); + Committer committer = committers.computeIfAbsent(who.getEmailAddress(), + email -> new Committer(who.getName(), email, new TreeMap<>())); + committer.getCommitsByDay().compute(ZonedDateTime + .ofInstant(who.getWhen().toInstant(), who.getTimeZone().toZoneId()) + .toLocalDate(), + (day, count) -> count == null ? 1 : count + 1); + } + return new ArrayList<>(committers.values()); + } catch (IOException | GitAPIException e) { + return emptyList(); + } + } + @Nullable private static String getChangeset(Repository repository) throws IOException { ObjectId head = repository.resolve(Constants.HEAD); @@ -340,4 +373,12 @@ public enum EOL { LF, Native } + + @Value + @With + public static class Committer { + String name; + String email; + NavigableMap<LocalDate, Integer> commitsByDay; + } } diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/ci/CircleCiBuildEnvironment.java b/rewrite-core/src/main/java/org/openrewrite/marker/ci/CircleCiBuildEnvironment.java index 45befa6dc2b..3261e3c5c4f 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/ci/CircleCiBuildEnvironment.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/ci/CircleCiBuildEnvironment.java @@ -21,9 +21,11 @@ import org.openrewrite.internal.StringUtils; import org.openrewrite.marker.GitProvenance; +import java.util.Collections; import java.util.UUID; import java.util.function.UnaryOperator; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; import static org.openrewrite.marker.OperatingSystemProvenance.hostname; @@ -67,6 +69,6 @@ public GitProvenance buildGitProvenance() throws IncompleteGitConfigException { throw new IncompleteGitConfigException(); } return new GitProvenance(UUID.randomUUID(), repositoryURL, StringUtils.isBlank(branch)? tag : branch, - sha1, null, null); + sha1, null, null, emptyList()); } } diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/ci/CustomBuildEnvironment.java b/rewrite-core/src/main/java/org/openrewrite/marker/ci/CustomBuildEnvironment.java index a251f2649d2..b5ee098ff11 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/ci/CustomBuildEnvironment.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/ci/CustomBuildEnvironment.java @@ -24,6 +24,7 @@ import java.util.UUID; import java.util.function.UnaryOperator; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; @Value @@ -50,7 +51,8 @@ public GitProvenance buildGitProvenance() throws IncompleteGitConfigException { || StringUtils.isBlank(sha)) { throw new IncompleteGitConfigException(); } else { - return new GitProvenance(UUID.randomUUID(), cloneURL, ref, sha, null, null); + return new GitProvenance(UUID.randomUUID(), cloneURL, ref, sha, + null, null, emptyList()); } } diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/ci/DroneBuildEnvironment.java b/rewrite-core/src/main/java/org/openrewrite/marker/ci/DroneBuildEnvironment.java index 63a4730dcf5..62df3e4dcf8 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/ci/DroneBuildEnvironment.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/ci/DroneBuildEnvironment.java @@ -24,6 +24,7 @@ import java.util.UUID; import java.util.function.UnaryOperator; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; import static org.openrewrite.marker.OperatingSystemProvenance.hostname; @@ -67,7 +68,8 @@ public GitProvenance buildGitProvenance() throws IncompleteGitConfigException { throw new IncompleteGitConfigException(); } return new GitProvenance(UUID.randomUUID(), remoteURL, - StringUtils.isBlank(branch)? tag: branch, commitSha, null, null); + StringUtils.isBlank(branch)? tag: branch, commitSha, + null, null, emptyList()); } public String getBuildUrl() { diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/ci/GithubActionsBuildEnvironment.java b/rewrite-core/src/main/java/org/openrewrite/marker/ci/GithubActionsBuildEnvironment.java index 8600379ef87..60b15ef104c 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/ci/GithubActionsBuildEnvironment.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/ci/GithubActionsBuildEnvironment.java @@ -24,6 +24,7 @@ import java.util.UUID; import java.util.function.UnaryOperator; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; @Value @@ -75,7 +76,6 @@ public GitProvenance buildGitProvenance() throws IncompleteGitConfigException { } return new GitProvenance(UUID.randomUUID(), host + "/" + getRepository() - + ".git", gitRef, getSha(), null, null); + + ".git", gitRef, getSha(), null, null, emptyList()); } - } diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/ci/GitlabBuildEnvironment.java b/rewrite-core/src/main/java/org/openrewrite/marker/ci/GitlabBuildEnvironment.java index 4ebe96ffbcb..fa5ed975817 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/ci/GitlabBuildEnvironment.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/ci/GitlabBuildEnvironment.java @@ -24,6 +24,7 @@ import java.util.UUID; import java.util.function.UnaryOperator; +import static java.util.Collections.emptyList; import static org.openrewrite.Tree.randomId; @Value @@ -60,6 +61,7 @@ public GitProvenance buildGitProvenance() throws IncompleteGitConfigException { || StringUtils.isBlank(ciCommitSha)) { throw new IncompleteGitConfigException(); } - return new GitProvenance(UUID.randomUUID(), ciRepositoryUrl, ciCommitRefName, ciCommitSha, null, null); + return new GitProvenance(UUID.randomUUID(), ciRepositoryUrl, ciCommitRefName, ciCommitSha, + null, null, emptyList()); } } diff --git a/rewrite-core/src/main/java/org/openrewrite/search/FindCommitters.java b/rewrite-core/src/main/java/org/openrewrite/search/FindCommitters.java new file mode 100644 index 00000000000..cf9f08a4956 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/search/FindCommitters.java @@ -0,0 +1,102 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.search; + +import org.openrewrite.*; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.GitProvenance; +import org.openrewrite.marker.SearchResult; +import org.openrewrite.table.CommitsByDay; +import org.openrewrite.table.DistinctCommitters; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +public class FindCommitters extends ScanningRecipe<Map<String, GitProvenance.Committer>> { + private transient final DistinctCommitters committers = new DistinctCommitters(this); + private transient final CommitsByDay commitsByDay = new CommitsByDay(this); + + @Override + public String getDisplayName() { + return "Find committers on repositories"; + } + + @Override + public String getDescription() { + return "List the committers on a repository."; + } + + @Override + public Map<String, GitProvenance.Committer> getInitialValue(ExecutionContext ctx) { + return new TreeMap<>(); + } + + @Override + public TreeVisitor<?, ExecutionContext> getScanner(Map<String, GitProvenance.Committer> acc) { + return new TreeVisitor<Tree, ExecutionContext>() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof SourceFile) { + SourceFile sourceFile = (SourceFile) tree; + sourceFile.getMarkers().findFirst(GitProvenance.class).ifPresent(provenance -> { + if (provenance.getCommitters() != null) { + for (GitProvenance.Committer committer : provenance.getCommitters()) { + acc.put(committer.getEmail(), committer); + } + } + }); + } + return tree; + } + }; + } + + @Override + public Collection<? extends SourceFile> generate(Map<String, GitProvenance.Committer> acc, ExecutionContext ctx) { + for (GitProvenance.Committer committer : acc.values()) { + committers.insertRow(ctx, new DistinctCommitters.Row( + committer.getName(), + committer.getEmail(), + committer.getCommitsByDay().lastKey(), + committer.getCommitsByDay().values().stream().mapToInt(Integer::intValue).sum() + )); + + committer.getCommitsByDay().forEach((day, commits) -> commitsByDay.insertRow(ctx, new CommitsByDay.Row( + committer.getName(), + committer.getEmail(), + day, + commits + ))); + } + return Collections.emptyList(); + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor(Map<String, GitProvenance.Committer> acc) { + return new TreeVisitor<Tree, ExecutionContext>() { + @Override + @Nullable + public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof SourceFile) { + return SearchResult.found(tree, String.join("\n", acc.keySet())); + } + return tree; + } + }; + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/table/CommitsByDay.java b/rewrite-core/src/main/java/org/openrewrite/table/CommitsByDay.java new file mode 100644 index 00000000000..d05b06f8d44 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/table/CommitsByDay.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.table; + +import lombok.Value; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +import java.time.LocalDate; + +public class CommitsByDay extends DataTable<CommitsByDay.Row> { + + public CommitsByDay(Recipe recipe) { + super(recipe, + "Commits by day", + "The commit activity by day by committer."); + } + + @Value + public static class Row { + String name; + String email; + LocalDate day; + int commits; + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/table/DistinctCommitters.java b/rewrite-core/src/main/java/org/openrewrite/table/DistinctCommitters.java new file mode 100644 index 00000000000..08dffaededa --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/table/DistinctCommitters.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.table; + +import lombok.Value; +import org.openrewrite.DataTable; +import org.openrewrite.Recipe; + +import java.time.LocalDate; + +public class DistinctCommitters extends DataTable<DistinctCommitters.Row> { + + public DistinctCommitters(Recipe recipe) { + super(recipe, + "Repository committers", + "The distinct set of committers per repository."); + } + + @Value + public static class Row { + String name; + String email; + LocalDate lastCommit; + int commits; + } +} diff --git a/rewrite-core/src/test/java/org/openrewrite/FindGitProvenanceTest.java b/rewrite-core/src/test/java/org/openrewrite/FindGitProvenanceTest.java index 07362368ec1..bac7647aa6e 100644 --- a/rewrite-core/src/test/java/org/openrewrite/FindGitProvenanceTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/FindGitProvenanceTest.java @@ -21,6 +21,9 @@ import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import java.util.Collections; + +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.marker.GitProvenance.AutoCRLF.False; import static org.openrewrite.marker.GitProvenance.EOL.Native; @@ -45,7 +48,8 @@ void showGitProvenance() { }), text( "Hello, World!", - spec -> spec.markers(new GitProvenance(Tree.randomId(), "https://github.com/openrewrite/rewrite", "main", "1234567", False, Native)) + spec -> spec.markers(new GitProvenance(Tree.randomId(), "https://github.com/openrewrite/rewrite", + "main", "1234567", False, Native, emptyList())) ) ); } diff --git a/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java b/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java index d14f7eda6ab..2f97f79af70 100644 --- a/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java @@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Stream; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_BRANCH_SECTION; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -65,14 +66,14 @@ private static Stream<String> remotes() { @ParameterizedTest @MethodSource("remotes") void getOrganizationName(String remote) { - assertThat(new GitProvenance(randomId(), remote, "main", "123", null, null).getOrganizationName()) + assertThat(new GitProvenance(randomId(), remote, "main", "123", null, null, emptyList()).getOrganizationName()) .isEqualTo("openrewrite"); } @ParameterizedTest @MethodSource("remotes") void getRepositoryName(String remote) { - assertThat(new GitProvenance(randomId(), remote, "main", "123", null, null).getRepositoryName()) + assertThat(new GitProvenance(randomId(), remote, "main", "123", null, null, emptyList()).getRepositoryName()) .isEqualTo("rewrite"); } @@ -192,7 +193,8 @@ void multiplePathSegments(String baseUrl) { "master", "1234567890abcdef1234567890abcdef12345678", null, - null); + null, + emptyList()); assertThat(provenance.getOrganizationName(baseUrl)).isEqualTo("group/subgroup1/subgroup2"); assertThat(provenance.getRepositoryName()).isEqualTo("repo"); diff --git a/rewrite-core/src/test/java/org/openrewrite/search/FindCommittersTest.java b/rewrite-core/src/test/java/org/openrewrite/search/FindCommittersTest.java new file mode 100644 index 00000000000..2239b5fe85d --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/search/FindCommittersTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.search; + +import org.junit.jupiter.api.Test; +import org.openrewrite.marker.GitProvenance; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import java.time.LocalDate; +import java.util.List; +import java.util.TreeMap; + +import static org.openrewrite.Tree.randomId; +import static org.openrewrite.test.SourceSpecs.text; + +public class FindCommittersTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new FindCommitters()); + } + + @Test + void findCommitters() { + GitProvenance git = new GitProvenance( + randomId(), "github.com", "main", "123", null, null, + List.of(new GitProvenance.Committer("Jon", "jkschneider@gmail.com", + new TreeMap<>() {{ + put(LocalDate.now().minusDays(5), 5); + put(LocalDate.now(), 5); + }})) + ); + + rewriteRun( + text( + "hi", + "~~(jkschneider@gmail.com)~~>hi", + spec -> spec.mapBeforeRecipe(pt -> pt.withMarkers(pt.getMarkers().add(git))) + ) + ); + } +} From 8bdf967117c603bffc1533801d6df92a2861cf42 Mon Sep 17 00:00:00 2001 From: Jonathan Schneider <jkschneider@gmail.com> Date: Wed, 1 Nov 2023 20:31:37 -0400 Subject: [PATCH 365/447] Allow FindAnnotations annotationPattern to not start with '@' --- .../java/search/FindAnnotationsTest.java | 13 +- .../main/antlr/AnnotationSignatureParser.g4 | 2 +- .../grammar/AnnotationSignatureParser.interp | 2 +- .../grammar/AnnotationSignatureParser.java | 171 +++++++++--------- 4 files changed, 99 insertions(+), 89 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindAnnotationsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindAnnotationsTest.java index e690611680b..5c906624019 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindAnnotationsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/search/FindAnnotationsTest.java @@ -17,6 +17,8 @@ import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.openrewrite.DocumentExample; import org.openrewrite.Issue; import org.openrewrite.java.JavaParser; @@ -39,7 +41,6 @@ class FindAnnotationsTest implements RewriteTest { @Test void matchMetaAnnotation() { rewriteRun( -// JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()).logCompilationWarningsAndErrors(true).build(), spec -> spec.recipe(new FindAnnotations("@javax.annotation.Nonnull", true)) .parser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())), java( @@ -94,10 +95,11 @@ public class MyExtension implements Extension {} ); } - @Test - void matchesSimpleFullyQualifiedAnnotation() { + @ParameterizedTest + @ValueSource(strings = {"@java.lang.Deprecated", "java.lang.Deprecated"}) + void matchesSimpleFullyQualifiedAnnotation(String annotationPattern) { rewriteRun( - spec -> spec.recipe(new FindAnnotations("@java.lang.Deprecated", null)), + spec -> spec.recipe(new FindAnnotations(annotationPattern, null)), java( "@Deprecated public class A {}", "/*~~>*/@Deprecated public class A {}" @@ -105,7 +107,6 @@ void matchesSimpleFullyQualifiedAnnotation() { ); } - @Test void matchesWildcard() { rewriteRun( @@ -296,7 +297,7 @@ void findAnnotationWithClassTypeArgument() { java( """ package com.foo; - + @Example(Foo.class) public class Foo {} """, diff --git a/rewrite-java/src/main/antlr/AnnotationSignatureParser.g4 b/rewrite-java/src/main/antlr/AnnotationSignatureParser.g4 index 4edd23e0884..9b203912956 100644 --- a/rewrite-java/src/main/antlr/AnnotationSignatureParser.g4 +++ b/rewrite-java/src/main/antlr/AnnotationSignatureParser.g4 @@ -3,7 +3,7 @@ parser grammar AnnotationSignatureParser; options { tokenVocab=AnnotationSignatureLexer; } annotation - : AT annotationName ( LPAREN ( elementValuePairs | elementValue )? RPAREN )? + : AT? annotationName ( LPAREN ( elementValuePairs | elementValue )? RPAREN )? ; annotationName diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.interp b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.interp index 992392f0c67..29bdb7426ab 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.interp +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.interp @@ -62,4 +62,4 @@ literal atn: -[4, 1, 22, 77, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 3, 0, 26, 8, 0, 1, 0, 3, 0, 29, 8, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 5, 2, 36, 8, 2, 10, 2, 12, 2, 39, 9, 2, 1, 3, 1, 3, 1, 3, 5, 3, 44, 8, 3, 10, 3, 12, 3, 47, 9, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 3, 6, 57, 8, 6, 1, 7, 1, 7, 1, 7, 5, 7, 62, 8, 7, 10, 7, 12, 7, 65, 9, 7, 1, 8, 1, 8, 1, 8, 5, 8, 70, 8, 8, 10, 8, 12, 8, 73, 9, 8, 1, 9, 1, 9, 1, 9, 0, 0, 10, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 0, 2, 2, 0, 11, 11, 20, 20, 1, 0, 1, 5, 74, 0, 20, 1, 0, 0, 0, 2, 30, 1, 0, 0, 0, 4, 32, 1, 0, 0, 0, 6, 40, 1, 0, 0, 0, 8, 48, 1, 0, 0, 0, 10, 52, 1, 0, 0, 0, 12, 56, 1, 0, 0, 0, 14, 58, 1, 0, 0, 0, 16, 66, 1, 0, 0, 0, 18, 74, 1, 0, 0, 0, 20, 21, 5, 18, 0, 0, 21, 28, 3, 2, 1, 0, 22, 25, 5, 6, 0, 0, 23, 26, 3, 6, 3, 0, 24, 26, 3, 10, 5, 0, 25, 23, 1, 0, 0, 0, 25, 24, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 29, 5, 7, 0, 0, 28, 22, 1, 0, 0, 0, 28, 29, 1, 0, 0, 0, 29, 1, 1, 0, 0, 0, 30, 31, 3, 4, 2, 0, 31, 3, 1, 0, 0, 0, 32, 37, 5, 22, 0, 0, 33, 34, 7, 0, 0, 0, 34, 36, 5, 22, 0, 0, 35, 33, 1, 0, 0, 0, 36, 39, 1, 0, 0, 0, 37, 35, 1, 0, 0, 0, 37, 38, 1, 0, 0, 0, 38, 5, 1, 0, 0, 0, 39, 37, 1, 0, 0, 0, 40, 45, 3, 8, 4, 0, 41, 42, 5, 10, 0, 0, 42, 44, 3, 8, 4, 0, 43, 41, 1, 0, 0, 0, 44, 47, 1, 0, 0, 0, 45, 43, 1, 0, 0, 0, 45, 46, 1, 0, 0, 0, 46, 7, 1, 0, 0, 0, 47, 45, 1, 0, 0, 0, 48, 49, 5, 22, 0, 0, 49, 50, 5, 12, 0, 0, 50, 51, 3, 10, 5, 0, 51, 9, 1, 0, 0, 0, 52, 53, 3, 12, 6, 0, 53, 11, 1, 0, 0, 0, 54, 57, 3, 18, 9, 0, 55, 57, 3, 14, 7, 0, 56, 54, 1, 0, 0, 0, 56, 55, 1, 0, 0, 0, 57, 13, 1, 0, 0, 0, 58, 63, 3, 16, 8, 0, 59, 60, 5, 8, 0, 0, 60, 62, 5, 9, 0, 0, 61, 59, 1, 0, 0, 0, 62, 65, 1, 0, 0, 0, 63, 61, 1, 0, 0, 0, 63, 64, 1, 0, 0, 0, 64, 15, 1, 0, 0, 0, 65, 63, 1, 0, 0, 0, 66, 71, 5, 22, 0, 0, 67, 68, 7, 0, 0, 0, 68, 70, 5, 22, 0, 0, 69, 67, 1, 0, 0, 0, 70, 73, 1, 0, 0, 0, 71, 69, 1, 0, 0, 0, 71, 72, 1, 0, 0, 0, 72, 17, 1, 0, 0, 0, 73, 71, 1, 0, 0, 0, 74, 75, 7, 1, 0, 0, 75, 19, 1, 0, 0, 0, 7, 25, 28, 37, 45, 56, 63, 71] \ No newline at end of file +[4, 1, 22, 79, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 1, 0, 3, 0, 22, 8, 0, 1, 0, 1, 0, 1, 0, 1, 0, 3, 0, 28, 8, 0, 1, 0, 3, 0, 31, 8, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 5, 2, 38, 8, 2, 10, 2, 12, 2, 41, 9, 2, 1, 3, 1, 3, 1, 3, 5, 3, 46, 8, 3, 10, 3, 12, 3, 49, 9, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 3, 6, 59, 8, 6, 1, 7, 1, 7, 1, 7, 5, 7, 64, 8, 7, 10, 7, 12, 7, 67, 9, 7, 1, 8, 1, 8, 1, 8, 5, 8, 72, 8, 8, 10, 8, 12, 8, 75, 9, 8, 1, 9, 1, 9, 1, 9, 0, 0, 10, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 0, 2, 2, 0, 11, 11, 20, 20, 1, 0, 1, 5, 77, 0, 21, 1, 0, 0, 0, 2, 32, 1, 0, 0, 0, 4, 34, 1, 0, 0, 0, 6, 42, 1, 0, 0, 0, 8, 50, 1, 0, 0, 0, 10, 54, 1, 0, 0, 0, 12, 58, 1, 0, 0, 0, 14, 60, 1, 0, 0, 0, 16, 68, 1, 0, 0, 0, 18, 76, 1, 0, 0, 0, 20, 22, 5, 18, 0, 0, 21, 20, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 30, 3, 2, 1, 0, 24, 27, 5, 6, 0, 0, 25, 28, 3, 6, 3, 0, 26, 28, 3, 10, 5, 0, 27, 25, 1, 0, 0, 0, 27, 26, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 29, 1, 0, 0, 0, 29, 31, 5, 7, 0, 0, 30, 24, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 1, 1, 0, 0, 0, 32, 33, 3, 4, 2, 0, 33, 3, 1, 0, 0, 0, 34, 39, 5, 22, 0, 0, 35, 36, 7, 0, 0, 0, 36, 38, 5, 22, 0, 0, 37, 35, 1, 0, 0, 0, 38, 41, 1, 0, 0, 0, 39, 37, 1, 0, 0, 0, 39, 40, 1, 0, 0, 0, 40, 5, 1, 0, 0, 0, 41, 39, 1, 0, 0, 0, 42, 47, 3, 8, 4, 0, 43, 44, 5, 10, 0, 0, 44, 46, 3, 8, 4, 0, 45, 43, 1, 0, 0, 0, 46, 49, 1, 0, 0, 0, 47, 45, 1, 0, 0, 0, 47, 48, 1, 0, 0, 0, 48, 7, 1, 0, 0, 0, 49, 47, 1, 0, 0, 0, 50, 51, 5, 22, 0, 0, 51, 52, 5, 12, 0, 0, 52, 53, 3, 10, 5, 0, 53, 9, 1, 0, 0, 0, 54, 55, 3, 12, 6, 0, 55, 11, 1, 0, 0, 0, 56, 59, 3, 18, 9, 0, 57, 59, 3, 14, 7, 0, 58, 56, 1, 0, 0, 0, 58, 57, 1, 0, 0, 0, 59, 13, 1, 0, 0, 0, 60, 65, 3, 16, 8, 0, 61, 62, 5, 8, 0, 0, 62, 64, 5, 9, 0, 0, 63, 61, 1, 0, 0, 0, 64, 67, 1, 0, 0, 0, 65, 63, 1, 0, 0, 0, 65, 66, 1, 0, 0, 0, 66, 15, 1, 0, 0, 0, 67, 65, 1, 0, 0, 0, 68, 73, 5, 22, 0, 0, 69, 70, 7, 0, 0, 0, 70, 72, 5, 22, 0, 0, 71, 69, 1, 0, 0, 0, 72, 75, 1, 0, 0, 0, 73, 71, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 17, 1, 0, 0, 0, 75, 73, 1, 0, 0, 0, 76, 77, 7, 1, 0, 0, 77, 19, 1, 0, 0, 0, 8, 21, 27, 30, 39, 47, 58, 65, 73] \ No newline at end of file diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.java index b5509f2d7b3..c27dd16856f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/grammar/AnnotationSignatureParser.java @@ -118,10 +118,10 @@ public AnnotationSignatureParser(TokenStream input) { @SuppressWarnings("CheckReturnValue") public static class AnnotationContext extends ParserRuleContext { - public TerminalNode AT() { return getToken(AnnotationSignatureParser.AT, 0); } public AnnotationNameContext annotationName() { return getRuleContext(AnnotationNameContext.class,0); } + public TerminalNode AT() { return getToken(AnnotationSignatureParser.AT, 0); } public TerminalNode LPAREN() { return getToken(AnnotationSignatureParser.LPAREN, 0); } public TerminalNode RPAREN() { return getToken(AnnotationSignatureParser.RPAREN, 0); } public ElementValuePairsContext elementValuePairs() { @@ -156,34 +156,42 @@ public final AnnotationContext annotation() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(20); - match(AT); setState(21); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==AT) { + { + setState(20); + match(AT); + } + } + + setState(23); annotationName(); - setState(28); + setState(30); _errHandler.sync(this); _la = _input.LA(1); if (_la==LPAREN) { { - setState(22); + setState(24); match(LPAREN); - setState(25); + setState(27); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,0,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,1,_ctx) ) { case 1: { - setState(23); + setState(25); elementValuePairs(); } break; case 2: { - setState(24); + setState(26); elementValue(); } break; } - setState(27); + setState(29); match(RPAREN); } } @@ -231,7 +239,7 @@ public final AnnotationNameContext annotationName() throws RecognitionException try { enterOuterAlt(_localctx, 1); { - setState(30); + setState(32); qualifiedName(); } } @@ -286,15 +294,15 @@ public final QualifiedNameContext qualifiedName() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(32); + setState(34); match(Identifier); - setState(37); + setState(39); _errHandler.sync(this); _la = _input.LA(1); while (_la==DOT || _la==DOTDOT) { { { - setState(33); + setState(35); _la = _input.LA(1); if ( !(_la==DOT || _la==DOTDOT) ) { _errHandler.recoverInline(this); @@ -304,11 +312,11 @@ public final QualifiedNameContext qualifiedName() throws RecognitionException { _errHandler.reportMatch(this); consume(); } - setState(34); + setState(36); match(Identifier); } } - setState(39); + setState(41); _errHandler.sync(this); _la = _input.LA(1); } @@ -363,21 +371,21 @@ public final ElementValuePairsContext elementValuePairs() throws RecognitionExce try { enterOuterAlt(_localctx, 1); { - setState(40); + setState(42); elementValuePair(); - setState(45); + setState(47); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(41); + setState(43); match(COMMA); - setState(42); + setState(44); elementValuePair(); } } - setState(47); + setState(49); _errHandler.sync(this); _la = _input.LA(1); } @@ -426,11 +434,11 @@ public final ElementValuePairContext elementValuePair() throws RecognitionExcept try { enterOuterAlt(_localctx, 1); { - setState(48); + setState(50); match(Identifier); - setState(49); + setState(51); match(ASSIGN); - setState(50); + setState(52); elementValue(); } } @@ -475,7 +483,7 @@ public final ElementValueContext elementValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(52); + setState(54); primary(); } } @@ -521,7 +529,7 @@ public final PrimaryContext primary() throws RecognitionException { PrimaryContext _localctx = new PrimaryContext(_ctx, getState()); enterRule(_localctx, 12, RULE_primary); try { - setState(56); + setState(58); _errHandler.sync(this); switch (_input.LA(1)) { case IntegerLiteral: @@ -531,14 +539,14 @@ public final PrimaryContext primary() throws RecognitionException { case StringLiteral: enterOuterAlt(_localctx, 1); { - setState(54); + setState(56); literal(); } break; case Identifier: enterOuterAlt(_localctx, 2); { - setState(55); + setState(57); type(); } break; @@ -596,21 +604,21 @@ public final TypeContext type() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(58); + setState(60); classOrInterfaceType(); - setState(63); + setState(65); _errHandler.sync(this); _la = _input.LA(1); while (_la==LBRACK) { { { - setState(59); + setState(61); match(LBRACK); - setState(60); + setState(62); match(RBRACK); } } - setState(65); + setState(67); _errHandler.sync(this); _la = _input.LA(1); } @@ -667,15 +675,15 @@ public final ClassOrInterfaceTypeContext classOrInterfaceType() throws Recogniti try { enterOuterAlt(_localctx, 1); { - setState(66); + setState(68); match(Identifier); - setState(71); + setState(73); _errHandler.sync(this); _la = _input.LA(1); while (_la==DOT || _la==DOTDOT) { { { - setState(67); + setState(69); _la = _input.LA(1); if ( !(_la==DOT || _la==DOTDOT) ) { _errHandler.recoverInline(this); @@ -685,11 +693,11 @@ public final ClassOrInterfaceTypeContext classOrInterfaceType() throws Recogniti _errHandler.reportMatch(this); consume(); } - setState(68); + setState(70); match(Identifier); } } - setState(73); + setState(75); _errHandler.sync(this); _la = _input.LA(1); } @@ -739,7 +747,7 @@ public final LiteralContext literal() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(74); + setState(76); _la = _input.LA(1); if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 62L) != 0) ) { _errHandler.recoverInline(this); @@ -763,50 +771,51 @@ public final LiteralContext literal() throws RecognitionException { } public static final String _serializedATN = - "\u0004\u0001\u0016M\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ + "\u0004\u0001\u0016O\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ "\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004\u0002"+ "\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007\u0002"+ - "\b\u0007\b\u0002\t\u0007\t\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000"+ - "\u0001\u0000\u0003\u0000\u001a\b\u0000\u0001\u0000\u0003\u0000\u001d\b"+ - "\u0000\u0001\u0001\u0001\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0005"+ - "\u0002$\b\u0002\n\u0002\f\u0002\'\t\u0002\u0001\u0003\u0001\u0003\u0001"+ - "\u0003\u0005\u0003,\b\u0003\n\u0003\f\u0003/\t\u0003\u0001\u0004\u0001"+ - "\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0006\u0001"+ - "\u0006\u0003\u00069\b\u0006\u0001\u0007\u0001\u0007\u0001\u0007\u0005"+ - "\u0007>\b\u0007\n\u0007\f\u0007A\t\u0007\u0001\b\u0001\b\u0001\b\u0005"+ - "\bF\b\b\n\b\f\bI\t\b\u0001\t\u0001\t\u0001\t\u0000\u0000\n\u0000\u0002"+ - "\u0004\u0006\b\n\f\u000e\u0010\u0012\u0000\u0002\u0002\u0000\u000b\u000b"+ - "\u0014\u0014\u0001\u0000\u0001\u0005J\u0000\u0014\u0001\u0000\u0000\u0000"+ - "\u0002\u001e\u0001\u0000\u0000\u0000\u0004 \u0001\u0000\u0000\u0000\u0006"+ - "(\u0001\u0000\u0000\u0000\b0\u0001\u0000\u0000\u0000\n4\u0001\u0000\u0000"+ - "\u0000\f8\u0001\u0000\u0000\u0000\u000e:\u0001\u0000\u0000\u0000\u0010"+ - "B\u0001\u0000\u0000\u0000\u0012J\u0001\u0000\u0000\u0000\u0014\u0015\u0005"+ - "\u0012\u0000\u0000\u0015\u001c\u0003\u0002\u0001\u0000\u0016\u0019\u0005"+ - "\u0006\u0000\u0000\u0017\u001a\u0003\u0006\u0003\u0000\u0018\u001a\u0003"+ - "\n\u0005\u0000\u0019\u0017\u0001\u0000\u0000\u0000\u0019\u0018\u0001\u0000"+ - "\u0000\u0000\u0019\u001a\u0001\u0000\u0000\u0000\u001a\u001b\u0001\u0000"+ - "\u0000\u0000\u001b\u001d\u0005\u0007\u0000\u0000\u001c\u0016\u0001\u0000"+ - "\u0000\u0000\u001c\u001d\u0001\u0000\u0000\u0000\u001d\u0001\u0001\u0000"+ - "\u0000\u0000\u001e\u001f\u0003\u0004\u0002\u0000\u001f\u0003\u0001\u0000"+ - "\u0000\u0000 %\u0005\u0016\u0000\u0000!\"\u0007\u0000\u0000\u0000\"$\u0005"+ - "\u0016\u0000\u0000#!\u0001\u0000\u0000\u0000$\'\u0001\u0000\u0000\u0000"+ - "%#\u0001\u0000\u0000\u0000%&\u0001\u0000\u0000\u0000&\u0005\u0001\u0000"+ - "\u0000\u0000\'%\u0001\u0000\u0000\u0000(-\u0003\b\u0004\u0000)*\u0005"+ - "\n\u0000\u0000*,\u0003\b\u0004\u0000+)\u0001\u0000\u0000\u0000,/\u0001"+ - "\u0000\u0000\u0000-+\u0001\u0000\u0000\u0000-.\u0001\u0000\u0000\u0000"+ - ".\u0007\u0001\u0000\u0000\u0000/-\u0001\u0000\u0000\u000001\u0005\u0016"+ - "\u0000\u000012\u0005\f\u0000\u000023\u0003\n\u0005\u00003\t\u0001\u0000"+ - "\u0000\u000045\u0003\f\u0006\u00005\u000b\u0001\u0000\u0000\u000069\u0003"+ - "\u0012\t\u000079\u0003\u000e\u0007\u000086\u0001\u0000\u0000\u000087\u0001"+ - "\u0000\u0000\u00009\r\u0001\u0000\u0000\u0000:?\u0003\u0010\b\u0000;<"+ - "\u0005\b\u0000\u0000<>\u0005\t\u0000\u0000=;\u0001\u0000\u0000\u0000>"+ - "A\u0001\u0000\u0000\u0000?=\u0001\u0000\u0000\u0000?@\u0001\u0000\u0000"+ - "\u0000@\u000f\u0001\u0000\u0000\u0000A?\u0001\u0000\u0000\u0000BG\u0005"+ - "\u0016\u0000\u0000CD\u0007\u0000\u0000\u0000DF\u0005\u0016\u0000\u0000"+ - "EC\u0001\u0000\u0000\u0000FI\u0001\u0000\u0000\u0000GE\u0001\u0000\u0000"+ - "\u0000GH\u0001\u0000\u0000\u0000H\u0011\u0001\u0000\u0000\u0000IG\u0001"+ - "\u0000\u0000\u0000JK\u0007\u0001\u0000\u0000K\u0013\u0001\u0000\u0000"+ - "\u0000\u0007\u0019\u001c%-8?G"; + "\b\u0007\b\u0002\t\u0007\t\u0001\u0000\u0003\u0000\u0016\b\u0000\u0001"+ + "\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0003\u0000\u001c\b\u0000\u0001"+ + "\u0000\u0003\u0000\u001f\b\u0000\u0001\u0001\u0001\u0001\u0001\u0002\u0001"+ + "\u0002\u0001\u0002\u0005\u0002&\b\u0002\n\u0002\f\u0002)\t\u0002\u0001"+ + "\u0003\u0001\u0003\u0001\u0003\u0005\u0003.\b\u0003\n\u0003\f\u00031\t"+ + "\u0003\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001"+ + "\u0005\u0001\u0006\u0001\u0006\u0003\u0006;\b\u0006\u0001\u0007\u0001"+ + "\u0007\u0001\u0007\u0005\u0007@\b\u0007\n\u0007\f\u0007C\t\u0007\u0001"+ + "\b\u0001\b\u0001\b\u0005\bH\b\b\n\b\f\bK\t\b\u0001\t\u0001\t\u0001\t\u0000"+ + "\u0000\n\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0000\u0002\u0002"+ + "\u0000\u000b\u000b\u0014\u0014\u0001\u0000\u0001\u0005M\u0000\u0015\u0001"+ + "\u0000\u0000\u0000\u0002 \u0001\u0000\u0000\u0000\u0004\"\u0001\u0000"+ + "\u0000\u0000\u0006*\u0001\u0000\u0000\u0000\b2\u0001\u0000\u0000\u0000"+ + "\n6\u0001\u0000\u0000\u0000\f:\u0001\u0000\u0000\u0000\u000e<\u0001\u0000"+ + "\u0000\u0000\u0010D\u0001\u0000\u0000\u0000\u0012L\u0001\u0000\u0000\u0000"+ + "\u0014\u0016\u0005\u0012\u0000\u0000\u0015\u0014\u0001\u0000\u0000\u0000"+ + "\u0015\u0016\u0001\u0000\u0000\u0000\u0016\u0017\u0001\u0000\u0000\u0000"+ + "\u0017\u001e\u0003\u0002\u0001\u0000\u0018\u001b\u0005\u0006\u0000\u0000"+ + "\u0019\u001c\u0003\u0006\u0003\u0000\u001a\u001c\u0003\n\u0005\u0000\u001b"+ + "\u0019\u0001\u0000\u0000\u0000\u001b\u001a\u0001\u0000\u0000\u0000\u001b"+ + "\u001c\u0001\u0000\u0000\u0000\u001c\u001d\u0001\u0000\u0000\u0000\u001d"+ + "\u001f\u0005\u0007\u0000\u0000\u001e\u0018\u0001\u0000\u0000\u0000\u001e"+ + "\u001f\u0001\u0000\u0000\u0000\u001f\u0001\u0001\u0000\u0000\u0000 !\u0003"+ + "\u0004\u0002\u0000!\u0003\u0001\u0000\u0000\u0000\"\'\u0005\u0016\u0000"+ + "\u0000#$\u0007\u0000\u0000\u0000$&\u0005\u0016\u0000\u0000%#\u0001\u0000"+ + "\u0000\u0000&)\u0001\u0000\u0000\u0000\'%\u0001\u0000\u0000\u0000\'(\u0001"+ + "\u0000\u0000\u0000(\u0005\u0001\u0000\u0000\u0000)\'\u0001\u0000\u0000"+ + "\u0000*/\u0003\b\u0004\u0000+,\u0005\n\u0000\u0000,.\u0003\b\u0004\u0000"+ + "-+\u0001\u0000\u0000\u0000.1\u0001\u0000\u0000\u0000/-\u0001\u0000\u0000"+ + "\u0000/0\u0001\u0000\u0000\u00000\u0007\u0001\u0000\u0000\u00001/\u0001"+ + "\u0000\u0000\u000023\u0005\u0016\u0000\u000034\u0005\f\u0000\u000045\u0003"+ + "\n\u0005\u00005\t\u0001\u0000\u0000\u000067\u0003\f\u0006\u00007\u000b"+ + "\u0001\u0000\u0000\u00008;\u0003\u0012\t\u00009;\u0003\u000e\u0007\u0000"+ + ":8\u0001\u0000\u0000\u0000:9\u0001\u0000\u0000\u0000;\r\u0001\u0000\u0000"+ + "\u0000<A\u0003\u0010\b\u0000=>\u0005\b\u0000\u0000>@\u0005\t\u0000\u0000"+ + "?=\u0001\u0000\u0000\u0000@C\u0001\u0000\u0000\u0000A?\u0001\u0000\u0000"+ + "\u0000AB\u0001\u0000\u0000\u0000B\u000f\u0001\u0000\u0000\u0000CA\u0001"+ + "\u0000\u0000\u0000DI\u0005\u0016\u0000\u0000EF\u0007\u0000\u0000\u0000"+ + "FH\u0005\u0016\u0000\u0000GE\u0001\u0000\u0000\u0000HK\u0001\u0000\u0000"+ + "\u0000IG\u0001\u0000\u0000\u0000IJ\u0001\u0000\u0000\u0000J\u0011\u0001"+ + "\u0000\u0000\u0000KI\u0001\u0000\u0000\u0000LM\u0007\u0001\u0000\u0000"+ + "M\u0013\u0001\u0000\u0000\u0000\b\u0015\u001b\u001e\'/:AI"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { From 30a74ee255a072070993aa6862784ccbbe8cfe62 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 2 Nov 2023 14:41:39 +0100 Subject: [PATCH 366/447] Match `JavaType.GenericTypeVariable` in `TypeUtils#isAssignableTo()` (#3655) Fix the assignability test for generic type variable types. --- .../openrewrite/java/tree/TypeUtilsTest.java | 32 +++++++++++++++++++ .../org/openrewrite/java/tree/TypeUtils.java | 9 ++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java index 7b2c4fbcc5c..7758f99be21 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java @@ -245,4 +245,36 @@ public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations ) ); } + + @Test + void isAssignableToGenericTypeVariable() { + rewriteRun( + java( + """ + import java.util.Map; + import java.util.function.Supplier; + + class Test { + <K, V> void m(Supplier<? extends Map<K, ? extends V>> map) { + } + void foo() { + Map<String, Integer> map = null; + m(() -> map); + } + } + """, + spec -> spec.afterRecipe(cu -> new JavaIsoVisitor<>() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Object o) { + JavaType paramType = method.getMethodType().getParameterTypes().get(0); + assertThat(paramType).isInstanceOf(JavaType.Parameterized.class); + JavaType argType = method.getArguments().get(0).getType(); + assertThat(argType).isInstanceOf(JavaType.Parameterized.class); + assertThat(TypeUtils.isAssignableTo(paramType, argType)).isTrue(); + return method; + } + }.visit(cu, new InMemoryExecutionContext())) + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index e61b423b2f0..7fb2798abe0 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -202,9 +202,12 @@ public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType f JavaType.FullyQualified toFq = (JavaType.FullyQualified) to; return isAssignableTo(toFq.getFullyQualifiedName(), from); } else if (to instanceof JavaType.GenericTypeVariable) { - JavaType.GenericTypeVariable genericTo = (JavaType.GenericTypeVariable) to; - if (genericTo.getBounds().isEmpty()) { - return genericTo.getName().equals("?"); + JavaType.GenericTypeVariable toGeneric = (JavaType.GenericTypeVariable) to; + List<JavaType> toBounds = toGeneric.getBounds(); + if (toBounds.isEmpty()) { + return toGeneric.getName().equals("?"); + } else if (toBounds.size() == 1) { + return isAssignableTo(toBounds.get(0), from); } return false; } else if (to instanceof JavaType.Variable) { From d5ed2c5a1cc64b412afcee87c32ba6ab1b344758 Mon Sep 17 00:00:00 2001 From: Alex Boyko <aboyko@vmware.com> Date: Thu, 2 Nov 2023 10:23:09 -0400 Subject: [PATCH 367/447] Fix after visitor cursor propagation (#3642) * Unit test for Autoformat top visitor * Fix bug in `TreeVisitor#visit()` When the `TreeVisitor` calls any scheduled after visitors, it must make sure that the cursor it sets on the after visitors doesn't end up having a parent cursor with the same value. * Revert change to `Cursor#fork()` again --------- Co-authored-by: Knut Wannheden <knut@moderne.io> --- .../java/org/openrewrite/TreeVisitor.java | 2 +- .../java/org/openrewrite/TreeVisitorTest.java | 2 +- .../org/openrewrite/java/JavaVisitorTest.java | 51 +++++++++++++++++++ .../java/org/openrewrite/java/ChangeType.java | 4 +- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java b/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java index a3b441ab0ae..73d6d853a48 100644 --- a/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java +++ b/rewrite-core/src/main/java/org/openrewrite/TreeVisitor.java @@ -309,7 +309,7 @@ public T visit(@Nullable Tree tree, P p) { if (t != null && afterVisit != null) { for (TreeVisitor<?, P> v : afterVisit) { if (v != null) { - v.setCursor(new Cursor(cursor, tree)); + v.setCursor(getCursor()); //noinspection unchecked t = (T) v.visit(t, p); } diff --git a/rewrite-core/src/test/java/org/openrewrite/TreeVisitorTest.java b/rewrite-core/src/test/java/org/openrewrite/TreeVisitorTest.java index 4e3cb0a8053..8a7c1f36168 100644 --- a/rewrite-core/src/test/java/org/openrewrite/TreeVisitorTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/TreeVisitorTest.java @@ -36,7 +36,7 @@ void scheduleAfterOnVisitWithCursor() { @Override public @Nullable Tree visit(@Nullable Tree tree, Integer i) { assertThat(tree).isSameAs(quark); - assertThat((Tree)getCursor().getValue()).isSameAs(quark); + assertThat(getCursor()).isSameAs(getCursor().getRoot()); visited.incrementAndGet(); return tree; } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java index d0f9d2bbb34..6e82ecd49a8 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaVisitorTest.java @@ -16,11 +16,13 @@ package org.openrewrite.java; import org.junit.jupiter.api.Test; +import org.openrewrite.Cursor; import org.openrewrite.DocumentExample; import org.openrewrite.ExecutionContext; import org.openrewrite.java.tree.J; import org.openrewrite.test.RewriteTest; +import static org.junit.jupiter.api.Assertions.fail; import static org.openrewrite.java.Assertions.java; import static org.openrewrite.test.RewriteTest.toRecipe; @@ -77,4 +79,53 @@ void removeMethod() {} ) ); } + + @Test + void topVisitor() { + final JavaIsoVisitor<ExecutionContext> afterVisitor = new JavaIsoVisitor<ExecutionContext>() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext p) { + for (Cursor parent = getCursor().getParent(); parent != null; parent = parent.getParent()) { + if (method == parent.getValue()) { + fail("Duplicate cursor: %s".formatted(getCursor())); + } + } + return super.visitMethodDeclaration(method, p); + } + }; + rewriteRun( + spec -> spec.recipe( + toRecipe(() -> new JavaIsoVisitor<>() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext p) { + var md = super.visitMethodDeclaration(method, p); + if ("myMethod".equals(md.getSimpleName())) { + //noinspection ConstantConditions + return (J.MethodDeclaration) new JavaIsoVisitor<ExecutionContext>() { + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext p) { + doAfterVisit(afterVisitor); + return super.visitMethodDeclaration(method, p); + } + }.visit(md, p, getCursor().getParent()); + } + return md; + } + }) + ), + java(""" + class A { + public void method1() { + } + + @Deprecated + public String myMethod() { + return "hello"; + } + + public void method2() { + } + } + """) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java index 55248729715..d9f068c5ebb 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java @@ -181,9 +181,9 @@ private void addImport(JavaType.FullyQualified owningClass) { if (maybeType instanceof JavaType.FullyQualified) { JavaType.FullyQualified type = (JavaType.FullyQualified) maybeType; if (originalType.getFullyQualifiedName().equals(type.getFullyQualifiedName())) { - sf = (JavaSourceFile) new RemoveImport<ExecutionContext>(originalType.getFullyQualifiedName()).visit(sf, ctx, getCursor()); + sf = (JavaSourceFile) new RemoveImport<ExecutionContext>(originalType.getFullyQualifiedName()).visit(sf, ctx, getCursor().getParentOrThrow()); } else if (originalType.getOwningClass() != null && originalType.getOwningClass().getFullyQualifiedName().equals(type.getFullyQualifiedName())) { - sf = (JavaSourceFile) new RemoveImport<ExecutionContext>(originalType.getOwningClass().getFullyQualifiedName()).visit(sf, ctx, getCursor()); + sf = (JavaSourceFile) new RemoveImport<ExecutionContext>(originalType.getOwningClass().getFullyQualifiedName()).visit(sf, ctx, getCursor().getParentOrThrow()); } } } From b5f1cda30a677778871b9c4c99e18f5c9c552bb2 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 2 Nov 2023 15:52:42 +0100 Subject: [PATCH 368/447] More corrections to `TypeUtils#isAssignableTo()` --- .../org/openrewrite/java/tree/TypeUtils.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 7fb2798abe0..2681247779e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -189,7 +189,12 @@ public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType f } else if (to instanceof JavaType.Parameterized) { JavaType.Parameterized toParameterized = (JavaType.Parameterized) to; if (!(from instanceof JavaType.Parameterized)) { - return toParameterized.getTypeParameters().stream().allMatch(p -> isAssignableTo(p, TYPE_OBJECT)); + return from instanceof JavaType.FullyQualified && + isAssignableTo(toParameterized.getType(), from) && + toParameterized.getTypeParameters().stream() + .allMatch(p -> (p instanceof JavaType.GenericTypeVariable && + ((JavaType.GenericTypeVariable) p).getName().equals("?")) || + isAssignableTo(p, TYPE_OBJECT)); } JavaType.Parameterized fromParameterized = (JavaType.Parameterized) from; List<JavaType> toParameters = toParameterized.getTypeParameters(); @@ -206,8 +211,13 @@ public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType f List<JavaType> toBounds = toGeneric.getBounds(); if (toBounds.isEmpty()) { return toGeneric.getName().equals("?"); - } else if (toBounds.size() == 1) { - return isAssignableTo(toBounds.get(0), from); + } else if (toGeneric.getVariance() == JavaType.GenericTypeVariable.Variance.COVARIANT) { + for (JavaType toBound : toBounds) { + if (!isAssignableTo(toBound, from)) { + return false; + } + } + return true; } return false; } else if (to instanceof JavaType.Variable) { @@ -239,7 +249,7 @@ public static boolean isAssignableTo(String to, @Nullable JavaType from) { } JavaType.FullyQualified classFrom = (JavaType.FullyQualified) from; if (fullyQualifiedNamesAreEqual(to, classFrom.getFullyQualifiedName()) || - isAssignableTo(to, classFrom.getSupertype())) { + isAssignableTo(to, classFrom.getSupertype())) { return true; } for (JavaType.FullyQualified i : classFrom.getInterfaces()) { From 2642269a6dfd65a876862dc00530349d6e566e65 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 2 Nov 2023 16:57:57 +0100 Subject: [PATCH 369/447] Make `TypeUtils#isAssignable()` a bit stricter --- .../src/main/java/org/openrewrite/java/tree/TypeUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 2681247779e..0209d3ebbf3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -209,8 +209,8 @@ public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType f } else if (to instanceof JavaType.GenericTypeVariable) { JavaType.GenericTypeVariable toGeneric = (JavaType.GenericTypeVariable) to; List<JavaType> toBounds = toGeneric.getBounds(); - if (toBounds.isEmpty()) { - return toGeneric.getName().equals("?"); + if (!toGeneric.getName().equals("?")) { + return false; } else if (toGeneric.getVariance() == JavaType.GenericTypeVariable.Variance.COVARIANT) { for (JavaType toBound : toBounds) { if (!isAssignableTo(toBound, from)) { From a361eaf8285b3c3dcd83d5b7fd8fe69e4a227e59 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 2 Nov 2023 13:22:20 -0700 Subject: [PATCH 370/447] Proof of concept for preconditions on declarative recipes (#3643) * Proof of concept for preconditions on declarative recipes * Delete unused file * Also apply preconditions to scanning phase, as that lets them narrow queries * License headers * Polish * Reject ScanningRecipe from being preconditions * Also exclude AdHocScanningRecipe from serialization requirement --- .../openrewrite/config/DeclarativeRecipe.java | 154 +++++++++++++++++- .../config/DeclarativeRecipeTest.java | 65 ++++++++ .../org/openrewrite/test/AdHocRecipe.java | 27 +-- .../openrewrite/test/AdHocScanningRecipe.java | 108 ++++++++++++ .../org/openrewrite/test/RewriteTest.java | 12 +- 5 files changed, 334 insertions(+), 32 deletions(-) create mode 100644 rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java create mode 100644 rewrite-test/src/main/java/org/openrewrite/test/AdHocScanningRecipe.java diff --git a/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java b/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java index 24c38790a2c..22719501c6d 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java @@ -20,11 +20,9 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Value; +import lombok.experimental.NonFinal; import org.intellij.lang.annotations.Language; -import org.openrewrite.Contributor; -import org.openrewrite.Maintainer; -import org.openrewrite.Recipe; -import org.openrewrite.Validated; +import org.openrewrite.*; import org.openrewrite.internal.lang.Nullable; import java.net.URI; @@ -67,6 +65,11 @@ public boolean causesAnotherCycle() { private final List<Recipe> uninitializedRecipes = new ArrayList<>(); private final List<Recipe> recipeList = new ArrayList<>(); + private final List<Recipe> preconditions = new ArrayList<>(); + + public void addPrecondition(Recipe recipe) { + preconditions.add(recipe); + } @JsonIgnore private Validated<Object> validation = Validated.test("initialization", @@ -111,11 +114,152 @@ public void initialize(Collection<Recipe> availableRecipes, Map<String, List<Con uninitializedRecipes.clear(); } + @Value + @EqualsAndHashCode(callSuper = true) + @RequiredArgsConstructor + static class PreconditionBellwether extends Recipe { + + @Override + public String getDisplayName() { + return "Precondition bellwether"; + } + + @Override + public String getDescription() { + return "Evaluates a precondition and makes that result available to the preconditions of other recipes."; + } + + TreeVisitor<?, ExecutionContext> precondition; + + @NonFinal + transient boolean preconditionApplicable; + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new TreeVisitor<Tree, ExecutionContext>() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + Tree t = precondition.visit(tree, ctx); + preconditionApplicable = t != tree; + return tree; + } + }; + } + } + + @EqualsAndHashCode(callSuper = true) + @Value + static class BellwetherDecoratedRecipe extends Recipe { + + DeclarativeRecipe.PreconditionBellwether bellwether; + Recipe delegate; + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public String getDisplayName() { + return delegate.getDisplayName(); + } + + @Override + public String getDescription() { + return delegate.getDescription(); + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return Preconditions.check(bellwether.isPreconditionApplicable(), delegate.getVisitor()); + } + } + + @Value + @EqualsAndHashCode(callSuper = true) + static class BellwetherDecoratedScanningRecipe<T> extends ScanningRecipe<T> { + + DeclarativeRecipe.PreconditionBellwether bellwether; + ScanningRecipe<T> delegate; + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public String getDisplayName() { + return delegate.getDisplayName(); + } + + @Override + public String getDescription() { + return delegate.getDescription(); + } + + @Override + public T getInitialValue(ExecutionContext ctx) { + return delegate.getInitialValue(ctx); + } + + @Override + public TreeVisitor<?, ExecutionContext> getScanner(T acc) { + return Preconditions.check(bellwether.isPreconditionApplicable(), delegate.getScanner(acc)); + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor(T acc) { + return Preconditions.check(bellwether.isPreconditionApplicable(), delegate.getVisitor(acc)); + } + } + + @Override public List<Recipe> getRecipeList() { - return recipeList; + if(preconditions.isEmpty()) { + return recipeList; + } + + TreeVisitor<?, ExecutionContext> andPreconditions = null; + for (Recipe precondition : preconditions) { + if(isScanningRecipe(precondition)) { + throw new IllegalArgumentException( + getName() + " declares the ScanningRecipe " + precondition.getName() + " as a precondition." + + "ScanningRecipe cannot be used as Preconditions."); + } + if(andPreconditions == null) { + andPreconditions = precondition.getVisitor(); + } else { + andPreconditions = Preconditions.and(andPreconditions, precondition.getVisitor()); + } + } + PreconditionBellwether bellwether = new PreconditionBellwether(andPreconditions); + List<Recipe> recipeListWithBellwether = new ArrayList<>(recipeList.size() + 1); + recipeListWithBellwether.add(bellwether); + for (Recipe recipe : recipeList) { + if(recipe instanceof ScanningRecipe) { + recipeListWithBellwether.add(new BellwetherDecoratedScanningRecipe<>(bellwether, (ScanningRecipe<?>) recipe)); + } else { + recipeListWithBellwether.add(new BellwetherDecoratedRecipe(bellwether, recipe)); + } + } + + return recipeListWithBellwether; + } + + private static boolean isScanningRecipe(Recipe recipe) { + if(recipe instanceof ScanningRecipe) { + return true; + } + for (Recipe r : recipe.getRecipeList()) { + if(isScanningRecipe(r)) { + return true; + } + } + return false; } + public void addUninitialized(Recipe recipe) { uninitializedRecipes.add(recipe); } diff --git a/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java b/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java new file mode 100644 index 00000000000..945db02c607 --- /dev/null +++ b/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.config; + +import org.junit.jupiter.api.Test; +import org.openrewrite.ExecutionContext; +import org.openrewrite.marker.SearchResult; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.text.ChangeText; +import org.openrewrite.text.PlainText; +import org.openrewrite.text.PlainTextVisitor; + +import java.util.List; +import java.util.Map; + +import static org.openrewrite.test.SourceSpecs.text; +import static org.openrewrite.test.RewriteTest.toRecipe; + +public class DeclarativeRecipeTest implements RewriteTest { + + @Test + void precondition() { + rewriteRun( + spec -> { + spec.validateRecipeSerialization(false); + DeclarativeRecipe dr = new DeclarativeRecipe("test", "test", "test", null, + null, null, true, null); + dr.addPrecondition( + toRecipe(() -> new PlainTextVisitor<>() { + @Override + public PlainText visitText(PlainText text, ExecutionContext executionContext) { + if("1".equals(text.getText())) { + return SearchResult.found(text); + } + return text; + } + }) + ); + dr.addUninitialized( + new ChangeText("2") + ); + dr.addUninitialized( + new ChangeText("3") + ); + dr.initialize(List.of(), Map.of()); + spec.recipe(dr); + }, + text("1","3"), + text("2") + ); + } +} diff --git a/rewrite-test/src/main/java/org/openrewrite/test/AdHocRecipe.java b/rewrite-test/src/main/java/org/openrewrite/test/AdHocRecipe.java index 3ebb08a30b2..0fcdc37e727 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/AdHocRecipe.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/AdHocRecipe.java @@ -30,7 +30,7 @@ @Value @EqualsAndHashCode(callSuper = false) -public class AdHocRecipe extends ScanningRecipe<Void> { +public class AdHocRecipe extends Recipe { @With @Nullable @Language("markdown") @@ -47,10 +47,6 @@ public class AdHocRecipe extends ScanningRecipe<Void> { @With Supplier<TreeVisitor<?, ExecutionContext>> getVisitor; - @Nullable - @With - Supplier<Collection<SourceFile>> generator; - @With @Nullable List<Maintainer> maintainers; @@ -59,6 +55,10 @@ public class AdHocRecipe extends ScanningRecipe<Void> { @Nullable Integer maxCycles; + public AdHocScanningRecipe withGenerator(Supplier<Collection<SourceFile>> generator) { + return new AdHocScanningRecipe(displayName, name, causesAnotherCycle, getVisitor, generator, maintainers, maxCycles); + } + public String getDisplayName() { return StringUtils.isBlank(displayName) ? "Ad hoc recipe" : displayName; } @@ -87,22 +87,7 @@ public List<Maintainer> getMaintainers() { } @Override - public Void getInitialValue(ExecutionContext ctx) { - return null; - } - - @Override - public TreeVisitor<?, ExecutionContext> getScanner(Void acc) { - return TreeVisitor.noop(); - } - - @Override - public Collection<? extends SourceFile> generate(Void acc, ExecutionContext ctx) { - return generator == null ? Collections.emptyList() : generator.get(); - } - - @Override - public TreeVisitor<?, ExecutionContext> getVisitor(Void acc) { + public TreeVisitor<?, ExecutionContext> getVisitor() { return getVisitor.get(); } } diff --git a/rewrite-test/src/main/java/org/openrewrite/test/AdHocScanningRecipe.java b/rewrite-test/src/main/java/org/openrewrite/test/AdHocScanningRecipe.java new file mode 100644 index 00000000000..63611f1a788 --- /dev/null +++ b/rewrite-test/src/main/java/org/openrewrite/test/AdHocScanningRecipe.java @@ -0,0 +1,108 @@ +/* + * Copyright 2022 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.test; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.With; +import org.intellij.lang.annotations.Language; +import org.openrewrite.*; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +@Value +@EqualsAndHashCode(callSuper = false) +public class AdHocScanningRecipe extends ScanningRecipe<Void> { + @With + @Nullable + @Language("markdown") + String displayName; + + @With + @Nullable + String name; + + @With + @Nullable + Boolean causesAnotherCycle; + + @With + Supplier<TreeVisitor<?, ExecutionContext>> getVisitor; + + @Nullable + @With + Supplier<Collection<SourceFile>> generator; + + @With + @Nullable + List<Maintainer> maintainers; + + @With + @Nullable + Integer maxCycles; + + public String getDisplayName() { + return StringUtils.isBlank(displayName) ? "Ad hoc recipe" : displayName; + } + + @Override + public String getDescription() { + return "An ad hoc recipe used in RewriteTest."; + } + + public String getName() { + return StringUtils.isBlank(name) ? super.getName() : name; + } + + @Override + public boolean causesAnotherCycle() { + return causesAnotherCycle == null ? super.causesAnotherCycle() : causesAnotherCycle; + } + + @Override + public int maxCycles() { + return maxCycles == null ? super.maxCycles() : maxCycles; + } + + public List<Maintainer> getMaintainers() { + return maintainers == null ? Collections.emptyList() : maintainers; + } + + @Override + public Void getInitialValue(ExecutionContext ctx) { + return null; + } + + @Override + public TreeVisitor<?, ExecutionContext> getScanner(Void acc) { + return TreeVisitor.noop(); + } + + @Override + public Collection<? extends SourceFile> generate(Void acc, ExecutionContext ctx) { + return generator == null ? Collections.emptyList() : generator.get(); + } + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor(Void acc) { + return getVisitor.get(); + } +} diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java index 1bea7fb1ebf..7c88bb7530e 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RewriteTest.java @@ -50,12 +50,12 @@ @SuppressWarnings("unused") public interface RewriteTest extends SourceSpecs { static AdHocRecipe toRecipe(Supplier<TreeVisitor<?, ExecutionContext>> visitor) { - return new AdHocRecipe(null, null, null, visitor, null, null, null); + return new AdHocRecipe(null, null, null, visitor, null, null); } static AdHocRecipe toRecipe() { return new AdHocRecipe(null, null, null, - TreeVisitor::noop, null, null, null); + TreeVisitor::noop, null, null); } static AdHocRecipe toRecipe(Function<Recipe, TreeVisitor<?, ExecutionContext>> visitor) { @@ -158,10 +158,10 @@ default void rewriteRun(Consumer<RecipeSpec> spec, SourceSpec<?>... sourceSpecs) .as("A recipe must be specified") .isNotNull(); - if (!(recipe instanceof AdHocRecipe) && !(recipe instanceof CompositeRecipe) && - !(recipe.equals(Recipe.noop())) && - testClassSpec.serializationValidation && - testMethodSpec.serializationValidation) { + if (!(recipe instanceof AdHocRecipe) && !(recipe instanceof AdHocScanningRecipe) && + !(recipe instanceof CompositeRecipe) && !(recipe.equals(Recipe.noop())) && + testClassSpec.serializationValidation && + testMethodSpec.serializationValidation) { RecipeSerializer recipeSerializer = new RecipeSerializer(); assertThat(recipeSerializer.read(recipeSerializer.write(recipe))) .as("Recipe must be serializable/deserializable") From 139499db785d5916d3ad61df6ee06aee07448d4a Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 2 Nov 2023 22:01:44 +0100 Subject: [PATCH 371/447] `Null` type is assignable to `String` type --- .../src/main/java/org/openrewrite/java/tree/TypeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 0209d3ebbf3..2e4b6cdc8d3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -308,7 +308,7 @@ public static boolean isAssignableTo(String to, @Nullable JavaType from) { return false; } } else if ("java.lang.String".equals(to)) { - return fromPrimitive == JavaType.Primitive.String; + return fromPrimitive == JavaType.Primitive.String || fromPrimitive == JavaType.Primitive.Null; } } else if (from instanceof JavaType.Variable) { return isAssignableTo(to, ((JavaType.Variable) from).getType()); From 8964125a3605f71d050ace1ae992d896cf720072 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 2 Nov 2023 22:28:46 +0100 Subject: [PATCH 372/447] Improve `isAssignableTo()` for primitive types --- .../org/openrewrite/java/tree/TypeUtils.java | 89 ++++++++++--------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 2e4b6cdc8d3..c4561301549 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -225,10 +225,53 @@ public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType f } else if (to instanceof JavaType.Method) { return isAssignableTo(((JavaType.Method) to).getReturnType(), from); } else if (to instanceof JavaType.Primitive) { + JavaType.Primitive toPrimitive = (JavaType.Primitive) to; if (from instanceof JavaType.FullyQualified) { // Account for auto-unboxing - JavaType.FullyQualified boxed = JavaType.ShallowClass.build(((JavaType.Primitive) to).getClassName()); + JavaType.FullyQualified boxed = JavaType.ShallowClass.build(toPrimitive.getClassName()); return isAssignableTo(boxed, from); + } else if (from instanceof JavaType.Primitive) { + JavaType.Primitive fromPrimitive = (JavaType.Primitive) from; + if (fromPrimitive == JavaType.Primitive.Boolean || fromPrimitive == JavaType.Primitive.Void) { + return false; + } else { + switch (toPrimitive) { + case Char: + return fromPrimitive == JavaType.Primitive.Byte; + case Short: + switch (fromPrimitive) { + case Byte: + case Char: + return true; + } + return false; + case Int: + switch (fromPrimitive) { + case Byte: + case Char: + case Short: + return true; + } + return false; + case Long: + switch (fromPrimitive) { + case Byte: + case Char: + case Short: + case Int: + return true; + } + return false; + case Float: + return fromPrimitive != JavaType.Primitive.Double; + case Double: + return true; + case String: + return fromPrimitive == JavaType.Primitive.Null; + default: + return false; + } + } } } else if (to instanceof JavaType.Array && from instanceof JavaType.Array) { return isAssignableTo(((JavaType.Array) to).getElemType(), ((JavaType.Array) from).getElemType()); @@ -266,49 +309,11 @@ public static boolean isAssignableTo(String to, @Nullable JavaType from) { } } } else if (from instanceof JavaType.Primitive) { - JavaType.Primitive fromPrimitive = (JavaType.Primitive) from; JavaType.Primitive toPrimitive = JavaType.Primitive.fromKeyword(to); - if (fromPrimitive == toPrimitive) { - return true; - } else if (fromPrimitive == JavaType.Primitive.Boolean || fromPrimitive == JavaType.Primitive.Void) { - return false; - } else if (toPrimitive != null) { - switch (toPrimitive) { - case Char: - return fromPrimitive == JavaType.Primitive.Byte; - case Short: - switch (fromPrimitive) { - case Byte: - case Char: - return true; - } - return false; - case Int: - switch (fromPrimitive) { - case Byte: - case Char: - case Short: - return true; - } - return false; - case Long: - switch (fromPrimitive) { - case Byte: - case Char: - case Short: - case Int: - return true; - } - return false; - case Float: - return fromPrimitive != JavaType.Primitive.Double; - case Double: - return true; - default: - return false; - } + if (toPrimitive != null) { + return isAssignableTo(toPrimitive, from); } else if ("java.lang.String".equals(to)) { - return fromPrimitive == JavaType.Primitive.String || fromPrimitive == JavaType.Primitive.Null; + return isAssignableTo(JavaType.Primitive.String, from); } } else if (from instanceof JavaType.Variable) { return isAssignableTo(to, ((JavaType.Variable) from).getType()); From 151dca1c1e8e24872dcde37dd8cf1c9de57833b2 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 2 Nov 2023 18:28:47 -0700 Subject: [PATCH 373/447] Fix title case --- .../src/main/java/org/openrewrite/maven/ChangeParentPom.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java index 1469ce10447..0a6c85e445e 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeParentPom.java @@ -50,7 +50,7 @@ public class ChangeParentPom extends Recipe { @Override public String getDisplayName() { - return "Change Maven Parent Pom"; + return "Change Maven parent pom"; } @Override From 84198092987f2230304cc4a0727f80e355620bca Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 2 Nov 2023 19:34:21 -0700 Subject: [PATCH 374/447] Load preconditions from yaml --- .../openrewrite/config/DeclarativeRecipe.java | 58 ++++++++++++++----- .../config/YamlResourceLoader.java | 48 +++++++++------ .../config/DeclarativeRecipeTest.java | 21 +++++++ 3 files changed, 94 insertions(+), 33 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java b/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java index 22719501c6d..e213360c10d 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java @@ -65,6 +65,8 @@ public boolean causesAnotherCycle() { private final List<Recipe> uninitializedRecipes = new ArrayList<>(); private final List<Recipe> recipeList = new ArrayList<>(); + + private final List<Recipe> uninitializedPreconditions = new ArrayList<>(); private final List<Recipe> preconditions = new ArrayList<>(); public void addPrecondition(Recipe recipe) { @@ -83,8 +85,13 @@ public Duration getEstimatedEffortPerOccurrence() { } public void initialize(Collection<Recipe> availableRecipes, Map<String, List<Contributor>> recipeToContributors) { - for (int i = 0; i < uninitializedRecipes.size(); i++) { - Recipe recipe = uninitializedRecipes.get(i); + initialize(uninitializedRecipes, recipeList, availableRecipes, recipeToContributors); + initialize(uninitializedPreconditions, preconditions, availableRecipes, recipeToContributors); + } + + private void initialize(List<Recipe> uninitialized, List<Recipe> initialized, Collection<Recipe> availableRecipes, Map<String, List<Contributor>> recipeToContributors) { + for (int i = 0; i < uninitialized.size(); i++) { + Recipe recipe = uninitialized.get(i); if (recipe instanceof LazyLoadedRecipe) { String recipeFqn = ((LazyLoadedRecipe) recipe).getRecipeFqn(); Optional<Recipe> next = availableRecipes.stream() @@ -94,7 +101,7 @@ public void initialize(Collection<Recipe> availableRecipes, Map<String, List<Con if (subRecipe instanceof DeclarativeRecipe) { ((DeclarativeRecipe) subRecipe).initialize(availableRecipes, recipeToContributors); } - recipeList.add(subRecipe); + initialized.add(subRecipe); } else { validation = validation.and( invalid(name + ".recipeList" + @@ -108,10 +115,10 @@ public void initialize(Collection<Recipe> availableRecipes, Map<String, List<Con if (recipe instanceof DeclarativeRecipe) { ((DeclarativeRecipe) recipe).initialize(availableRecipes, recipeToContributors); } - recipeList.add(recipe); + initialized.add(recipe); } } - uninitializedRecipes.clear(); + uninitialized.clear(); } @Value @@ -126,7 +133,8 @@ public String getDisplayName() { @Override public String getDescription() { - return "Evaluates a precondition and makes that result available to the preconditions of other recipes."; + return "Evaluates a precondition and makes that result available to the preconditions of other recipes. " + + "\"bellwether\", noun - One that serves as a leader or as a leading indicator of future trends. "; } TreeVisitor<?, ExecutionContext> precondition; @@ -173,6 +181,11 @@ public String getDescription() { public TreeVisitor<?, ExecutionContext> getVisitor() { return Preconditions.check(bellwether.isPreconditionApplicable(), delegate.getVisitor()); } + + @Override + public List<Recipe> getRecipeList() { + return decorateWithPreconditionBellwether(bellwether, delegate.getRecipeList()); + } } @Value @@ -211,6 +224,11 @@ public TreeVisitor<?, ExecutionContext> getScanner(T acc) { public TreeVisitor<?, ExecutionContext> getVisitor(T acc) { return Preconditions.check(bellwether.isPreconditionApplicable(), delegate.getVisitor(acc)); } + + @Override + public List<Recipe> getRecipeList() { + return decorateWithPreconditionBellwether(bellwether, delegate.getRecipeList()); + } } @@ -236,14 +254,7 @@ public List<Recipe> getRecipeList() { PreconditionBellwether bellwether = new PreconditionBellwether(andPreconditions); List<Recipe> recipeListWithBellwether = new ArrayList<>(recipeList.size() + 1); recipeListWithBellwether.add(bellwether); - for (Recipe recipe : recipeList) { - if(recipe instanceof ScanningRecipe) { - recipeListWithBellwether.add(new BellwetherDecoratedScanningRecipe<>(bellwether, (ScanningRecipe<?>) recipe)); - } else { - recipeListWithBellwether.add(new BellwetherDecoratedRecipe(bellwether, recipe)); - } - } - + recipeListWithBellwether.addAll(decorateWithPreconditionBellwether(bellwether, recipeList)); return recipeListWithBellwether; } @@ -259,6 +270,17 @@ private static boolean isScanningRecipe(Recipe recipe) { return false; } + private static List<Recipe> decorateWithPreconditionBellwether(PreconditionBellwether bellwether, List<Recipe> recipeList) { + List<Recipe> mappedRecipeList = new ArrayList<>(recipeList.size()); + for (Recipe recipe : recipeList) { + if(recipe instanceof ScanningRecipe) { + mappedRecipeList.add(new BellwetherDecoratedScanningRecipe<>(bellwether, (ScanningRecipe<?>) recipe)); + } else { + mappedRecipeList.add(new BellwetherDecoratedRecipe(bellwether, recipe)); + } + } + return mappedRecipeList; + } public void addUninitialized(Recipe recipe) { uninitializedRecipes.add(recipe); @@ -268,6 +290,14 @@ public void addUninitialized(String recipeName) { uninitializedRecipes.add(new DeclarativeRecipe.LazyLoadedRecipe(recipeName)); } + public void addUninitializedPrecondition(Recipe recipe) { + uninitializedPreconditions.add(recipe); + } + + public void addUninitializedPrecondition(String recipeName) { + uninitializedPreconditions.add(new DeclarativeRecipe.LazyLoadedRecipe(recipeName)); + } + public void addValidation(Validated<Object> validated) { validation = validation.and(validated); } diff --git a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java index 4899de59ced..9050c6f5ac8 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import lombok.Getter; import org.intellij.lang.annotations.Language; import org.openrewrite.*; import org.openrewrite.internal.PropertyPlaceholderHelper; @@ -43,6 +44,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.*; +import java.util.function.Consumer; import java.util.stream.Stream; import static java.util.Collections.emptyList; @@ -73,6 +75,7 @@ public class YamlResourceLoader implements ResourceLoader { @Nullable private Map<String, List<RecipeExample>> recipeNameToExamples; + @Getter private enum ResourceType { Recipe("specs.openrewrite.org/v1beta/recipe"), Style("specs.openrewrite.org/v1beta/style"), @@ -86,10 +89,6 @@ private enum ResourceType { this.spec = spec; } - public String getSpec() { - return spec; - } - @Nullable public static ResourceType fromSpec(@Nullable String spec) { return Arrays.stream(values()) @@ -244,9 +243,14 @@ public Collection<Recipe> listRecipes() { throw new RecipeException("Invalid Recipe [" + name + "] recipeList is null"); } for (int i = 0; i < recipeList.size(); i++) { - loadRecipe(name, recipe, i, recipeList.get(i)); + loadRecipe(name, i, recipeList.get(i), recipe::addUninitialized, recipe::addUninitialized, recipe::addValidation); + } + List<Object> preconditions = (List<Object>) r.get("preconditions"); + if(preconditions != null) { + for (int i = 0; i < preconditions.size(); i++) { + loadRecipe(name, i, preconditions.get(i), recipe::addUninitializedPrecondition, recipe::addUninitializedPrecondition, recipe::addValidation); + } } - recipe.setContributors(contributors.get(recipe.getName())); recipes.add(recipe); } @@ -256,24 +260,27 @@ public Collection<Recipe> listRecipes() { @SuppressWarnings("unchecked") private void loadRecipe(@Language("markdown") String name, - DeclarativeRecipe recipe, int i, - Object recipeData) { + Object recipeData, + Consumer<String> addLazyLoadRecipe, + Consumer<Recipe> addRecipe, + Consumer<Validated<Object>> addValidation) { if (recipeData instanceof String) { String recipeName = (String) recipeData; try { + // first try an explicitly-declared zero-arg constructor - recipe.addUninitialized((Recipe) Class.forName(recipeName, true, + addRecipe.accept((Recipe) Class.forName(recipeName, true, classLoader == null ? this.getClass().getClassLoader() : classLoader) .getDeclaredConstructor() .newInstance()); } catch (ReflectiveOperationException reflectiveOperationException) { try { // then try jackson - recipe.addUninitialized(instantiateRecipe(recipeName, new HashMap<>())); + addRecipe.accept(instantiateRecipe(recipeName, new HashMap<>())); } catch (IllegalArgumentException illegalArgumentException) { // else, it's probably declarative - recipe.addUninitialized(recipeName); + addLazyLoadRecipe.accept(recipeName); } } } else if (recipeData instanceof Map) { @@ -281,31 +288,30 @@ private void loadRecipe(@Language("markdown") String name, try { if (nameAndConfig.getValue() instanceof Map) { try { - recipe.addUninitialized(instantiateRecipe(nameAndConfig.getKey(), + addRecipe.accept(instantiateRecipe(nameAndConfig.getKey(), (Map<String, Object>) nameAndConfig.getValue())); } catch (IllegalArgumentException e) { if (e.getCause() instanceof InvalidTypeIdException) { - recipe.addValidation(Validated.invalid(nameAndConfig.getKey(), + addValidation.accept(Validated.invalid(nameAndConfig.getKey(), nameAndConfig.getValue(), "Recipe class " + nameAndConfig.getKey() + " cannot be found")); } else { - recipe.addValidation(Validated.invalid(nameAndConfig.getKey(), nameAndConfig.getValue(), + addValidation.accept(Validated.invalid(nameAndConfig.getKey(), nameAndConfig.getValue(), "Unable to load Recipe: " + e)); } } } else { - recipe.addValidation(Validated.invalid(nameAndConfig.getKey(), + addValidation.accept(Validated.invalid(nameAndConfig.getKey(), nameAndConfig.getValue(), "Declarative recipeList entries are expected to be strings or mappings")); } } catch (Exception e) { - e.printStackTrace(); - recipe.addValidation(Validated.invalid(nameAndConfig.getKey(), nameAndConfig.getValue(), + addValidation.accept(Validated.invalid(nameAndConfig.getKey(), nameAndConfig.getValue(), "Unexpected declarative recipe parsing exception " + e.getClass().getName())); } } else { - recipe.addValidation(invalid( + addValidation.accept(invalid( name + ".recipeList[" + i + "] (in " + source + ")", recipeData, "is an object type that isn't recognized as a recipe.", @@ -390,7 +396,11 @@ public Collection<NamedStyles> listStyles() { Style e = mapper.convertValue(withJsonType, Style.class); styles.add(e); } catch (Exception e) { - e.printStackTrace(); + namedStyles.addValidation(invalid( + name + ".styleConfigs[" + i + "] (in " + source + ")", + next, + " encountered an error being loaded as a style.", + e)); } } else { namedStyles.addValidation(invalid( diff --git a/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java b/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java index 945db02c607..66ff6f788f3 100644 --- a/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/config/DeclarativeRecipeTest.java @@ -62,4 +62,25 @@ public PlainText visitText(PlainText text, ExecutionContext executionContext) { text("2") ); } + + @Test + void yamlPrecondition() { + rewriteRun( + spec -> spec.recipeFromYaml(""" + --- + type: specs.openrewrite.org/v1beta/recipe + name: org.openrewrite.PreconditionTest + preconditions: + - org.openrewrite.text.Find: + find: 1 + recipeList: + - org.openrewrite.text.ChangeText: + toText: 2 + - org.openrewrite.text.ChangeText: + toText: 3 + """, "org.openrewrite.PreconditionTest"), + text("1","3"), + text("2") + ); + } } From 578a4abdc952fb88e8ac4cf78bc7be3579078432 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 2 Nov 2023 19:46:39 -0700 Subject: [PATCH 375/447] Add "IsInRepository" recipe for use as a precondition --- .../java/org/openrewrite/IsInRepository.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 rewrite-core/src/main/java/org/openrewrite/IsInRepository.java diff --git a/rewrite-core/src/main/java/org/openrewrite/IsInRepository.java b/rewrite-core/src/main/java/org/openrewrite/IsInRepository.java new file mode 100644 index 00000000000..e668ee6ef54 --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/IsInRepository.java @@ -0,0 +1,51 @@ +package org.openrewrite; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.GitProvenance; +import org.openrewrite.marker.SearchResult; + +import java.util.Optional; +import java.util.Set; + +@Value +@EqualsAndHashCode(callSuper = true) +public class IsInRepository extends Recipe { + @Override + public String getDisplayName() { + return "Is in repository"; + } + + @Override + public String getDescription() { + return "A search recipe which marks files that are in a repository with one of the supplied names. " + + "Intended for use as a precondition for other recipes being run over many different repositories"; + } + + @Option(displayName = "Allowed repositories", + description = "The names of the repositories that are allowed to be searched. " + + "Determines repository name according to git metadata recorded in the `GitProvenance` marker.", + example = "rewrite") + Set<String> allowedRepositories; + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return new TreeVisitor<Tree, ExecutionContext>() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext executionContext) { + if (tree == null) { + return null; + } + Optional<GitProvenance> maybeGp = tree.getMarkers().findFirst(GitProvenance.class); + if (maybeGp.isPresent()) { + GitProvenance gp = maybeGp.get(); + if (!allowedRepositories.contains(gp.getRepositoryName())) { + return SearchResult.found(tree); + } + } + return tree; + } + }; + } +} From 73ee090d311fdfdfeb13049619d41d00c7303f81 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 2 Nov 2023 21:45:04 -0700 Subject: [PATCH 376/447] License headers --- .../main/java/org/openrewrite/IsInRepository.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/rewrite-core/src/main/java/org/openrewrite/IsInRepository.java b/rewrite-core/src/main/java/org/openrewrite/IsInRepository.java index e668ee6ef54..f205bd94639 100644 --- a/rewrite-core/src/main/java/org/openrewrite/IsInRepository.java +++ b/rewrite-core/src/main/java/org/openrewrite/IsInRepository.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.openrewrite; import lombok.EqualsAndHashCode; From 19b5a0d4dfdab5495a22f2156130e2336cf2742c Mon Sep 17 00:00:00 2001 From: motu55 <david.simmen@bluewin.ch> Date: Fri, 3 Nov 2023 11:42:31 +0100 Subject: [PATCH 377/447] Fixed error when no file matcher was present (#3656) Co-authored-by: U808771 <U808771@mobi.ch> --- .../org/openrewrite/xml/RemoveXmlTag.java | 2 +- .../org/openrewrite/xml/RemoveXmlTagTest.java | 23 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/RemoveXmlTag.java b/rewrite-xml/src/main/java/org/openrewrite/xml/RemoveXmlTag.java index 1ead7ddff26..2eeeba2df81 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/RemoveXmlTag.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/RemoveXmlTag.java @@ -48,7 +48,7 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - return Preconditions.check(fileMatcher != null ? new HasSourcePath<>(fileMatcher) : TreeVisitor.noop(), new XmlIsoVisitor<ExecutionContext>() { + return Preconditions.check(fileMatcher != null ? new HasSourcePath<>(fileMatcher) : new HasSourcePath<>(""), new XmlIsoVisitor<ExecutionContext>() { private final XPathMatcher xPathMatcher = new XPathMatcher(xPath); @Override diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/RemoveXmlTagTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/RemoveXmlTagTest.java index 73772071793..1eddad69405 100644 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/RemoveXmlTagTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/RemoveXmlTagTest.java @@ -20,8 +20,6 @@ import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; -import java.io.File; -import java.nio.file.Path; import static org.openrewrite.xml.Assertions.xml; @@ -83,4 +81,25 @@ void fileNotMatched() { ) ); } + + @Test + void fileMatcherEmpty() { + rewriteRun( + spec -> spec.recipe(new RemoveXmlTag("//bean", null)), + xml( + """ + <beans> + <bean id='myBean.subpackage.subpackage2'/> + <other id='myBean.subpackage.subpackage2'/> + </beans> + """, + """ + <beans> + <other id='myBean.subpackage.subpackage2'/> + </beans> + """, + documentSourceSpec -> documentSourceSpec.path("my/project/notBeans.xml") + ) + ); + } } From 2d1a53625ed18a077d8dca0aa0b84d8f89d1a0fc Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Fri, 3 Nov 2023 16:07:08 +0100 Subject: [PATCH 378/447] update method type (#3660) --- .../java/DeleteMethodArgumentTest.java | 11 ++++------- .../openrewrite/java/DeleteMethodArgument.java | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/DeleteMethodArgumentTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/DeleteMethodArgumentTest.java index 8003e0ee0ce..6949a868520 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/DeleteMethodArgumentTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/DeleteMethodArgumentTest.java @@ -37,8 +37,7 @@ public B(int n) {} @Test void deleteMiddleArgumentDeclarative() { rewriteRun( - spec -> spec.recipe(new DeleteMethodArgument("B foo(int, int, int)", 1)) - .cycles(1).expectedCyclesThatMakeChanges(1), + spec -> spec.recipes(new DeleteMethodArgument("B foo(int, int, int)", 1)), java(b), java( "public class A {{ B.foo(0, 1, 2); }}", @@ -50,8 +49,7 @@ void deleteMiddleArgumentDeclarative() { @Test void deleteMiddleArgument() { rewriteRun( - spec -> spec.recipe(new DeleteMethodArgument("B foo(int, int, int)", 1)) - .cycles(1).expectedCyclesThatMakeChanges(1), + spec -> spec.recipe(new DeleteMethodArgument("B foo(int, int, int)", 1)), java(b), java( "public class A {{ B.foo(0, 1, 2); }}", @@ -65,7 +63,7 @@ void deleteArgumentsConsecutively() { rewriteRun( spec -> spec.recipes( new DeleteMethodArgument("B foo(int, int, int)", 1), - new DeleteMethodArgument("B foo(int, int, int)", 1) + new DeleteMethodArgument("B foo(int, int)", 1) ), java(b), java("public class A {{ B.foo(0, 1, 2); }}", @@ -98,8 +96,7 @@ void insertEmptyWhenLastArgumentIsDeleted() { @Test void deleteConstructorArgument() { rewriteRun( - spec -> spec.recipe(new DeleteMethodArgument("B <constructor>(int)", 0)) - .cycles(1).expectedCyclesThatMakeChanges(1), + spec -> spec.recipe(new DeleteMethodArgument("B <constructor>(int)", 0)), java(b), java( "public class A { B b = new B(0); }", diff --git a/rewrite-java/src/main/java/org/openrewrite/java/DeleteMethodArgument.java b/rewrite-java/src/main/java/org/openrewrite/java/DeleteMethodArgument.java index 1d69fce01f9..fbcd44a2c44 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/DeleteMethodArgument.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/DeleteMethodArgument.java @@ -19,10 +19,7 @@ import lombok.Value; import org.openrewrite.*; import org.openrewrite.java.search.UsesMethod; -import org.openrewrite.java.tree.Expression; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.MethodCall; -import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import java.util.ArrayList; @@ -104,6 +101,18 @@ private MethodCall visitMethodCall(MethodCall methodCall) { } m = m.withArguments(args); + + JavaType.Method methodType = m.getMethodType(); + if (methodType != null) { + List<String> parameterNames = new ArrayList<>(methodType.getParameterNames()); + parameterNames.remove(argumentIndex); + List<JavaType> parameterTypes = new ArrayList<>(methodType.getParameterTypes()); + parameterTypes.remove(argumentIndex); + + m = m.withMethodType(methodType + .withParameterNames(parameterNames) + .withParameterTypes(parameterTypes)); + } } return m; } From d93c2d93dc9e3d2f6c49d4e2e518c01bfdeffe80 Mon Sep 17 00:00:00 2001 From: Daniel Wallman <daniel.wallman@m.co> Date: Fri, 3 Nov 2023 18:24:25 +0100 Subject: [PATCH 379/447] Fix bug in glob pattern matching (#3657) * add failing test case for glob pattern * fix comparison bug --- rewrite-core/src/main/java/org/openrewrite/PathUtils.java | 2 +- rewrite-core/src/test/java/org/openrewrite/PathUtilsTest.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/PathUtils.java b/rewrite-core/src/main/java/org/openrewrite/PathUtils.java index ad7138159de..efcc738ba4e 100755 --- a/rewrite-core/src/main/java/org/openrewrite/PathUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/PathUtils.java @@ -136,7 +136,7 @@ private static boolean matchesGlob(String pattern, String path) { } if (pathIdxStart > pathIdxEnd) { // Path exhausted - for (int i = pattIdxStart; i < pattIdxEnd; i++) { + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { if (!pattTokens[i].equals("**")) { return false; } diff --git a/rewrite-core/src/test/java/org/openrewrite/PathUtilsTest.java b/rewrite-core/src/test/java/org/openrewrite/PathUtilsTest.java index 253762c8b8e..cf599619497 100644 --- a/rewrite-core/src/test/java/org/openrewrite/PathUtilsTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/PathUtilsTest.java @@ -94,6 +94,8 @@ void globMatching() { assertThat(matchesGlob(path("a/b/test.txt"), "a/**/*.txt")).isTrue(); assertThat(matchesGlob(path("a/b/test.txt"), "a/**/test.*")).isTrue(); assertThat(matchesGlob(path("a/b/test.txt"), "a/**/*.*")).isTrue(); + assertThat(matchesGlob(path("a-test/a-test/test.txt"), "**/*-test/*-test/test.txt")).isTrue(); + assertThat(matchesGlob(path("a-test/test.txt"), "**/*-test/*-test/test.txt")).isFalse(); } private static Path path(String path) { From 2173a7125fd4360b71a998c70b6eb9b6431eedba Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Fri, 3 Nov 2023 14:57:30 -0700 Subject: [PATCH 380/447] Fix warnings --- rewrite-core/src/main/java/org/openrewrite/PathUtils.java | 2 +- .../main/java/org/openrewrite/gradle/UpdateGradleWrapper.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/PathUtils.java b/rewrite-core/src/main/java/org/openrewrite/PathUtils.java index efcc738ba4e..921642e9994 100755 --- a/rewrite-core/src/main/java/org/openrewrite/PathUtils.java +++ b/rewrite-core/src/main/java/org/openrewrite/PathUtils.java @@ -34,7 +34,6 @@ private PathUtils() {} * Compare two paths, returning true if they indicate the same path, regardless of separators. * Does not account for comparison of a relative path to an absolute path, but within the context of OpenRewrite * all paths should be relative anyway. - * * "foo/a.txt" is considered to be equal to "foo\a.txt" */ public static boolean equalIgnoringSeparators(Path a, Path b) { @@ -212,6 +211,7 @@ private static boolean isFileSeparator(char ch) { return isFileSeparator(false, ch); } + @SuppressWarnings("SameParameterValue") private static boolean isFileSeparator(boolean strict, char ch) { return strict ? ch == File.separatorChar diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java index af9d1279189..18c1477854d 100755 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpdateGradleWrapper.java @@ -26,7 +26,6 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.ipc.http.HttpSender; import org.openrewrite.marker.BuildTool; import org.openrewrite.marker.Markers; import org.openrewrite.properties.PropertiesParser; @@ -118,7 +117,7 @@ private GradleWrapper getGradleWrapper(ExecutionContext ctx) { return gradleWrapper; } - static class GradleWrapperState { + public static class GradleWrapperState { boolean needsWrapperUpdate = false; BuildTool updatedMarker; boolean addGradleWrapperProperties = true; From 887e9c61f8522d15f46e0899a042a3b22c6e3b03 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Mon, 6 Nov 2023 21:21:24 +0100 Subject: [PATCH 381/447] Improve `isAssignableTo()` for primitive wrapper types --- .../org/openrewrite/java/tree/JavaType.java | 27 +++++++++++++++++++ .../org/openrewrite/java/tree/TypeUtils.java | 6 +++++ 2 files changed, 33 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java index aa29dfd44c0..2759aefba45 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java @@ -921,6 +921,33 @@ public static Primitive fromKeyword(String keyword) { return null; } + @Nullable + public static Primitive fromClassName(String className) { + switch (className) { + case "java.lang.Boolean": + return Boolean; + case "java.lang.Byte": + return Byte; + case "java.lang.Character": + return Char; + case "java.lang.Double": + return Double; + case "java.lang.Float": + return Float; + case "java.lang.Integer": + return Int; + case "java.lang.Long": + return Long; + case "java.lang.Short": + return Short; + case "java.lang.Void": + return Void; + case "java.lang.String": + return String; + } + return null; + } + public String getKeyword() { switch (this) { case Boolean: diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index c4561301549..89034bd0985 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -205,6 +205,12 @@ public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType f IntStream.range(0, parameterCount).allMatch(i -> isAssignableTo(toParameters.get(i), fromParameters.get(i))); } else if (to instanceof JavaType.FullyQualified) { JavaType.FullyQualified toFq = (JavaType.FullyQualified) to; + if (from instanceof JavaType.Primitive) { + JavaType.Primitive toPrimitive = JavaType.Primitive.fromClassName(toFq.getFullyQualifiedName()); + if (toPrimitive != null) { + return isAssignableTo(toPrimitive, from); + } + } return isAssignableTo(toFq.getFullyQualifiedName(), from); } else if (to instanceof JavaType.GenericTypeVariable) { JavaType.GenericTypeVariable toGeneric = (JavaType.GenericTypeVariable) to; From ecb1a44b3bdc9fb5609206666aa1446ea4f2fa2d Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Mon, 6 Nov 2023 21:39:33 +0100 Subject: [PATCH 382/447] Contra-variant bounds checking in `isAssignableTo()` --- .../main/java/org/openrewrite/java/tree/TypeUtils.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index 89034bd0985..a0a29a1a338 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -209,6 +209,8 @@ public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType f JavaType.Primitive toPrimitive = JavaType.Primitive.fromClassName(toFq.getFullyQualifiedName()); if (toPrimitive != null) { return isAssignableTo(toPrimitive, from); + } else if (toFq.getFullyQualifiedName().equals("java.lang.Object")) { + return true; } } return isAssignableTo(toFq.getFullyQualifiedName(), from); @@ -224,6 +226,13 @@ public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType f } } return true; + } else if (toGeneric.getVariance() == JavaType.GenericTypeVariable.Variance.CONTRAVARIANT) { + for (JavaType toBound : toBounds) { + if (!isAssignableTo(from, toBound)) { + return false; + } + } + return true; } return false; } else if (to instanceof JavaType.Variable) { From f34b695123eccd83cc16b04bbb04daa6c97e609e Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 6 Nov 2023 17:36:16 -0800 Subject: [PATCH 383/447] Fix test failing when JDK version is a whole number like "21" --- .../java/org/openrewrite/maven/internal/RawPomTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/RawPomTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/RawPomTest.java index f7ef1f14eb3..8a0fa1bc652 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/RawPomTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/RawPomTest.java @@ -32,7 +32,11 @@ class RawPomTest { @Test void profileActivationByJdk() { String javaVersion = System.getProperty("java.version"); - int runtimeVersion = Integer.parseInt(javaVersion.substring(0, javaVersion.indexOf('.'))); + int dotIndex = javaVersion.indexOf('.'); + if (dotIndex > 0) { + javaVersion = javaVersion.substring(0, dotIndex); + } + int runtimeVersion = Integer.parseInt(javaVersion); assertThat(new ProfileActivation(false, Integer.toString(runtimeVersion), null).isActive()).isTrue(); assertThat(new ProfileActivation(false, "[," + (runtimeVersion + 1) + ")", null).isActive()).isTrue(); assertThat(new ProfileActivation(false, "[," + runtimeVersion + "]", null).isActive()).isFalse(); From 4c654e41ab20aa27e359ba0338e352bd1c9020ff Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 6 Nov 2023 18:15:00 -0800 Subject: [PATCH 384/447] Provide a less confusing error message when a recipe which is expected to make changes makes none --- .../test/LargeSourceSetCheckingExpectedCycles.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rewrite-test/src/main/java/org/openrewrite/test/LargeSourceSetCheckingExpectedCycles.java b/rewrite-test/src/main/java/org/openrewrite/test/LargeSourceSetCheckingExpectedCycles.java index 66124cf6446..1d7307cbbc0 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/LargeSourceSetCheckingExpectedCycles.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/LargeSourceSetCheckingExpectedCycles.java @@ -75,10 +75,14 @@ public void afterCycle(boolean lastCycle) { } } lastCycleChanges = thisCycleChanges; - - if (lastCycle && cyclesThatResultedInChanges != expectedCyclesThatMakeChanges) { - fail("Expected recipe to complete in " + expectedCyclesThatMakeChanges + " cycle" + (expectedCyclesThatMakeChanges > 1 ? "s" : "") + ", " + - "but took " + cyclesThatResultedInChanges + " cycle" + (cyclesThatResultedInChanges > 1 ? "s" : "") + "."); + if(lastCycle) { + if(cyclesThatResultedInChanges == 0 && expectedCyclesThatMakeChanges > 0) { + fail("Recipe was expected to make a change but made no changes."); + } else if(cyclesThatResultedInChanges != expectedCyclesThatMakeChanges) { + fail("Expected recipe to complete in " + expectedCyclesThatMakeChanges + " cycle" + (expectedCyclesThatMakeChanges > 1 ? "s" : "") + ", " + + "but took " + cyclesThatResultedInChanges + " cycle" + (cyclesThatResultedInChanges > 1 ? "s" : "") + ". " + + "This usually indicates the recipe is making changes after it should have stabilized."); + } } } } From a04285145c127e06de982bbcbc5bc3b4712f85d8 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 6 Nov 2023 22:18:37 -0800 Subject: [PATCH 385/447] Actionable error message when test attempts to access a non-existent data table. This is usually a minor mistake when the test author provides the type of the DataTable, when actually they need to provide the type of the row. --- .../src/main/java/org/openrewrite/test/RecipeSpec.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java index 90c43fbb0ea..24139d4579b 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java @@ -34,6 +34,7 @@ import java.nio.file.Path; import java.util.*; import java.util.function.Function; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -209,7 +210,14 @@ public <E> RecipeSpec dataTable(Class<E> rowType, UncheckedConsumer<List<E>> ext return; } } - fail("No data table found with row type " + rowType); + String message = "No data table found with row type: " + rowType; + Set<DataTable<?>> tables = run.getDataTables().keySet(); + if (!tables.isEmpty()) { + message += "\nFound data tables row type(s): " + tables.stream() + .map(it -> it.getType().getName().replace("$", ".")) + .collect(Collectors.joining(",")); + } + fail(message); }); } From 635fdaab38b0a74625ff648827bbbf86d5de52d3 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Mon, 6 Nov 2023 23:27:23 -0800 Subject: [PATCH 386/447] Fix Gradle dependency resolution always checking mavenCentral(). In Gradle, Maven Central is not implicitly defined as an artifact repository. --- .../gradle/AddDependencyVisitor.java | 2 +- .../gradle/UpgradeDependencyVersion.java | 3 +-- .../maven/internal/MavenPomDownloader.java | 22 ++++++++++++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java index a2393902b84..44896cddfda 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependencyVisitor.java @@ -153,7 +153,7 @@ static GradleProject addDependency( resolvedGav = null; transitiveDependencies = Collections.emptyList(); } else { - MavenPomDownloader mpd = new MavenPomDownloader(emptyMap(), ctx, null, null); + MavenPomDownloader mpd = new MavenPomDownloader(ctx); Pom pom = mpd.download(gav, null, null, gp.getMavenRepositories()); ResolvedPom resolvedPom = pom.resolve(emptyList(), mpd, gp.getMavenRepositories(), ctx); resolvedGav = resolvedPom.getGav(); diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index c93bb86ccbe..8b93e25ff11 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -45,7 +45,6 @@ import java.util.*; import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; import static java.util.Objects.requireNonNull; @Value @@ -437,7 +436,7 @@ static GradleProject replaceVersion(GradleProject gp, ExecutionContext ctx, Grou return gp; } - MavenPomDownloader mpd = new MavenPomDownloader(emptyMap(), ctx, null, null); + MavenPomDownloader mpd = new MavenPomDownloader(ctx); Pom pom = mpd.download(gav, null, null, gp.getMavenRepositories()); ResolvedPom resolvedPom = pom.resolve(emptyList(), mpd, gp.getMavenRepositories(), ctx); ResolvedGroupArtifactVersion resolvedGav = resolvedPom.getGav(); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index 177d86c165d..891b2023148 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -53,6 +53,7 @@ import java.util.regex.Pattern; import java.util.stream.Stream; +import static java.util.Collections.emptyMap; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; @@ -87,6 +88,8 @@ public class MavenPomDownloader { private final CheckedFunction1<HttpSender.Request, byte[]> sendRequest; + private boolean addDefaultRepositories = true; + /** * @param projectPoms Other POMs in this project. * @param ctx The execution context, which potentially contain Maven settings customization @@ -104,9 +107,21 @@ public MavenPomDownloader(Map<Path, Pom> projectPoms, ExecutionContext ctx, this.activeProfiles = activeProfiles; } + /** + * A MavenPomDownloader for non-maven contexts where there are no project poms or assumption that maven central + * is implicitly added as a repository. In a Maven contexts, a non-empty projectPoms should be specified to + * {@link #MavenPomDownloader(Map, ExecutionContext)} for accurate results. + * + * @param ctx The execution context, which potentially contain Maven settings customization and {@link HttpSender} customization. + */ + public MavenPomDownloader(ExecutionContext ctx) { + this(emptyMap(), HttpSenderExecutionContextView.view(ctx).getHttpSender(), ctx); + this.addDefaultRepositories = false; + } + /** * @param projectPoms Other POMs in this project. - * @param ctx The execution context, which potentially contain Maven settings customization and + * @param ctx The execution context, which potentially contain Maven settings customization * and {@link HttpSender} customization. */ public MavenPomDownloader(Map<Path, Pom> projectPoms, ExecutionContext ctx) { @@ -646,8 +661,9 @@ private Collection<MavenRepository> distinctNormalizedRepositories(List<MavenRep normalizedRepositories.add(normalizedRepo); } } - - normalizedRepositories.add(normalizeRepository(MavenRepository.MAVEN_CENTRAL, containingPom)); + if(addDefaultRepositories) { + normalizedRepositories.add(normalizeRepository(MavenRepository.MAVEN_CENTRAL, containingPom)); + } return normalizedRepositories; } From cac658f4be75e5c5eb5d806c141b6695bce77e2e Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 7 Nov 2023 00:15:33 -0800 Subject: [PATCH 387/447] Suspect this will fix CI failures I cannot reproduce locally. Specifically: https://ge.openrewrite.org/s/l2g6n4eivgkty/tests/overview?outcome=FAILED --- .../gradle/UpgradeDependencyVersionTest.java | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java index 1d190651c92..ae6792549d7 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/UpgradeDependencyVersionTest.java @@ -55,9 +55,7 @@ void guava() { dependencies { compileOnly 'com.google.guava:guava:29.0-jre' - runtimeOnly ('com.google.guava:guava:29.0-jre') { - force = true - } + runtimeOnly ('com.google.guava:guava:29.0-jre') } """, """ @@ -71,9 +69,7 @@ void guava() { dependencies { compileOnly 'com.google.guava:guava:30.1.1-jre' - runtimeOnly ('com.google.guava:guava:30.1.1-jre') { - force = true - } + runtimeOnly ('com.google.guava:guava:30.1.1-jre') } """, spec -> spec.afterRecipe(after -> { @@ -115,9 +111,7 @@ void updateVersionInVariable() { } dependencies { - implementation ("com.google.guava:guava:$guavaVersion") { - force = true - } + implementation ("com.google.guava:guava:$guavaVersion") implementation "com.fasterxml.jackson.core:jackson-databind:$otherVersion" } """, @@ -133,9 +127,7 @@ void updateVersionInVariable() { } dependencies { - implementation ("com.google.guava:guava:$guavaVersion") { - force = true - } + implementation ("com.google.guava:guava:$guavaVersion") implementation "com.fasterxml.jackson.core:jackson-databind:$otherVersion" } """ @@ -230,9 +222,7 @@ void mapNotationLiteral() { } dependencies { - implementation (group: "com.google.guava", name: "guava", version: '29.0-jre') { - force = true - } + implementation (group: "com.google.guava", name: "guava", version: '29.0-jre') } """, """ @@ -245,9 +235,7 @@ void mapNotationLiteral() { } dependencies { - implementation (group: "com.google.guava", name: "guava", version: '30.1.1-jre') { - force = true - } + implementation (group: "com.google.guava", name: "guava", version: '30.1.1-jre') } """ ) From 1469f123fd3c1ddcc3bf8c0efa9d4fd37d11d4b3 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 7 Nov 2023 00:31:48 -0800 Subject: [PATCH 388/447] Missed removing MavenLocal as default, implicit repository from gradle projects --- .../org/openrewrite/maven/internal/MavenPomDownloader.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index 891b2023148..f0e0bb1616c 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -645,7 +645,9 @@ private Collection<MavenRepository> distinctNormalizedRepositories(List<MavenRep @Nullable ResolvedPom containingPom, @Nullable String acceptsVersion) { Set<MavenRepository> normalizedRepositories = new LinkedHashSet<>(); - normalizedRepositories.add(ctx.getLocalRepository()); + if(addDefaultRepositories) { + normalizedRepositories.add(ctx.getLocalRepository()); + } for (MavenRepository repo : repositories) { MavenRepository normalizedRepo = normalizeRepository(repo, containingPom); From 1d0aeec11e2d7f239f2f656bfd964b17ca8e4d17 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 7 Nov 2023 00:41:16 -0800 Subject: [PATCH 389/447] Record that local repositories were tried when unable to locate a dependency from local repositories. --- .../java/org/openrewrite/maven/internal/MavenPomDownloader.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index f0e0bb1616c..2672567dc0a 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -524,6 +524,7 @@ public Pom download(GroupArtifactVersion gav, //NOTE: The pom may exist without a .jar artifact if the pom packaging is "pom" if (!f.exists()) { + repositoryResponses.put(repo, "Local repository does not contain pom"); continue; } From 68ab2fadad88f177fad22173ced6d1774e34ee0a Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 7 Nov 2023 02:13:21 -0800 Subject: [PATCH 390/447] Revert change adding logging of failure to lookup dependency in local repository. In the context of Maven, although not Gradle, it is usually redundant to specify that local .m2 repository access was attempted. --- .../java/org/openrewrite/maven/internal/MavenPomDownloader.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index 2672567dc0a..f0e0bb1616c 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -524,7 +524,6 @@ public Pom download(GroupArtifactVersion gav, //NOTE: The pom may exist without a .jar artifact if the pom packaging is "pom" if (!f.exists()) { - repositoryResponses.put(repo, "Local repository does not contain pom"); continue; } From a94650d765fea173629655e4c1a300805054a7f4 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Tue, 7 Nov 2023 14:27:25 +0100 Subject: [PATCH 391/447] fix collision with nested class references (#3669) * fix collision with nested class references * fix --- ...ortenFullyQualifiedTypeReferencesTest.java | 44 +++++++++++++++++++ .../ShortenFullyQualifiedTypeReferences.java | 10 +++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java index 1632c32cc3f..0c58a549d59 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java @@ -464,4 +464,48 @@ Function<Collection<?>, Integer> m1() { ) ); } + + @Test + void nestedReferenceCollision() { + rewriteRun( + java(""" + class List { + class A { + } + } + """), + java(""" + import java.util.ArrayList; + + class Test { + void test(List.A l1) { + java.util.List<Integer> l2 = new ArrayList<>(); + } + } + """) + ); + } + + @Test + void deeperNestedReferenceCollision() { + rewriteRun( + java(""" + class List { + class A { + class B { + } + } + } + """), + java(""" + import java.util.ArrayList; + + class Test { + void test(List.A.B l1) { + java.util.List<Integer> l2 = new ArrayList<>(); + } + } + """) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java b/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java index 0f66f92fa03..c2ba78b55ca 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java @@ -19,10 +19,7 @@ import org.openrewrite.internal.lang.NonNull; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.DefaultJavaTypeSignatureBuilder; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaSourceFile; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.Space; +import org.openrewrite.java.tree.*; import java.time.Duration; import java.util.HashMap; @@ -83,6 +80,11 @@ public J.Import visitImport(J.Import import_, Map<String, JavaType> types) { @Override public J.FieldAccess visitFieldAccess(J.FieldAccess fieldAccess, Map<String, JavaType> types) { + if (fieldAccess.getTarget() instanceof J.Identifier) { + visitIdentifier((J.Identifier) fieldAccess.getTarget(), types); + } else if (fieldAccess.getTarget() instanceof J.FieldAccess) { + visitFieldAccess((J.FieldAccess) fieldAccess.getTarget(), types); + } return fieldAccess; } From 662822fa92824dcbe481ce15f4b287930d003014 Mon Sep 17 00:00:00 2001 From: Kun Li <122563761+kunli2@users.noreply.github.com> Date: Tue, 7 Nov 2023 10:35:40 -0800 Subject: [PATCH 392/447] For parsing errors caused by print-idempotent check, add diff to the message of ParseExceptionResult marker (#3670) --- .../org/openrewrite/ParseExceptionResult.java | 12 ++++-- .../src/main/java/org/openrewrite/Parser.java | 12 +++++- .../src/main/java/org/openrewrite/Result.java | 30 ++++++++------ .../java/org/openrewrite/tree/ParseError.java | 12 +++++- .../openrewrite/FindParseFailuresTest.java | 2 +- .../test/java/org/openrewrite/ParserTest.java | 39 +++++++++++++++++++ 6 files changed, 90 insertions(+), 17 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/ParseExceptionResult.java b/rewrite-core/src/main/java/org/openrewrite/ParseExceptionResult.java index 298e5670fbc..e02e8b214de 100644 --- a/rewrite-core/src/main/java/org/openrewrite/ParseExceptionResult.java +++ b/rewrite-core/src/main/java/org/openrewrite/ParseExceptionResult.java @@ -40,18 +40,24 @@ public class ParseExceptionResult implements Marker { @Nullable String treeType; - public static ParseExceptionResult build(Class<? extends Parser> parserClass, Throwable t) { + public static ParseExceptionResult build(Class<? extends Parser> parserClass, + Throwable t, + @Nullable String message) { String simpleName = t.getClass().getSimpleName(); return new ParseExceptionResult( randomId(), parserClass.getSimpleName(), !StringUtils.isBlank(simpleName) ? simpleName : t.getClass().getName(), - ExceptionUtils.sanitizeStackTrace(t, parserClass), + (message != null ? message : "") + ExceptionUtils.sanitizeStackTrace(t, parserClass), null ); } + public static ParseExceptionResult build(Parser parser, Throwable t, @Nullable String message) { + return build(parser.getClass(), t, message); + } + public static ParseExceptionResult build(Parser parser, Throwable t) { - return build(parser.getClass(), t); + return build(parser.getClass(), t, null); } } diff --git a/rewrite-core/src/main/java/org/openrewrite/Parser.java b/rewrite-core/src/main/java/org/openrewrite/Parser.java index 44e8bc2220b..1638c4619ea 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Parser.java +++ b/rewrite-core/src/main/java/org/openrewrite/Parser.java @@ -17,7 +17,9 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.eclipse.jgit.lib.FileMode; import org.openrewrite.internal.EncodingDetectingInputStream; +import org.openrewrite.internal.InMemoryDiffEntry; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.tree.ParseError; @@ -30,6 +32,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.function.Supplier; @@ -43,12 +46,18 @@ public interface Parser { default SourceFile requirePrintEqualsInput(SourceFile sourceFile, Parser.Input input, @Nullable Path relativeTo, ExecutionContext ctx) { if (ctx.getMessage(ExecutionContext.REQUIRE_PRINT_EQUALS_INPUT, true) && !sourceFile.printEqualsInput(input, ctx)) { + String diff = Result.diff(input.getSource(ctx).readFully(), sourceFile.printAll(), input.getPath()); + String message = null; + if (diff != null) { + message = sourceFile.getSourcePath() + " is not print idempotent. \n" + diff; + } return ParseError.build( this, input, relativeTo, ctx, - new IllegalStateException(sourceFile.getSourcePath() + " is not print idempotent.") + new IllegalStateException(sourceFile.getSourcePath() + " is not print idempotent."), + message ).withErroneous(sourceFile); } return sourceFile; @@ -70,6 +79,7 @@ default Stream<SourceFile> parse(Iterable<Path> sourceFiles, @Nullable Path rela ); } + default Stream<SourceFile> parse(String... sources) { return parse(new InMemoryExecutionContext(), sources); } diff --git a/rewrite-core/src/main/java/org/openrewrite/Result.java b/rewrite-core/src/main/java/org/openrewrite/Result.java index e2fc144eec2..74c6a89827c 100755 --- a/rewrite-core/src/main/java/org/openrewrite/Result.java +++ b/rewrite-core/src/main/java/org/openrewrite/Result.java @@ -16,27 +16,16 @@ package org.openrewrite; import lombok.Getter; -import org.eclipse.jgit.diff.DiffEntry; -import org.eclipse.jgit.diff.DiffFormatter; -import org.eclipse.jgit.diff.RawTextComparator; -import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; -import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; import org.eclipse.jgit.lib.*; import org.openrewrite.config.RecipeDescriptor; import org.openrewrite.internal.InMemoryDiffEntry; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.RecipesThatMadeChanges; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; public class Result { /** @@ -182,6 +171,25 @@ public String diff(@Nullable Path relativeTo, @Nullable PrintOutputCapture.Marke } } + @Nullable + public static String diff(String before, String after, Path path) { + String diff = null; + try (InMemoryDiffEntry diffEntry = new InMemoryDiffEntry( + path, + path, + null, + before, + after, + Collections.emptySet(), + FileMode.REGULAR_FILE, + FileMode.REGULAR_FILE + )) { + diff = diffEntry.getDiff(Boolean.FALSE); + } catch (Exception ignored) { + } + return diff; + } + @Override public String toString() { return diff(); diff --git a/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java b/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java index a5e76f99e95..e1e8fb01bbc 100644 --- a/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java +++ b/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java @@ -86,15 +86,25 @@ public <R extends Tree, P> R accept(TreeVisitor<R, P> v, P p) { return (R) v.adapt(ParseErrorVisitor.class).visitParseError(this, p); } + public static ParseError build(Parser parser, Parser.Input input, @Nullable Path relativeTo, ExecutionContext ctx, Throwable t) { + return build(parser, input, relativeTo, ctx, t, null); + } + + public static ParseError build(Parser parser, + Parser.Input input, + @Nullable Path relativeTo, + ExecutionContext ctx, + Throwable t, + @Nullable String message) { EncodingDetectingInputStream is = input.getSource(ctx); return new ParseError( Tree.randomId(), - new Markers(Tree.randomId(), singletonList(ParseExceptionResult.build(parser, t))), + new Markers(Tree.randomId(), singletonList(ParseExceptionResult.build(parser, t, message))), input.getRelativePath(relativeTo), input.getFileAttributes(), parser.getCharset(ctx).name(), diff --git a/rewrite-core/src/test/java/org/openrewrite/FindParseFailuresTest.java b/rewrite-core/src/test/java/org/openrewrite/FindParseFailuresTest.java index 228db90be1e..4ee8eac4b57 100644 --- a/rewrite-core/src/test/java/org/openrewrite/FindParseFailuresTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/FindParseFailuresTest.java @@ -37,7 +37,7 @@ public void defaults(RecipeSpec spec) { @Test void findParseFailures() { - ParseExceptionResult per = ParseExceptionResult.build(PlainTextParser.class, new RuntimeException("boom")); + ParseExceptionResult per = ParseExceptionResult.build(PlainTextParser.class, new RuntimeException("boom"), null); rewriteRun( spec -> spec.dataTable(ParseFailures.Row.class, rows -> { assertThat(rows).hasSize(1); diff --git a/rewrite-core/src/test/java/org/openrewrite/ParserTest.java b/rewrite-core/src/test/java/org/openrewrite/ParserTest.java index 2888f157384..0f1b00fdb4e 100644 --- a/rewrite-core/src/test/java/org/openrewrite/ParserTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/ParserTest.java @@ -21,6 +21,10 @@ import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingExecutionContextView; +import java.io.ByteArrayInputStream; +import java.nio.file.Path; +import java.nio.file.Paths; + import static java.nio.charset.StandardCharsets.ISO_8859_1; import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.test.SourceSpecs.text; @@ -51,4 +55,39 @@ void canPrintParseError() { assertThat(pe.printAll()).isEqualTo("bad file"); } + + @Test + void printIdempotentDiffPrint() { + Parser parser = new PlainTextParser(); + ExecutionContext ctx = new InMemoryExecutionContext(); + + String before = """ + line 1 + line 2 + """; + String after = """ + line 1 + DIFF HERE + line 2 + """; + Path path = Paths.get("1.txt"); + String expectedDiff = """ + --- a/1.txt + +++ b/1.txt + @@ -1,3 +1,2 @@\s + line 1 + -DIFF HERE + line 2 + """; + + Parser.Input input1 = new Parser.Input(path, () -> new ByteArrayInputStream(before.getBytes())); + Parser.Input input2 = new Parser.Input(path, () -> new ByteArrayInputStream(after.getBytes())); + SourceFile s1 = parser.parse(input1.getSource(ctx).readFully()).toList().get(0); + SourceFile out = parser.requirePrintEqualsInput(s1, input2, null, ctx); + assertThat(out).isInstanceOf(ParseError.class); + ParseExceptionResult parseExceptionResult = out.getMarkers().findFirst(ParseExceptionResult.class).get(); + int startIndex = parseExceptionResult.getMessage().indexOf(expectedDiff); + int endIndex = startIndex + expectedDiff.length(); + assertThat(parseExceptionResult.getMessage().substring(startIndex, endIndex)).isEqualTo(expectedDiff); + } } From e929f1519549262d4259dff504bc427d185a82ce Mon Sep 17 00:00:00 2001 From: Kun Li <122563761+kunli2@users.noreply.github.com> Date: Tue, 7 Nov 2023 12:24:08 -0800 Subject: [PATCH 393/447] Add missing visit import alias (#3672) --- rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index 7d28636c23f..547bd0aa864 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -695,6 +695,7 @@ public J visitImport(J.Import import_, P p) { i = i.withMarkers(visitMarkers(i.getMarkers(), p)); i = i.getPadding().withStatic(visitLeftPadded(i.getPadding().getStatic(), JLeftPadded.Location.STATIC_IMPORT, p)); i = i.withQualid(visitAndCast(i.getQualid(), p)); + i = i.getPadding().withAlias(visitLeftPadded(i.getPadding().getAlias(), JLeftPadded.Location.IMPORT_ALIAS_PREFIX, p)); return i; } From 37e66818481536917866673d05a144503cbe25dc Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 7 Nov 2023 13:02:13 -0800 Subject: [PATCH 394/447] Remove unnecessary message field from ParseError.build() --- .../src/main/java/org/openrewrite/Parser.java | 22 ++++--------------- .../java/org/openrewrite/tree/ParseError.java | 12 +--------- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/Parser.java b/rewrite-core/src/main/java/org/openrewrite/Parser.java index 1638c4619ea..d31b95fe116 100644 --- a/rewrite-core/src/main/java/org/openrewrite/Parser.java +++ b/rewrite-core/src/main/java/org/openrewrite/Parser.java @@ -17,9 +17,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.eclipse.jgit.lib.FileMode; import org.openrewrite.internal.EncodingDetectingInputStream; -import org.openrewrite.internal.InMemoryDiffEntry; import org.openrewrite.internal.StringUtils; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.tree.ParseError; @@ -32,7 +30,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.function.Supplier; @@ -47,17 +44,12 @@ default SourceFile requirePrintEqualsInput(SourceFile sourceFile, Parser.Input i if (ctx.getMessage(ExecutionContext.REQUIRE_PRINT_EQUALS_INPUT, true) && !sourceFile.printEqualsInput(input, ctx)) { String diff = Result.diff(input.getSource(ctx).readFully(), sourceFile.printAll(), input.getPath()); - String message = null; - if (diff != null) { - message = sourceFile.getSourcePath() + " is not print idempotent. \n" + diff; - } return ParseError.build( this, input, relativeTo, ctx, - new IllegalStateException(sourceFile.getSourcePath() + " is not print idempotent."), - message + new IllegalStateException(sourceFile.getSourcePath() + " is not print idempotent. \n" + diff) ).withErroneous(sourceFile); } return sourceFile; @@ -142,7 +134,9 @@ default Charset getCharset(ExecutionContext ctx) { * memory. */ class Input { + @Getter private final boolean synthetic; + @Getter private final Path path; private final Supplier<InputStream> source; @@ -206,10 +200,6 @@ public static List<Input> fromResource(String resource, String delimiter, @Nulla .collect(toList()); } - public Path getPath() { - return path; - } - public Path getRelativePath(@Nullable Path relativeTo) { return relativeTo == null ? path : relativeTo.relativize(path); } @@ -218,10 +208,6 @@ public EncodingDetectingInputStream getSource(ExecutionContext ctx) { return new EncodingDetectingInputStream(source.get(), ParsingExecutionContextView.view(ctx).getCharset()); } - public boolean isSynthetic() { - return synthetic; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -238,9 +224,9 @@ public int hashCode() { Path sourcePathFromSourceText(Path prefix, String sourceCode); + @Getter @RequiredArgsConstructor abstract class Builder implements Cloneable { - @Getter private final Class<? extends SourceFile> sourceFileType; public abstract Parser build(); diff --git a/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java b/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java index e1e8fb01bbc..a5e76f99e95 100644 --- a/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java +++ b/rewrite-core/src/main/java/org/openrewrite/tree/ParseError.java @@ -86,25 +86,15 @@ public <R extends Tree, P> R accept(TreeVisitor<R, P> v, P p) { return (R) v.adapt(ParseErrorVisitor.class).visitParseError(this, p); } - public static ParseError build(Parser parser, Parser.Input input, @Nullable Path relativeTo, ExecutionContext ctx, Throwable t) { - return build(parser, input, relativeTo, ctx, t, null); - } - - public static ParseError build(Parser parser, - Parser.Input input, - @Nullable Path relativeTo, - ExecutionContext ctx, - Throwable t, - @Nullable String message) { EncodingDetectingInputStream is = input.getSource(ctx); return new ParseError( Tree.randomId(), - new Markers(Tree.randomId(), singletonList(ParseExceptionResult.build(parser, t, message))), + new Markers(Tree.randomId(), singletonList(ParseExceptionResult.build(parser, t))), input.getRelativePath(relativeTo), input.getFileAttributes(), parser.getCharset(ctx).name(), From c60df03b1029cebcb8b378028efe12506b6e7eab Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 7 Nov 2023 22:39:10 +0100 Subject: [PATCH 395/447] Fix problem with error on `Jenkinsfile` source Recipes executed against the spring-projects/spring-plugin repo often reported an error on the `Jenkinsfile` source. This turns out to be a combination of two things: - First, the `G.Range#withType()` always returns a new object, even if there were no changes. This is problematic for preconditions, as the precondition will appear to be met, if the visitor traverses the entire source. - Second, the `FindGradleProject` visitor is indeed used as a precondition and leads to the problem described above, as it traverses the entire source. --- .../org/openrewrite/gradle/search/FindGradleProject.java | 9 ++++----- .../src/main/java/org/openrewrite/groovy/tree/G.java | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleProject.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleProject.java index 742356f8ccd..10745cfd788 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleProject.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindGradleProject.java @@ -20,8 +20,6 @@ import org.openrewrite.*; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.marker.SearchResult; @@ -53,16 +51,17 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { if (searchCriteria == SearchCriteria.Marker) { - return new JavaVisitor<ExecutionContext>() { + return new TreeVisitor<Tree, ExecutionContext>() { @Override - public J visit(@Nullable Tree tree, ExecutionContext ctx) { + public Tree preVisit(Tree tree, ExecutionContext ctx) { + stopAfterPreVisit(); if (tree instanceof JavaSourceFile) { JavaSourceFile cu = (JavaSourceFile) requireNonNull(tree); if (cu.getMarkers().findFirst(GradleProject.class).isPresent()) { return SearchResult.found(cu); } } - return super.visit(tree, ctx); + return tree; } }; } diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/tree/G.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/tree/G.java index 2c48ae4fe8a..b6cb704d6fd 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/tree/G.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/tree/G.java @@ -804,8 +804,7 @@ public JavaType getType() { @Override public Range withType(@Nullable JavaType type) { - return new Range(null, id, prefix, markers, from.withType(type), inclusive, - to.withType(type)); + return withFrom(from.withType(type)).withTo(to.withType(type)); } @Override From 204f7099446106ffbe37352b19d29f7c7c4678ef Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 7 Nov 2023 16:53:23 -0800 Subject: [PATCH 396/447] Ensure that MavenPomDownloader.normalizeRepository supplies exception to its nullReasonConsumer on all failing code paths --- .../openrewrite/maven/internal/MavenPomDownloader.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index f0e0bb1616c..e6de85cb102 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -765,12 +765,20 @@ public MavenRepository normalizeRepository(MavenRepository originalRepository, @ } } } + if(normalized == null) { + if (nullReasonConsumer != null) { + nullReasonConsumer.accept(t); + } + } } mavenCache.putNormalizedRepository(repository, normalized); result = Optional.ofNullable(normalized); } } catch (Exception e) { ctx.getOnError().accept(e); + if (nullReasonConsumer != null) { + nullReasonConsumer.accept(e); + } mavenCache.putNormalizedRepository(repository, null); } @@ -834,7 +842,7 @@ private MavenRepository applyMirrors(MavenRepository repository) { } @Getter - private static class HttpSenderResponseException extends Exception { + public static class HttpSenderResponseException extends Exception { @Nullable private final Integer responseCode; From bc6f3b89c762cfecb18cc398570b76c538ab75a8 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 8 Nov 2023 11:03:13 +0100 Subject: [PATCH 397/447] Move prefix to correct position for array type references in Javadoc Fixes: #3674 --- .../ReloadableJava11JavadocVisitor.java | 6 +++--- .../ReloadableJava17JavadocVisitor.java | 6 +++--- .../ReloadableJava21JavadocVisitor.java | 6 +++--- .../java/ReloadableJava8JavadocVisitor.java | 6 +++--- .../java/format/MethodParamPadTest.java | 19 +++++++++++++++++++ 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java index be568dbbbf2..26718b5b11d 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java @@ -1080,7 +1080,7 @@ private List<Javadoc> convertMultiline(List<? extends DocTree> dts) { /** * A {@link J} may contain new lines in each {@link Space} and each new line will have a corresponding * {@link org.openrewrite.java.tree.Javadoc.LineBreak}. - * + * <p> * This method collects the linebreaks associated to new lines in a Space, and removes the applicable linebreaks * from the map. */ @@ -1144,7 +1144,7 @@ public J visitArrayType(ArrayTypeTree node, Space fmt) { typeIdent = ((ArrayTypeTree) typeIdent).getType(); } - TypeTree elemType = (TypeTree) scan(typeIdent, fmt); + TypeTree elemType = (TypeTree) scan(typeIdent, Space.EMPTY); List<JRightPadded<Space>> dimensions = emptyList(); if (dimCount > 0) { @@ -1160,7 +1160,7 @@ public J visitArrayType(ArrayTypeTree node, Space fmt) { return new J.ArrayType( randomId(), - Space.EMPTY, + fmt, Markers.EMPTY, elemType, dimensions diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java index 8a6fa49bd6d..aa78dbc4194 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java @@ -601,7 +601,6 @@ public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { } else { qualifier = null; } - } if (ref.memberName != null) { @@ -795,6 +794,7 @@ public Tree visitSerialData(SerialDataTree node, List<Javadoc> body) { @Override public Tree visitSerialField(SerialFieldTree node, List<Javadoc> body) { body.addAll(sourceBefore("@serialField")); + return new Javadoc.SerialField(randomId(), Markers.EMPTY, visitIdentifier(node.getName(), whitespaceBefore()), visitReference(node.getType(), whitespaceBefore()), @@ -1148,7 +1148,7 @@ public J visitArrayType(ArrayTypeTree node, Space fmt) { typeIdent = ((ArrayTypeTree) typeIdent).getType(); } - TypeTree elemType = (TypeTree) scan(typeIdent, fmt); + TypeTree elemType = (TypeTree) scan(typeIdent, Space.EMPTY); List<JRightPadded<Space>> dimensions = emptyList(); if (dimCount > 0) { @@ -1164,7 +1164,7 @@ public J visitArrayType(ArrayTypeTree node, Space fmt) { return new J.ArrayType( randomId(), - Space.EMPTY, + fmt, Markers.EMPTY, elemType, dimensions diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java index 464a1aa9511..a0ecceb6bbc 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java @@ -601,7 +601,6 @@ public J visitReference(@Nullable ReferenceTree node, List<Javadoc> body) { } else { qualifier = null; } - } if (ref.memberName != null) { @@ -795,6 +794,7 @@ public Tree visitSerialData(SerialDataTree node, List<Javadoc> body) { @Override public Tree visitSerialField(SerialFieldTree node, List<Javadoc> body) { body.addAll(sourceBefore("@serialField")); + return new Javadoc.SerialField(randomId(), Markers.EMPTY, visitIdentifier(node.getName(), whitespaceBefore()), visitReference(node.getType(), whitespaceBefore()), @@ -1148,7 +1148,7 @@ public J visitArrayType(ArrayTypeTree node, Space fmt) { typeIdent = ((ArrayTypeTree) typeIdent).getType(); } - TypeTree elemType = (TypeTree) scan(typeIdent, fmt); + TypeTree elemType = (TypeTree) scan(typeIdent, Space.EMPTY); List<JRightPadded<Space>> dimensions = emptyList(); if (dimCount > 0) { @@ -1164,7 +1164,7 @@ public J visitArrayType(ArrayTypeTree node, Space fmt) { return new J.ArrayType( randomId(), - Space.EMPTY, + fmt, Markers.EMPTY, elemType, dimensions diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java index 0c641775262..74e47da605d 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java @@ -1002,7 +1002,7 @@ private List<Javadoc> convertMultiline(List<? extends DocTree> dts) { /** * A {@link J} may contain new lines in each {@link Space} and each new line will have a corresponding * {@link org.openrewrite.java.tree.Javadoc.LineBreak}. - * + * <p> * This method collects the linebreaks associated to new lines in a Space, and removes the applicable linebreaks * from the map. */ @@ -1065,7 +1065,7 @@ public J visitArrayType(ArrayTypeTree node, Space fmt) { typeIdent = ((ArrayTypeTree) typeIdent).getType(); } - TypeTree elemType = (TypeTree) scan(typeIdent, fmt); + TypeTree elemType = (TypeTree) scan(typeIdent, Space.EMPTY); List<JRightPadded<Space>> dimensions = emptyList(); if (dimCount > 0) { @@ -1081,7 +1081,7 @@ public J visitArrayType(ArrayTypeTree node, Space fmt) { return new J.ArrayType( randomId(), - Space.EMPTY, + fmt, Markers.EMPTY, elemType, dimensions diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/MethodParamPadTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/MethodParamPadTest.java index 97186c8bcb9..80e220461f4 100755 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/MethodParamPadTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/MethodParamPadTest.java @@ -26,6 +26,7 @@ import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpec; +import org.openrewrite.test.TypeValidation; import java.util.Collection; import java.util.function.Consumer; @@ -326,6 +327,24 @@ void test() { ); } + @Test + void javadocNotFormatted() { + rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), + java( + """ + class A { + /** + * @see java.sql.Connection#createArrayOf(String, Object[]) + */ + void test() { + } + } + """ + ) + ); + } + @Test void recordWithCompactConstructor() { rewriteRun( From 69b48c8338da56969fb0a9433b6dea47adb28737 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 8 Nov 2023 22:27:26 +0100 Subject: [PATCH 398/447] Tweak `isInitBlock()` so it doesn't return `true` for lambda bodies --- rewrite-java/src/main/java/org/openrewrite/java/tree/J.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index 540ffe2b6b3..836f467bd05 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -851,6 +851,8 @@ public static boolean isInitBlock(Cursor cursor) { } else if (next instanceof J.NewClass) { J.NewClass newClass = (J.NewClass) next; return newClass.getBody() == parentBlock; + } else if (next instanceof J.Lambda) { + return false; } } return false; From 7f7efacc10f37f00f325ae95cc22ff6e7e858218 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 8 Nov 2023 14:00:22 -0800 Subject: [PATCH 399/447] Add a ParenthesizedTypeTree to J for use in languages like kotlin that have different parenthesis placement rules --- .../java/org/openrewrite/java/tree/J.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index 836f467bd05..1262163ffb4 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -2349,6 +2349,46 @@ public ForLoop withBody(JRightPadded<Statement> body) { } } + /** + * Java does not allow for parenthesis around TypeTree in places like a type cast where a J.ControlParenthesis is + * used. But other languages, like Kotlin, do. + */ + @Value + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @AllArgsConstructor(onConstructor_ = {@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)} ) + @With + class ParenthesizedTypeTree implements J, TypeTree, Expression { + @Getter + @EqualsAndHashCode.Include + UUID id; + + @Getter + Space prefix; + + @Getter + Markers markers; + + J.Parentheses<TypeTree> parenthesizedType; + + List<J.Annotation> annotations; + + @Override + public @Nullable JavaType getType() { + return parenthesizedType.getType(); + } + + @SuppressWarnings("unchecked") + @Override + public ParenthesizedTypeTree withType(@Nullable JavaType type) { + return withParenthesizedType(parenthesizedType.withType(type)); + } + + @Override + public CoordinateBuilder.Expression getCoordinates() { + return new CoordinateBuilder.Expression(this); + } + } + @Value @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) @AllArgsConstructor(onConstructor_ = {@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)} ) From 02c02b659d4aa4884cb2d9dea05da34225868eb5 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 8 Nov 2023 14:28:02 -0800 Subject: [PATCH 400/447] Annotations before parenthesizedType --- .../src/main/java/org/openrewrite/java/tree/J.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index 1262163ffb4..6a06ca1b81b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -1245,24 +1245,19 @@ public String getSimpleName() { public static final class Kind implements J { @With - @Getter @EqualsAndHashCode.Include UUID id; @With - @Getter Space prefix; @With - @Getter Markers markers; @With - @Getter List<Annotation> annotations; @With - @Getter Type type; public enum Type { @@ -2368,10 +2363,10 @@ class ParenthesizedTypeTree implements J, TypeTree, Expression { @Getter Markers markers; - J.Parentheses<TypeTree> parenthesizedType; - List<J.Annotation> annotations; + J.Parentheses<TypeTree> parenthesizedType; + @Override public @Nullable JavaType getType() { return parenthesizedType.getType(); @@ -3556,11 +3551,9 @@ public String toString() { @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) @Data public static final class IdentifierWithAnnotations { - @Getter @With Identifier identifier; - @Getter @With List<Annotation> annotations; } From 73a74572533f4b7f17cd7c76a5a43d4be48bcc59 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 8 Nov 2023 18:46:30 -0800 Subject: [PATCH 401/447] Make Scope parameter optional --- .../openrewrite/maven/search/DependencyInsight.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/DependencyInsight.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/DependencyInsight.java index 241d4ce11d9..2a6e6d64f93 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/search/DependencyInsight.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/DependencyInsight.java @@ -55,13 +55,15 @@ public class DependencyInsight extends Recipe { String artifactIdPattern; @Option(displayName = "Scope", - description = "Match dependencies with the specified scope", + description = "Match dependencies with the specified scope. All scopes are searched by default.", valid = {"compile", "test", "runtime", "provided"}, - example = "compile") + example = "compile", + required = false) + @Nullable String scope; @Option(displayName = "Only direct", - description = "Default false. If enabled, transitive dependencies will not be considered.", + description = "If enabled, transitive dependencies will not be considered. All dependencies are searched by default.", required = false, example = "true") @Nullable @@ -88,7 +90,7 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - Scope aScope = Scope.fromName(scope); + Scope aScope = (scope == null) ? null : Scope.fromName(scope); return new MavenIsoVisitor<ExecutionContext>() { @Override From 828ba1c8c2d228cabb7b5e4bd1d3bf9e7304b331 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 9 Nov 2023 19:30:23 +0100 Subject: [PATCH 402/447] Fix `StackOverflowError` in `ResolvedDependency#findDependency()` (#3678) * Fix `StackOverflowError` in `ResolvedDependency#findDependency()` * Add test case --- .../gradle/search/DependencyInsightTest.java | 22 ++++++++++++++++++- .../maven/tree/ResolvedDependency.java | 11 ++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java index 9688e307609..bb2474d85b9 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java @@ -67,6 +67,27 @@ void findTransitiveDependency() { ); } + @Test + void recursive() { + rewriteRun( + spec -> spec.recipe(new DependencyInsight("doesnotexist", "doesnotexist", null)), + buildGradle(""" + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + implementation 'io.grpc:grpc-services:1.59.0' + } + """ + ) + ); + } + @Test void pattern() { rewriteRun( @@ -107,5 +128,4 @@ void pattern() { ) ); } - } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java index 61d0b9b5f12..ccb4f02b48b 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedDependency.java @@ -23,7 +23,7 @@ import org.openrewrite.internal.lang.Nullable; import java.io.Serializable; -import java.util.List; +import java.util.*; import static java.util.Collections.emptyList; import static org.openrewrite.internal.StringUtils.matchesGlob; @@ -121,12 +121,19 @@ public String getDatedSnapshotVersion() { @Nullable public ResolvedDependency findDependency(String groupId, String artifactId) { + return findDependency0(groupId, artifactId, Collections.newSetFromMap(new IdentityHashMap<>())); + } + + @Nullable + private ResolvedDependency findDependency0(String groupId, String artifactId, Set<ResolvedDependency> visited) { if (matchesGlob(getGroupId(), groupId) && matchesGlob(getArtifactId(), artifactId)) { return this; + } else if (!visited.add(this)) { + return null; } outer: for (ResolvedDependency dependency : dependencies) { - ResolvedDependency found = dependency.findDependency(groupId, artifactId); + ResolvedDependency found = dependency.findDependency0(groupId, artifactId, visited); if (found != null) { if (getRequested().getExclusions() != null) { for (GroupArtifact exclusion : getRequested().getExclusions()) { From 173bafc8309e36e5728ced0e6df66a6fcfbbea40 Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Thu, 9 Nov 2023 12:46:25 -0800 Subject: [PATCH 403/447] Add missing visitParenthesizedTypeTree --- .../org/openrewrite/java/JavaVisitor.java | 19 +++++++++++++++++++ .../java/org/openrewrite/java/tree/J.java | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index 547bd0aa864..dab2a19af94 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Objects; +@SuppressWarnings("unused") public class JavaVisitor<P> extends TreeVisitor<J, P> { @Nullable @@ -646,6 +647,24 @@ public J visitForControl(J.ForLoop.Control control, P p) { return c; } + public J visitParenthesizedTypeTree(J.ParenthesizedTypeTree parTree, P p) { + J.ParenthesizedTypeTree t = parTree; + t = t.withPrefix(visitSpace(t.getPrefix(), Space.Location.PARENTHESES_PREFIX, p)); + t = t.withMarkers(visitMarkers(t.getMarkers(), p)); + if (t.getAnnotations() != null && !t.getAnnotations().isEmpty()) { + t = t.withAnnotations(ListUtils.map(t.getAnnotations(), a -> visitAndCast(a, p))); + } + + J temp = visitParentheses(t.getParenthesizedType(), p); + if (!(temp instanceof J.Parentheses)) { + return temp; + } else { + //noinspection unchecked + t = t.withParenthesizedType((J.Parentheses<TypeTree>)temp); + } + return t; + } + public J visitIdentifier(J.Identifier ident, P p) { J.Identifier i = ident; if (i.getAnnotations() != null && !i.getAnnotations().isEmpty()) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index 6a06ca1b81b..7d10a24ea65 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -2378,6 +2378,11 @@ public ParenthesizedTypeTree withType(@Nullable JavaType type) { return withParenthesizedType(parenthesizedType.withType(type)); } + @Override + public <P> J acceptJava(JavaVisitor<P> v, P p) { + return v.visitParenthesizedTypeTree(this, p); + } + @Override public CoordinateBuilder.Expression getCoordinates() { return new CoordinateBuilder.Expression(this); From e4f703bb82caf6e50865989fdfc16595e70450ac Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 9 Nov 2023 13:15:19 -0800 Subject: [PATCH 404/447] Add optional version parameter to dependency insight recipes --- .../gradle/search/DependencyInsight.java | 93 ++++++++++++------ .../gradle/search/DependencyInsightTest.java | 42 +++++++- .../maven/search/DependencyInsight.java | 98 ++++++++++++------- .../search/DoesNotIncludeDependency.java | 6 +- .../maven/search/DependencyInsightTest.java | 56 ++++++++++- 5 files changed, 217 insertions(+), 78 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java index d9bd5c3f60e..7061923c5cd 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/DependencyInsight.java @@ -28,11 +28,14 @@ import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; +import org.openrewrite.marker.Markup; import org.openrewrite.marker.SearchResult; import org.openrewrite.maven.table.DependenciesInUse; import org.openrewrite.maven.tree.Dependency; import org.openrewrite.maven.tree.GroupArtifactVersion; import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; import java.util.*; import java.util.stream.Collectors; @@ -57,6 +60,15 @@ public class DependencyInsight extends Recipe { example = "jackson-module-*") String artifactIdPattern; + @Option(displayName = "Version", + description = "Match only dependencies with the specified version. " + + "Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used." + + "All versions are searched by default.", + example = "1.x", + required = false) + @Nullable + String version; + @Option(displayName = "Scope", description = "Match dependencies with the specified scope. If not specified, all configurations will be searched.", example = "compileClasspath", @@ -75,6 +87,15 @@ public String getDescription() { "Results include dependencies that either directly match or transitively include a matching dependency."; } + @Override + public Validated<Object> validate() { + Validated<Object> v = super.validate(); + if (version != null) { + v = v.and(Semver.validate(version, null)); + } + return v; + } + @Override public TreeVisitor<?, ExecutionContext> getVisitor() { return new TreeVisitor<Tree, ExecutionContext>() { @@ -98,38 +119,50 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { Map<String, Set<GroupArtifactVersion>> configurationToDirectDependency = new HashMap<>(); Map<GroupArtifactVersion, Set<GroupArtifactVersion>> directDependencyToTargetDependency = new HashMap<>(); for (GradleDependencyConfiguration c : gp.getConfigurations()) { - if (configuration == null || configuration.isEmpty() || c.getName().equals(configuration)) { - for (ResolvedDependency resolvedDependency : c.getResolved()) { - ResolvedDependency dep = resolvedDependency.findDependency(groupIdPattern, artifactIdPattern); - if (dep != null) { - GroupArtifactVersion requestedGav = new GroupArtifactVersion(resolvedDependency.getGroupId(), resolvedDependency.getArtifactId(), resolvedDependency.getVersion()); - GroupArtifactVersion targetGav = new GroupArtifactVersion(dep.getGroupId(), dep.getArtifactId(), dep.getVersion()); - configurationToDirectDependency.compute(c.getName(), (k, v) -> { - if (v == null) { - v = new LinkedHashSet<>(); - } - v.add(requestedGav); - return v; - }); - directDependencyToTargetDependency.compute(requestedGav, (k, v) -> { - if (v == null) { - v = new LinkedHashSet<>(); - } - v.add(targetGav); - return v; - }); - dependenciesInUse.insertRow(ctx, new DependenciesInUse.Row( - projectName, - sourceSetName, - dep.getGroupId(), - dep.getArtifactId(), - dep.getVersion(), - dep.getDatedSnapshotVersion(), - dep.getRequested().getScope(), - dep.getDepth() - )); + if (!(configuration == null || configuration.isEmpty() || c.getName().equals(configuration))) { + continue; + } + for (ResolvedDependency resolvedDependency : c.getResolved()) { + ResolvedDependency dep = resolvedDependency.findDependency(groupIdPattern, artifactIdPattern); + if(dep ==null) { + continue; + } + if(version != null) { + VersionComparator versionComparator = Semver.validate(version, null).getValue(); + if(versionComparator == null) { + sourceFile = Markup.warn(sourceFile, new IllegalArgumentException("Could not construct a valid version comparator from " + version + ".")); + } else { + if(!versionComparator.isValid(null, dep.getVersion())) { + continue; + } } } + GroupArtifactVersion requestedGav = new GroupArtifactVersion(resolvedDependency.getGroupId(), resolvedDependency.getArtifactId(), resolvedDependency.getVersion()); + GroupArtifactVersion targetGav = new GroupArtifactVersion(dep.getGroupId(), dep.getArtifactId(), dep.getVersion()); + configurationToDirectDependency.compute(c.getName(), (k, v) -> { + if (v == null) { + v = new LinkedHashSet<>(); + } + v.add(requestedGav); + return v; + }); + directDependencyToTargetDependency.compute(requestedGav, (k, v) -> { + if (v == null) { + v = new LinkedHashSet<>(); + } + v.add(targetGav); + return v; + }); + dependenciesInUse.insertRow(ctx, new DependenciesInUse.Row( + projectName, + sourceSetName, + dep.getGroupId(), + dep.getArtifactId(), + dep.getVersion(), + dep.getDatedSnapshotVersion(), + dep.getRequested().getScope(), + dep.getDepth() + )); } } if (directDependencyToTargetDependency.isEmpty()) { diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java index bb2474d85b9..74c9c585a80 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/DependencyInsightTest.java @@ -30,7 +30,7 @@ class DependencyInsightTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec.beforeRecipe(withToolingApi()) - .recipe(new DependencyInsight("com.google.guava", "failureaccess", null)); + .recipe(new DependencyInsight("com.google.guava", "failureaccess", null,null)); } @DocumentExample @@ -70,7 +70,7 @@ void findTransitiveDependency() { @Test void recursive() { rewriteRun( - spec -> spec.recipe(new DependencyInsight("doesnotexist", "doesnotexist", null)), + spec -> spec.recipe(new DependencyInsight("doesnotexist", "doesnotexist", null, null)), buildGradle(""" plugins { id 'java-library' @@ -91,7 +91,7 @@ void recursive() { @Test void pattern() { rewriteRun( - spec -> spec.recipe(new DependencyInsight("*", "jackson-core", null)) + spec -> spec.recipe(new DependencyInsight("*", "jackson-core", null, null)) .dataTable(DependenciesInUse.Row.class, rows -> { assertThat(rows).isNotEmpty(); DependenciesInUse.Row row = rows.get(0); @@ -128,4 +128,40 @@ void pattern() { ) ); } + + @Test + void versionSearch() { + rewriteRun( + spec -> spec.recipe(new DependencyInsight("org.openrewrite", "*", "7.0.0", null)), + buildGradle(""" + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + implementation 'org.openrewrite:rewrite-yaml:7.0.0' + implementation 'org.openrewrite:rewrite-java:8.0.0' + } + """, + """ + plugins { + id 'java-library' + } + + repositories { + mavenCentral() + } + + dependencies { + /*~~(org.openrewrite:rewrite-yaml:7.0.0)~~>*/implementation 'org.openrewrite:rewrite-yaml:7.0.0' + implementation 'org.openrewrite:rewrite-java:8.0.0' + } + """ + ) + ); + } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/DependencyInsight.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/DependencyInsight.java index 2a6e6d64f93..eb05da637b5 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/search/DependencyInsight.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/DependencyInsight.java @@ -22,17 +22,17 @@ import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.marker.JavaProject; import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.marker.Markup; import org.openrewrite.marker.SearchResult; import org.openrewrite.maven.MavenIsoVisitor; import org.openrewrite.maven.table.DependenciesInUse; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.Scope; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; import org.openrewrite.xml.tree.Xml; import java.util.Optional; -import java.util.UUID; - -import static org.openrewrite.Tree.randomId; /** * Find direct and transitive dependencies, marking first order dependencies that @@ -62,6 +62,15 @@ public class DependencyInsight extends Recipe { @Nullable String scope; + @Option(displayName = "Version", + description = "Match only dependencies with the specified version. " + + "Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used." + + "All versions are searched by default.", + example = "1.x", + required = false) + @Nullable + String version; + @Option(displayName = "Only direct", description = "If enabled, transitive dependencies will not be considered. All dependencies are searched by default.", required = false, @@ -69,12 +78,15 @@ public class DependencyInsight extends Recipe { @Nullable Boolean onlyDirect; - UUID searchId = randomId(); - @Override public Validated<Object> validate() { - return super.validate().and(Validated.test("scope", "scope is a valid Maven scope", scope, - s -> Scope.fromName(s) != Scope.Invalid)); + Validated<Object> v = super.validate() + .and(Validated.test("scope", "scope is a valid Maven scope", scope, + s -> Scope.fromName(s) != Scope.Invalid)); + if (version != null) { + v = v.and(Semver.validate(version, null)); + } + return v; } @Override @@ -96,39 +108,51 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { Xml.Tag t = super.visitTag(tag, ctx); - - if (isDependencyTag()) { - ResolvedDependency dependency = findDependency(t, aScope); - if (dependency != null) { - ResolvedDependency match = dependency.findDependency(groupIdPattern, artifactIdPattern); - if (match != null) { - if (match == dependency) { - t = SearchResult.found(t); - } else if (Boolean.TRUE.equals(onlyDirect)) { - return t; - } else { - t = SearchResult.found(t, match.getGav().toString()); - } - - Optional<JavaProject> javaProject = getCursor().firstEnclosingOrThrow(Xml.Document.class).getMarkers() - .findFirst(JavaProject.class); - Optional<JavaSourceSet> javaSourceSet = getCursor().firstEnclosingOrThrow(Xml.Document.class).getMarkers() - .findFirst(JavaSourceSet.class); - - dependenciesInUse.insertRow(ctx, new DependenciesInUse.Row( - javaProject.map(JavaProject::getProjectName).orElse(""), - javaSourceSet.map(JavaSourceSet::getName).orElse("main"), - match.getGroupId(), - match.getArtifactId(), - match.getVersion(), - match.getDatedSnapshotVersion(), - StringUtils.isBlank(match.getRequested().getScope()) ? "compile" : - match.getRequested().getScope(), - match.getDepth() - )); + if(!isDependencyTag()) { + return t; + } + ResolvedDependency dependency = findDependency(t, aScope); + if(dependency == null) { + return t; + } + ResolvedDependency match = dependency.findDependency(groupIdPattern, artifactIdPattern); + if(match == null) { + return t; + } + if(version != null) { + VersionComparator versionComparator = Semver.validate(version, null).getValue(); + if(versionComparator == null) { + t = Markup.warn(t, new IllegalArgumentException("Could not construct a valid version comparator from " + version + ".")); + } else { + if(!versionComparator.isValid(null, match.getVersion())) { + return t; } } } + if (match == dependency) { + t = SearchResult.found(t); + } else if (Boolean.TRUE.equals(onlyDirect)) { + return t; + } else { + t = SearchResult.found(t, match.getGav().toString()); + } + + Optional<JavaProject> javaProject = getCursor().firstEnclosingOrThrow(Xml.Document.class).getMarkers() + .findFirst(JavaProject.class); + Optional<JavaSourceSet> javaSourceSet = getCursor().firstEnclosingOrThrow(Xml.Document.class).getMarkers() + .findFirst(JavaSourceSet.class); + + dependenciesInUse.insertRow(ctx, new DependenciesInUse.Row( + javaProject.map(JavaProject::getProjectName).orElse(""), + javaSourceSet.map(JavaSourceSet::getName).orElse("main"), + match.getGroupId(), + match.getArtifactId(), + match.getVersion(), + match.getDatedSnapshotVersion(), + StringUtils.isBlank(match.getRequested().getScope()) ? "compile" : + match.getRequested().getScope(), + match.getDepth() + )); return t; } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/DoesNotIncludeDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/DoesNotIncludeDependency.java index a68dc553b60..44dc6eea4c4 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/search/DoesNotIncludeDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/DoesNotIncludeDependency.java @@ -80,11 +80,9 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { private TreeVisitor<?, ExecutionContext>[] dependencyInsightVisitors() { if (scope == null) { return new TreeVisitor[] { - // anything in compile/runtime scope will also be in test classpath; no need to check individually - new DependencyInsight(groupId, artifactId, Scope.Test.toString(), onlyDirect).getVisitor(), - new DependencyInsight(groupId, artifactId, Scope.Provided.toString(), onlyDirect).getVisitor() + new DependencyInsight(groupId, artifactId, null, null, onlyDirect).getVisitor(), }; } - return new TreeVisitor[] { new DependencyInsight(groupId, artifactId, scope, onlyDirect).getVisitor() }; + return new TreeVisitor[] { new DependencyInsight(groupId, artifactId, scope, null, onlyDirect).getVisitor() }; } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/search/DependencyInsightTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/search/DependencyInsightTest.java index 7b302b8e9b9..984a9632102 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/search/DependencyInsightTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/search/DependencyInsightTest.java @@ -28,7 +28,7 @@ class DependencyInsightTest implements RewriteTest { @Test void doesNotMatchTestScope() { rewriteRun( - spec -> spec.recipe(new DependencyInsight("*guava*", "*", "compile", null)), + spec -> spec.recipe(new DependencyInsight("*guava*", "*", "compile", null, null)), pomXml( """ <project> @@ -53,7 +53,7 @@ void doesNotMatchTestScope() { @Test void findDependency() { rewriteRun( - spec -> spec.recipe(new DependencyInsight("*guava*", "*", "compile", null)), + spec -> spec.recipe(new DependencyInsight("*guava*", "*", "compile", null, null)), pomXml( """ <project> @@ -90,7 +90,7 @@ void findDependency() { @Test void findDependencyTransitively() { rewriteRun( - spec -> spec.recipe(new DependencyInsight("*", "*simpleclient*", "compile", null)), + spec -> spec.recipe(new DependencyInsight("*", "*simpleclient*", "compile", null, null)), pomXml( """ <project> @@ -127,7 +127,7 @@ void findDependencyTransitively() { @Test void onlyDirect() { rewriteRun( - spec -> spec.recipe(new DependencyInsight("*", "*simpleclient*", "compile", true)), + spec -> spec.recipe(new DependencyInsight("*", "*simpleclient*", "compile", null, true)), pomXml( """ <project> @@ -146,4 +146,52 @@ void onlyDirect() { ) ); } + + + @Test + void versionSelector() { + rewriteRun( + spec -> spec.recipe(new DependencyInsight("org.openrewrite", "*", "compile", "8.0.0", true)), + pomXml( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <dependencies> + <dependency> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-java</artifactId> + <version>8.0.0</version> + </dependency> + <dependency> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-yaml</artifactId> + <version>7.0.0</version> + </dependency> + </dependencies> + </project> + """, + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <dependencies> + <!--~~>--><dependency> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-java</artifactId> + <version>8.0.0</version> + </dependency> + <dependency> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite-yaml</artifactId> + <version>7.0.0</version> + </dependency> + </dependencies> + </project> + """ + ) + ); + } } From d24a745d5f45421ef5da18bbd759e11817154559 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Thu, 9 Nov 2023 15:27:19 -0800 Subject: [PATCH 405/447] After applying a mirror to a maven repository, do not assume that the new repository is known to exist. --- .../org/openrewrite/maven/tree/MavenRepositoryMirror.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java index 7742bb98b34..d543ac7ba4a 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepositoryMirror.java @@ -42,7 +42,7 @@ public class MavenRepositoryMirror { * repo,repo1 = repo or repo1 * !repo1 = everything except repo1 * <p> - * See: https://maven.apache.org/guides/mini/guide-mirror-settings.html#advanced-mirror-specification + * See: <a href="https://maven.apache.org/guides/mini/guide-mirror-settings.html#advanced-mirror-specification">Maven's Mirrors documentation</a> */ @Nullable String mirrorOf; @@ -107,10 +107,13 @@ public MavenRepository apply(MavenRepository repo) { if (repo.getUri().equals(url) && Objects.equals(id, repo.getId()) || !matches(repo)) { return repo; } else { - return repo.withUri(url) + MavenRepository repoWithMirror = repo.withUri(url) .withId(id) .withReleases(!Boolean.FALSE.equals(releases) ? "true" : "false") .withSnapshots(!Boolean.FALSE.equals(snapshots) ? "true" : "false"); + // Since the URL has likely changed we cannot assume that the new repository is known to exist + repoWithMirror.setKnownToExist(false); + return repoWithMirror; } } From b9c589a583f4920b68a7d6d5c21395940fe1fb58 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Fri, 10 Nov 2023 09:32:35 +0100 Subject: [PATCH 406/447] fix defaultClasspath for GradleParser (#3679) * fix defaultClasspath for GradleParser * do not modify base builder --- .../org/openrewrite/gradle/GradleParser.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/GradleParser.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/GradleParser.java index d7fb61a8727..b2e95e92f65 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/GradleParser.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/GradleParser.java @@ -44,12 +44,15 @@ public class GradleParser implements Parser { @Override public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path relativeTo, ExecutionContext ctx) { if (buildParser == null) { - if (base.buildscriptClasspath == null && defaultClasspath == null) { - defaultClasspath = loadDefaultClasspath(); - base.buildscriptClasspath = defaultClasspath; + Collection<Path> buildscriptClasspath = base.buildscriptClasspath; + if (buildscriptClasspath == null) { + if (defaultClasspath == null) { + defaultClasspath = loadDefaultClasspath(); + } + buildscriptClasspath = defaultClasspath; } buildParser = GroovyParser.builder(base.groovyParser) - .classpath(base.buildscriptClasspath) + .classpath(buildscriptClasspath) .compilerCustomizers( new DefaultImportsCustomizer(), config -> config.setScriptBaseClass("RewriteGradleProject") @@ -57,12 +60,15 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re .build(); } if (settingsParser == null) { - if (base.settingsClasspath == null && defaultClasspath == null) { - defaultClasspath = loadDefaultClasspath(); - base.settingsClasspath = defaultClasspath; + Collection<Path> settingsClasspath = base.settingsClasspath; + if (settingsClasspath == null) { + if (defaultClasspath == null) { + defaultClasspath = loadDefaultClasspath(); + } + settingsClasspath = defaultClasspath; } settingsParser = GroovyParser.builder(base.groovyParser) - .classpath(base.settingsClasspath) + .classpath(settingsClasspath) .compilerCustomizers( new DefaultImportsCustomizer(), config -> config.setScriptBaseClass("RewriteSettings") From 571e3ed518e29595e3adca0183b14fad52a0036e Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 10 Nov 2023 10:58:43 +0100 Subject: [PATCH 407/447] Add `preconditions` to JSON schema --- rewrite-core/openrewrite.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rewrite-core/openrewrite.json b/rewrite-core/openrewrite.json index 5972e8eadfc..1624cdccae9 100644 --- a/rewrite-core/openrewrite.json +++ b/rewrite-core/openrewrite.json @@ -53,6 +53,9 @@ "type": "boolean", "default": false }, + "preconditions": { + "$ref": "#/$defs/recipeList" + }, "recipeList": { "$ref": "#/$defs/recipeList" } From aa67ab879234096038e164a06e36e8a077176889 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 10 Nov 2023 12:38:16 +0100 Subject: [PATCH 408/447] Fix parsing of space before colon in YAML sequence mappings Fixes: #3680 --- .../main/java/org/openrewrite/yaml/YamlParser.java | 14 +++++++++++++- .../org/openrewrite/yaml/tree/SequenceTest.java | 13 +++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index bd60f386c5b..21865c00593 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -541,7 +541,11 @@ private static class SequenceWithPrefix extends Yaml.Sequence { private String prefix; public SequenceWithPrefix(String prefix, @Nullable String startBracketPrefix, List<Yaml.Sequence.Entry> entries, @Nullable String endBracketPrefix, @Nullable Anchor anchor) { - super(randomId(), Markers.EMPTY, startBracketPrefix, entries, endBracketPrefix, anchor); + this(randomId(), Markers.EMPTY, prefix, startBracketPrefix, entries, endBracketPrefix, anchor); + } + + private SequenceWithPrefix(UUID id, Markers markers, String prefix, @Nullable String startBracketPrefix, List<Yaml.Sequence.Entry> entries, @Nullable String endBracketPrefix, @Nullable Anchor anchor) { + super(id, markers, startBracketPrefix, entries, endBracketPrefix, anchor); this.prefix = prefix; } @@ -550,6 +554,14 @@ public Sequence withPrefix(String prefix) { this.prefix = prefix; return this; } + + @Override + public Sequence withClosingBracketPrefix(@Nullable String closingBracketPrefix) { + if (Objects.equals(closingBracketPrefix, getClosingBracketPrefix())) { + return this; + } + return new SequenceWithPrefix(getId(), getMarkers(), prefix, getOpeningBracketPrefix(), getEntries(), closingBracketPrefix, getAnchor()); + } } private Yaml.Documents unwrapPrefixedMappings(Yaml.Documents y) { diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java index 5c7ed8316f9..34013ddd37e 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java @@ -16,6 +16,7 @@ package org.openrewrite.yaml.tree; import org.junit.jupiter.api.Test; +import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; import static org.assertj.core.api.Assertions.assertThat; @@ -119,6 +120,18 @@ void sequenceOfMixedSequences() { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/3680") + @Test + void spaceBeforeColon() { + rewriteRun( + yaml( + """ + foo : [] + """ + ) + ); + } + @Test void inlineSequenceWithWhitespaceBeforeCommas() { rewriteRun( From aaf1993b05a1f0a375a282656f0ca06c22427fb4 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Fri, 10 Nov 2023 12:39:46 +0100 Subject: [PATCH 409/447] Revert "Fix parsing of space before colon in YAML sequence mappings" This reverts commit aa67ab879234096038e164a06e36e8a077176889. --- .../main/java/org/openrewrite/yaml/YamlParser.java | 14 +------------- .../org/openrewrite/yaml/tree/SequenceTest.java | 13 ------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java index 21865c00593..bd60f386c5b 100644 --- a/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java +++ b/rewrite-yaml/src/main/java/org/openrewrite/yaml/YamlParser.java @@ -541,11 +541,7 @@ private static class SequenceWithPrefix extends Yaml.Sequence { private String prefix; public SequenceWithPrefix(String prefix, @Nullable String startBracketPrefix, List<Yaml.Sequence.Entry> entries, @Nullable String endBracketPrefix, @Nullable Anchor anchor) { - this(randomId(), Markers.EMPTY, prefix, startBracketPrefix, entries, endBracketPrefix, anchor); - } - - private SequenceWithPrefix(UUID id, Markers markers, String prefix, @Nullable String startBracketPrefix, List<Yaml.Sequence.Entry> entries, @Nullable String endBracketPrefix, @Nullable Anchor anchor) { - super(id, markers, startBracketPrefix, entries, endBracketPrefix, anchor); + super(randomId(), Markers.EMPTY, startBracketPrefix, entries, endBracketPrefix, anchor); this.prefix = prefix; } @@ -554,14 +550,6 @@ public Sequence withPrefix(String prefix) { this.prefix = prefix; return this; } - - @Override - public Sequence withClosingBracketPrefix(@Nullable String closingBracketPrefix) { - if (Objects.equals(closingBracketPrefix, getClosingBracketPrefix())) { - return this; - } - return new SequenceWithPrefix(getId(), getMarkers(), prefix, getOpeningBracketPrefix(), getEntries(), closingBracketPrefix, getAnchor()); - } } private Yaml.Documents unwrapPrefixedMappings(Yaml.Documents y) { diff --git a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java index 34013ddd37e..5c7ed8316f9 100644 --- a/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java +++ b/rewrite-yaml/src/test/java/org/openrewrite/yaml/tree/SequenceTest.java @@ -16,7 +16,6 @@ package org.openrewrite.yaml.tree; import org.junit.jupiter.api.Test; -import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; import static org.assertj.core.api.Assertions.assertThat; @@ -120,18 +119,6 @@ void sequenceOfMixedSequences() { ); } - @Issue("https://github.com/openrewrite/rewrite/issues/3680") - @Test - void spaceBeforeColon() { - rewriteRun( - yaml( - """ - foo : [] - """ - ) - ); - } - @Test void inlineSequenceWithWhitespaceBeforeCommas() { rewriteRun( From bdaaa25de1b5d9a8aac9276529e6e025bf3d0fc9 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Fri, 10 Nov 2023 15:59:57 +0100 Subject: [PATCH 410/447] Do not skip proto3 files, as it warns about out of date LSTs (#3681) * Do not skip proto3 files, as it warns about out of date LSTs * Update the _one_ test that we have --- .../org/openrewrite/protobuf/ProtoParser.java | 26 ++++++++++++++----- .../openrewrite/protobuf/ProtoParserTest.java | 10 +++---- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java b/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java index f2a7aae4bb4..bbd419a439c 100644 --- a/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java +++ b/rewrite-protobuf/src/main/java/org/openrewrite/protobuf/ProtoParser.java @@ -17,22 +17,27 @@ import org.antlr.v4.runtime.*; import org.intellij.lang.annotations.Language; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Parser; -import org.openrewrite.*; +import org.openrewrite.SourceFile; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.marker.Markers; import org.openrewrite.protobuf.internal.ProtoParserVisitor; import org.openrewrite.protobuf.internal.grammar.Protobuf2Lexer; import org.openrewrite.protobuf.internal.grammar.Protobuf2Parser; import org.openrewrite.protobuf.tree.Proto; +import org.openrewrite.text.PlainText; import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; import java.nio.file.Path; -import java.util.Objects; import java.util.stream.Stream; +import static org.openrewrite.Tree.randomId; + public class ProtoParser implements Parser { @Override @@ -51,7 +56,18 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat parser.addErrorListener(new ForwardingErrorListener(input.getPath(), ctx)); if (sourceStr.contains("proto3")) { - return null; + // Pending Proto3 support, the best we can do is plain text & not skip files + return new PlainText( + randomId(), + path, + Markers.EMPTY, + is.getCharset().name(), + is.isCharsetBomMarked(), + input.getFileAttributes(), + null, + sourceStr, + null + ); } Proto.Document document = new ProtoParserVisitor( @@ -67,9 +83,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sourceFiles, @Nullable Pat ctx.getOnError().accept(t); return ParseError.build(this, input, relativeTo, ctx, t); } - }) - // filter out the nulls produced for `proto3` sources - .filter(Objects::nonNull); + }); } @Override diff --git a/rewrite-protobuf/src/test/java/org/openrewrite/protobuf/ProtoParserTest.java b/rewrite-protobuf/src/test/java/org/openrewrite/protobuf/ProtoParserTest.java index 2c2c8cc867c..0a078ee4f3f 100644 --- a/rewrite-protobuf/src/test/java/org/openrewrite/protobuf/ProtoParserTest.java +++ b/rewrite-protobuf/src/test/java/org/openrewrite/protobuf/ProtoParserTest.java @@ -18,18 +18,16 @@ import org.junit.jupiter.api.Test; import org.openrewrite.SourceFile; import org.openrewrite.test.RewriteTest; +import org.openrewrite.text.PlainText; import java.util.List; -import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; -public class ProtoParserTest implements RewriteTest { - +class ProtoParserTest implements RewriteTest { @Test void noNullsForProto3Files() { - List<SourceFile> sources = ProtoParser.builder().build().parse("syntax = \"proto3\";") - .collect(Collectors.toList()); - assertThat(sources).isEmpty(); + List<SourceFile> sources = ProtoParser.builder().build().parse("syntax = \"proto3\";").toList(); + assertThat(sources).singleElement().isInstanceOf(PlainText.class); } } From eb9cddacc2e03662b974d78eb4740a5c6bb8a0c6 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sat, 11 Nov 2023 16:00:48 +0100 Subject: [PATCH 411/447] Another fix for `TypeUtils#isAssignable()` A type like `List<? extends String>` is not assignable to e.g. `List<String>` (without cast). --- .../src/main/java/org/openrewrite/java/tree/TypeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index a0a29a1a338..cb0142f8d73 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -213,7 +213,7 @@ public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType f return true; } } - return isAssignableTo(toFq.getFullyQualifiedName(), from); + return !(from instanceof JavaType.GenericTypeVariable) && isAssignableTo(toFq.getFullyQualifiedName(), from); } else if (to instanceof JavaType.GenericTypeVariable) { JavaType.GenericTypeVariable toGeneric = (JavaType.GenericTypeVariable) to; List<JavaType> toBounds = toGeneric.getBounds(); From 42ebe52f57f9a1c7affd6eb163f16da9c1fdec0e Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Mon, 13 Nov 2023 09:27:01 +0100 Subject: [PATCH 412/447] Align `SpacesVisitor#postVisit()` with other formatting visitors Using the root cursor is typically a bad idea, as it is globally shared in a recipe run. --- .../main/java/org/openrewrite/java/format/SpacesVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java index 323813817a6..2328a6736b9 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/SpacesVisitor.java @@ -1076,7 +1076,7 @@ public J.TypeParameter visitTypeParameter(J.TypeParameter typeParam, P p) { @Override public J postVisit(J tree, P p) { if (stopAfter != null && stopAfter.isScope(tree)) { - getCursor().getRoot().putMessage("stop", true); + getCursor().putMessageOnFirstEnclosing(JavaSourceFile.class, "stop", true); } return super.postVisit(tree, p); } From 368b25366c3415d3f938b1e7a3b8096d4477ae99 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Mon, 13 Nov 2023 10:51:34 +0100 Subject: [PATCH 413/447] CreateTextFile should override files of different type (#3682) * CreateTextFile should override files of different type * Address review comments to override any file --- .../org/openrewrite/text/CreateTextFile.java | 28 +++++++++++++----- rewrite-test/build.gradle.kts | 2 ++ .../openrewrite/text/CreateTextFileTest.java | 29 +++++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/CreateTextFile.java b/rewrite-core/src/main/java/org/openrewrite/text/CreateTextFile.java index 2e3ecba140b..f218d03aba9 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/CreateTextFile.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/CreateTextFile.java @@ -71,7 +71,7 @@ public TreeVisitor<?, ExecutionContext> getScanner(AtomicBoolean shouldCreate) { @Override public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { SourceFile sourceFile = (SourceFile) requireNonNull(tree); - if (path.toString().equals(sourceFile.getSourcePath().toString())) { + if (path.equals(sourceFile.getSourcePath())) { shouldCreate.set(false); } return sourceFile; @@ -81,7 +81,7 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { @Override public Collection<SourceFile> generate(AtomicBoolean shouldCreate, ExecutionContext ctx) { - if(shouldCreate.get()) { + if (shouldCreate.get()) { return PlainTextParser.builder().build().parse(fileContents) .map(brandNewFile -> (SourceFile) brandNewFile.withSourcePath(Paths.get(relativeFileName))) .collect(Collectors.toList()); @@ -92,13 +92,27 @@ public Collection<SourceFile> generate(AtomicBoolean shouldCreate, ExecutionCont @Override public TreeVisitor<?, ExecutionContext> getVisitor(AtomicBoolean created) { Path path = Paths.get(relativeFileName); - return new PlainTextVisitor<ExecutionContext>() { + return new TreeVisitor<SourceFile, ExecutionContext>() { @Override - public PlainText visitText(PlainText text, ExecutionContext ctx) { - if ((created.get() || Boolean.TRUE.equals(overwriteExisting)) && path.toString().equals(text.getSourcePath().toString())) { - return text.withText(fileContents); + public SourceFile visit(@Nullable Tree tree, ExecutionContext executionContext) { + SourceFile sourceFile = (SourceFile) requireNonNull(tree); + if ((created.get() || Boolean.TRUE.equals(overwriteExisting)) && path.equals(sourceFile.getSourcePath())) { + if (sourceFile instanceof PlainText) { + return ((PlainText) sourceFile).withText(fileContents); + } + PlainText plainText = PlainText.builder() + .id(sourceFile.getId()) + .sourcePath(sourceFile.getSourcePath()) + .fileAttributes(sourceFile.getFileAttributes()) + .charsetBomMarked(sourceFile.isCharsetBomMarked()) + .text(fileContents) + .build(); + if (sourceFile.getCharset() != null) { + return plainText.withCharset(sourceFile.getCharset()); + } + return plainText; } - return text; + return sourceFile; } }; } diff --git a/rewrite-test/build.gradle.kts b/rewrite-test/build.gradle.kts index e7f48222158..dc57aa0ed8a 100644 --- a/rewrite-test/build.gradle.kts +++ b/rewrite-test/build.gradle.kts @@ -12,4 +12,6 @@ dependencies { implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv") implementation("org.slf4j:slf4j-api:1.7.36") implementation("org.eclipse.jgit:org.eclipse.jgit:5.13.+") + + testImplementation(project(":rewrite-groovy")) } diff --git a/rewrite-test/src/test/java/org/openrewrite/text/CreateTextFileTest.java b/rewrite-test/src/test/java/org/openrewrite/text/CreateTextFileTest.java index 4205b722046..a3dfe9a3b08 100644 --- a/rewrite-test/src/test/java/org/openrewrite/text/CreateTextFileTest.java +++ b/rewrite-test/src/test/java/org/openrewrite/text/CreateTextFileTest.java @@ -17,8 +17,10 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; import org.openrewrite.test.RewriteTest; +import static org.openrewrite.groovy.Assertions.groovy; import static org.openrewrite.test.SourceSpecs.text; class CreateTextFileTest implements RewriteTest { @@ -97,4 +99,31 @@ void shouldAddAnotherFile() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite-jenkins/issues/52") + void shouldOverrideDifferentSourceFileType() { + String after = """ + /* + See the documentation for more options: + https://github.com/jenkins-infra/pipeline-library/ + */ + buildPlugin( + useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests + configurations: [ + [platform: 'linux', jdk: 21], + [platform: 'windows', jdk: 17], + ])"""; + rewriteRun( + spec -> spec.recipe(new CreateTextFile(after, "Jenkinsfile", true)), + groovy( + """ + #!groovy + buildPlugin() + """, + after, + spec -> spec.path("Jenkinsfile") + ) + ); + } } From 171d4b12821351eee82c5ef85ddb56f5b9af8a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= <56278322+fabapp2@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:13:51 +0100 Subject: [PATCH 414/447] Streamline local Maven repo URI closes #3671 (#3676) * Streamline local Maven repo URI closes #3671 * Apply suggestions from code review * Add missing import and rename test class * Delete MavenRepositoryTest --------- Co-authored-by: Tim te Beek <timtebeek@gmail.com> Co-authored-by: Tim te Beek <tim@moderne.io> --- .../main/java/org/openrewrite/maven/tree/MavenRepository.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepository.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepository.java index 20140b69464..a00690976d2 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepository.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenRepository.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.Serializable; import java.net.URI; +import java.nio.file.Paths; @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@ref") @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @@ -37,7 +38,7 @@ public class MavenRepository implements Serializable { public static final MavenRepository MAVEN_LOCAL_USER_NEUTRAL = new MavenRepository("local", new File("~/.m2/repository").toString(), "true", "true", true, null, null, false); - public static final MavenRepository MAVEN_LOCAL_DEFAULT = new MavenRepository("local", new File(System.getProperty("user.home") + "/.m2/repository").toURI().toString(), "true", "true", true, null, null, false); + public static final MavenRepository MAVEN_LOCAL_DEFAULT = new MavenRepository("local", Paths.get(System.getProperty("user.home"), ".m2", "repository").toUri().toString(), "true", "true", true, null, null, false); public static final MavenRepository MAVEN_CENTRAL = new MavenRepository("central", "https://repo.maven.apache.org/maven2", "true", "false", true, null, null, true); @EqualsAndHashCode.Include From d925bc21084535d75d9c52f4ea0ba01dc1d379ee Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 14 Nov 2023 12:23:48 +0100 Subject: [PATCH 415/447] Make `Assertions#validateTypes()` public `FindMissingTypes#findMissingTypes()` should possibly build on the service model to allow other languages to supply their own type validations. --- .../src/main/java/org/openrewrite/java/Assertions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java index b4613d39548..8c890606e13 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java @@ -52,7 +52,7 @@ static void customizeExecutionContext(ExecutionContext ctx) { } } - static SourceFile validateTypes(SourceFile source, TypeValidation typeValidation) { + public static SourceFile validateTypes(SourceFile source, TypeValidation typeValidation) { if (source instanceof JavaSourceFile) { assertValidTypes(typeValidation, (JavaSourceFile) source); } @@ -83,7 +83,7 @@ private static void assertValidTypes(TypeValidation typeValidation, J sf) { }) .collect(Collectors.toList()); if (!missingTypeResults.isEmpty()) { - throw new IllegalStateException("AST contains missing or invalid type information\n" + missingTypeResults.stream().map(v -> v.getPath() + "\n" + v.getPrintedTree()) + throw new IllegalStateException("LST contains missing or invalid type information\n" + missingTypeResults.stream().map(v -> v.getPath() + "\n" + v.getPrintedTree()) .collect(Collectors.joining("\n\n"))); } } From 1897320b2d8d41dbcfe8e887175772376a1d2106 Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Tue, 14 Nov 2023 12:13:20 -0800 Subject: [PATCH 416/447] Update description of UpdateMovedRecipe --- .../java/org/openrewrite/java/recipes/UpdateMovedRecipe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/recipes/UpdateMovedRecipe.java b/rewrite-java/src/main/java/org/openrewrite/java/recipes/UpdateMovedRecipe.java index 487205709d4..94f88eef986 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/recipes/UpdateMovedRecipe.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/recipes/UpdateMovedRecipe.java @@ -43,7 +43,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "Update moved package recipe."; + return "If a recipe moved between packages, update the code reference places, declarative recipes, and `activeRecipes` in ppm.xml."; } @Override From 33c985f69d2f4552a952d49f108dc814c5e34743 Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Tue, 14 Nov 2023 14:22:57 -0600 Subject: [PATCH 417/447] AddProperty accumulater should be public as it is visible outside of package scope --- .../src/main/java/org/openrewrite/gradle/AddProperty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddProperty.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddProperty.java index 4434da03c6b..08cde786594 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddProperty.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddProperty.java @@ -66,7 +66,7 @@ public String getDescription() { return "Add a property to the `gradle.properties` file."; } - static class NeedsProperty { + public static class NeedsProperty { boolean isGradleProject; boolean hasGradleProperties; } From ac5cdc969b590927f0fec10dda802a15c760890e Mon Sep 17 00:00:00 2001 From: Peter Streef <peter@moderne.io> Date: Wed, 15 Nov 2023 15:35:37 +0100 Subject: [PATCH 418/447] Maven local could be in a different location (#3690) * Maven local could be in a different location * Check if file is found in default maven local * Fix skip, add comment * formatting * formatting Co-authored-by: Tim te Beek <tim@moderne.io> * add equals case with ~/.m2/repository --------- Co-authored-by: Tim te Beek <tim@moderne.io> --- .../org/openrewrite/maven/internal/MavenPomDownloader.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index e6de85cb102..dcbce940f84 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -526,6 +526,11 @@ public Pom download(GroupArtifactVersion gav, if (!f.exists()) { continue; } + String fullDefaultMavenLocalUri = MavenRepository.MAVEN_LOCAL_USER_NEUTRAL.getUri().replace("~", System.getProperty("user.home")); + if (!repo.getUri().equals(MavenRepository.MAVEN_LOCAL_USER_NEUTRAL.getUri()) && !repo.getUri().startsWith(fullDefaultMavenLocalUri)) { + // Non-default local Maven dependencies can not be shared between users, so we skip the repo + continue; + } try (FileInputStream fis = new FileInputStream(f)) { RawPom rawPom = RawPom.parse(fis, Objects.equals(versionMaybeDatedSnapshot, gav.getVersion()) ? null : versionMaybeDatedSnapshot); From 851c3825a51005c196cefdb3d9a33ce5b3fde19f Mon Sep 17 00:00:00 2001 From: Sam Snyder <sam@moderne.io> Date: Wed, 15 Nov 2023 15:03:52 -0600 Subject: [PATCH 419/447] Fix FindPlugin sometimes reporting a plugin not having a version number when one is available --- .../gradle/search/FindPlugins.java | 22 ++++++++++++++----- .../gradle/search/FindPluginsTest.java | 2 ++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindPlugins.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindPlugins.java index b4eb7439edf..a9738aa87ed 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindPlugins.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindPlugins.java @@ -29,10 +29,10 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; @Value @EqualsAndHashCode(callSuper = true) @@ -60,7 +60,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { if (pluginMatcher.matches(method)) { if (method.getArguments().get(0) instanceof J.Literal && - pluginId.equals(((J.Literal) method.getArguments().get(0)).getValue())) { + pluginId.equals(((J.Literal) method.getArguments().get(0)).getValue())) { return SearchResult.found(method); } } @@ -86,7 +86,7 @@ public static List<GradlePlugin> find(J j, String pluginIdPattern) { MethodMatcher idMatcher = new MethodMatcher("PluginSpec id(..)", false); MethodMatcher versionMatcher = new MethodMatcher("Plugin version(..)", false); - return plugins.stream().flatMap(plugin -> { + List<GradlePlugin> pluginsWithVersion = plugins.stream().flatMap(plugin -> { if (versionMatcher.matches(plugin) && idMatcher.matches(plugin.getSelect())) { return Stream.of(new GradlePlugin( plugin, @@ -94,7 +94,12 @@ public static List<GradlePlugin> find(J j, String pluginIdPattern) { .getArguments().get(0)).getValue()).toString(), requireNonNull(((J.Literal) plugin.getArguments().get(0)).getValue()).toString() )); - } else if (idMatcher.matches(plugin)) { + } + return Stream.empty(); + }).collect(toList()); + List<GradlePlugin> pluginsWithoutVersion = plugins.stream().flatMap(plugin -> { + if (idMatcher.matches(plugin) && pluginsWithVersion.stream() + .noneMatch(it -> it.getPluginId().equals(plugin.getSimpleName()))) { return Stream.of(new GradlePlugin( plugin, requireNonNull(((J.Literal) requireNonNull(plugin) @@ -102,7 +107,12 @@ public static List<GradlePlugin> find(J j, String pluginIdPattern) { null )); } - return Stream.<GradlePlugin>empty(); - }).collect(Collectors.toList()); + return Stream.empty(); + }).collect(toList()); + + List<GradlePlugin> result = new ArrayList<>(pluginsWithVersion.size() + pluginsWithoutVersion.size()); + result.addAll(pluginsWithVersion); + result.addAll(pluginsWithoutVersion); + return result; } } diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindPluginsTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindPluginsTest.java index 8ce740bbd32..3b15478858b 100644 --- a/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindPluginsTest.java +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/search/FindPluginsTest.java @@ -36,11 +36,13 @@ void findPlugin() { buildGradle( """ plugins { + id 'com.jfrog.bintray' id 'com.jfrog.bintray' version '1.8.5' } """, """ plugins { + /*~~>*/id 'com.jfrog.bintray' /*~~>*/id 'com.jfrog.bintray' version '1.8.5' } """, From 9538741da818afb35ce0cf0f57976a1775a78058 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Wed, 15 Nov 2023 22:33:02 +0100 Subject: [PATCH 420/447] do not overwrite the repo if it's not the default local one (#3691) * do not overwrite the repo if it's not the default local one * Simplify check and add test * remove duplicated createDirectories --------- Co-authored-by: Peter Streef <p.streef@gmail.com> --- .../maven/internal/MavenPomDownloader.java | 11 +++---- .../internal/MavenPomDownloaderTest.java | 33 +++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index dcbce940f84..8feca1d100a 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -526,11 +526,6 @@ public Pom download(GroupArtifactVersion gav, if (!f.exists()) { continue; } - String fullDefaultMavenLocalUri = MavenRepository.MAVEN_LOCAL_USER_NEUTRAL.getUri().replace("~", System.getProperty("user.home")); - if (!repo.getUri().equals(MavenRepository.MAVEN_LOCAL_USER_NEUTRAL.getUri()) && !repo.getUri().startsWith(fullDefaultMavenLocalUri)) { - // Non-default local Maven dependencies can not be shared between users, so we skip the repo - continue; - } try (FileInputStream fis = new FileInputStream(f)) { RawPom rawPom = RawPom.parse(fis, Objects.equals(versionMaybeDatedSnapshot, gav.getVersion()) ? null : versionMaybeDatedSnapshot); @@ -544,8 +539,10 @@ public Pom download(GroupArtifactVersion gav, } } - // so that the repository path is the same regardless of username - pom = pom.withRepository(MavenRepository.MAVEN_LOCAL_USER_NEUTRAL); + if (repo.getUri().equals(MavenRepository.MAVEN_LOCAL_DEFAULT.getUri())) { + // so that the repository path is the same regardless of username + pom = pom.withRepository(MavenRepository.MAVEN_LOCAL_USER_NEUTRAL); + } if (!Objects.equals(versionMaybeDatedSnapshot, pom.getVersion())) { pom = pom.withGav(pom.getGav().withDatedSnapshotVersion(versionMaybeDatedSnapshot)); diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java index 883d5e13ab7..5453a7f0b92 100755 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/MavenPomDownloaderTest.java @@ -32,6 +32,7 @@ import org.openrewrite.Issue; import org.openrewrite.ipc.http.HttpUrlConnectionSender; import org.openrewrite.maven.MavenDownloadingException; +import org.openrewrite.maven.MavenExecutionContextView; import org.openrewrite.maven.MavenParser; import org.openrewrite.maven.tree.GroupArtifact; import org.openrewrite.maven.tree.GroupArtifactVersion; @@ -41,6 +42,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; import java.util.Arrays; import java.util.List; @@ -550,4 +552,35 @@ void skipsLocalInvalidArtifactsEmptyJar(@TempDir Path localRepository) throws IO .download(new GroupArtifactVersion("com.bad", "bad-artifact", "1"), null, null, List.of(mavenLocal))); } + @Test + void doNotRenameRepoForCustomMavenLocal(@TempDir Path tempDir) throws MavenDownloadingException, IOException { + GroupArtifactVersion gav = createArtifact(tempDir); + MavenExecutionContextView.view(ctx).setLocalRepository(MavenRepository.MAVEN_LOCAL_DEFAULT.withUri(tempDir.toUri().toString())); + var downloader = new MavenPomDownloader(emptyMap(), ctx); + + var result = downloader.download(gav, null, null, List.of()); + assertThat(result.getRepository().getUri()).startsWith(tempDir.toUri().toString()); + } + + private static GroupArtifactVersion createArtifact(Path repository) throws IOException { + Path target = repository.resolve(Paths.get("org", "openrewrite", "rewrite", "1.0.0")); + Path pom = target.resolve("rewrite-1.0.0.pom"); + Path jar = target.resolve("rewrite-1.0.0.jar"); + Files.createDirectories(target); + Files.createFile(pom); + Files.createFile(jar); + + Files.write(pom, + //language=xml + """ + <project> + <groupId>org.openrewrite</groupId> + <artifactId>rewrite</artifactId> + <version>1.0.0</version> + </project> + """.getBytes()); + Files.write(jar, "I'm a jar".getBytes()); // empty jars get ignored + return new GroupArtifactVersion("org.openrewrite", "rewrite", "1.0.0"); + } + } From efd3214f6f3317a8a59d64821f30211ac6d9993e Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Thu, 16 Nov 2023 08:13:07 +0100 Subject: [PATCH 421/447] Add `ImportService#shortenFullyQualifiedTypeReferencesIn()` (#3687) * Add `ImportService#shortenFullyQualifiedTypeReferencesIn()` Extends `ImportService` with `shortenFullyQualifiedTypeReferencesIn()` so that other languages extending `J` can provide a customized implementation. * Add some Javadoc --- ...ortenFullyQualifiedTypeReferencesTest.java | 5 ++-- .../ShortenFullyQualifiedTypeReferences.java | 27 ++++++++++++++++--- .../java/service/ImportService.java | 11 ++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java index 0c58a549d59..e7cbf2cff2e 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferencesTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.ExecutionContext; +import org.openrewrite.java.service.ImportService; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.test.RecipeSpec; @@ -228,7 +229,7 @@ void visitSubtreeOnly() { @SuppressWarnings("DataFlowIssue") public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { if (method.getSimpleName().equals("m1")) { - return (J.MethodDeclaration) new ShortenFullyQualifiedTypeReferences().getVisitor().visit(method, ctx, getCursor().getParent()); + return (J.MethodDeclaration) ShortenFullyQualifiedTypeReferences.modifyOnly(method).visit(method, ctx, getCursor().getParent()); } return super.visitMethodDeclaration(method, ctx); } @@ -429,7 +430,7 @@ void subtree() { @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { if (method.getSimpleName().equals("m1")) { - doAfterVisit(ShortenFullyQualifiedTypeReferences.modifyOnly(method)); + doAfterVisit(service(ImportService.class).shortenFullyQualifiedTypeReferencesIn(method)); } return super.visitMethodDeclaration(method, ctx); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java b/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java index c2ba78b55ca..f6361e80027 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ShortenFullyQualifiedTypeReferences.java @@ -19,6 +19,7 @@ import org.openrewrite.internal.lang.NonNull; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.DefaultJavaTypeSignatureBuilder; +import org.openrewrite.java.service.ImportService; import org.openrewrite.java.tree.*; import java.time.Duration; @@ -47,11 +48,31 @@ public String getDescription() { } @Override - public TreeVisitor<?, ExecutionContext> getVisitor() { - return getVisitor(null); + public JavaVisitor<ExecutionContext> getVisitor() { + // This wrapper is necessary so that the "correct" implementation is used when this recipe is used declaratively + return new JavaVisitor<ExecutionContext>() { + @Override + public @Nullable J visit(@Nullable Tree tree, ExecutionContext ctx) { + if (tree instanceof JavaSourceFile) { + return ((JavaSourceFile) tree).service(ImportService.class).shortenFullyQualifiedTypeReferencesIn((J) tree).visit(tree, ctx); + } + return (J) tree; + } + }; } - public static <J2 extends J> TreeVisitor<J, ExecutionContext> modifyOnly(J2 subtree) { + /** + * Returns a visitor which replaces all fully qualified references in the given subtree with simple names and adds + * corresponding import statements. + * <p> + * For compatibility with other Java-based languages it is recommended to use this as a service via + * {@link ImportService#shortenFullyQualifiedTypeReferencesIn(J)}, as that will dispatch to the correct + * implementation for the language. + * + * @see ImportService#shortenFullyQualifiedTypeReferencesIn(J) + * @see JavaVisitor#service(Class) + */ + public static <J2 extends J> JavaVisitor<ExecutionContext> modifyOnly(J2 subtree) { return getVisitor(subtree); } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java b/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java index c2a8a2d42ac..7ab5bd5e2ff 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/service/ImportService.java @@ -15,10 +15,13 @@ */ package org.openrewrite.java.service; +import org.openrewrite.ExecutionContext; import org.openrewrite.Incubating; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.AddImport; import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.ShortenFullyQualifiedTypeReferences; +import org.openrewrite.java.tree.J; @Incubating(since = "8.2.0") public class ImportService { @@ -30,4 +33,12 @@ public <P> JavaVisitor<P> addImportVisitor(@Nullable String packageName, boolean onlyIfReferenced) { return new AddImport<>(packageName, typeName, member, alias, onlyIfReferenced); } + + public <J2 extends J> JavaVisitor<ExecutionContext> shortenAllFullyQualifiedTypeReferences() { + return new ShortenFullyQualifiedTypeReferences().getVisitor(); + } + + public <J2 extends J> JavaVisitor<ExecutionContext> shortenFullyQualifiedTypeReferencesIn(J2 subtree) { + return ShortenFullyQualifiedTypeReferences.modifyOnly(subtree); + } } From 42a5017a373f518549337bb5c6ad628e3afeae6a Mon Sep 17 00:00:00 2001 From: Mike Solomon <mike@moderne.io> Date: Thu, 16 Nov 2023 08:51:12 -0800 Subject: [PATCH 422/447] Update UpdateMovedRecipe.java (#3697) Typo fix --- .../java/org/openrewrite/java/recipes/UpdateMovedRecipe.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/recipes/UpdateMovedRecipe.java b/rewrite-java/src/main/java/org/openrewrite/java/recipes/UpdateMovedRecipe.java index 94f88eef986..d512278df09 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/recipes/UpdateMovedRecipe.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/recipes/UpdateMovedRecipe.java @@ -43,7 +43,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "If a recipe moved between packages, update the code reference places, declarative recipes, and `activeRecipes` in ppm.xml."; + return "If a recipe moved between packages, update the code reference places, declarative recipes, and `activeRecipes` in pom.xml."; } @Override @@ -54,4 +54,4 @@ public List<Recipe> getRecipeList() { new UpdateMovedRecipeXml(oldRecipeFullyQualifiedClassName, newRecipeFullyQualifiedClassName) ); } -} \ No newline at end of file +} From 5d04f6429d1f76230dcb6955d8d362a1774951a9 Mon Sep 17 00:00:00 2001 From: Kun Li <122563761+kunli2@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:41:46 -0800 Subject: [PATCH 423/447] Add modifiers to TypeParameter for kotlin (#3698) --- .../java/org/openrewrite/groovy/GroovyParserVisitor.java | 2 +- .../java/isolated/ReloadableJava11JavadocVisitor.java | 1 + .../java/isolated/ReloadableJava11ParserVisitor.java | 2 +- .../java/isolated/ReloadableJava17JavadocVisitor.java | 1 + .../java/isolated/ReloadableJava17ParserVisitor.java | 2 +- .../java/isolated/ReloadableJava21JavadocVisitor.java | 1 + .../java/isolated/ReloadableJava21ParserVisitor.java | 2 +- .../org/openrewrite/java/ReloadableJava8JavadocVisitor.java | 1 + .../org/openrewrite/java/ReloadableJava8ParserVisitor.java | 2 +- rewrite-java/src/main/java/org/openrewrite/java/tree/J.java | 6 +++++- 10 files changed, 14 insertions(+), 6 deletions(-) diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index 989938b26db..521452b8234 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -2433,7 +2433,7 @@ private J.TypeParameter visitTypeParameter(GenericsType genericType) { .withAfter(EMPTY)); bounds = JContainer.build(boundsPrefix, convertedBounds, Markers.EMPTY); } - return new J.TypeParameter(randomId(), prefix, Markers.EMPTY, emptyList(), name, bounds); + return new J.TypeParameter(randomId(), prefix, Markers.EMPTY, emptyList(), emptyList(), name, bounds); } private J.Wildcard visitWildcard(GenericsType genericType) { diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java index 26718b5b11d..f4bd273dc0d 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11JavadocVisitor.java @@ -531,6 +531,7 @@ public Tree visitParam(ParamTree node, List<Javadoc> body) { Space.EMPTY, Markers.EMPTY, emptyList(), + emptyList(), visitIdentifier(node.getName(), whitespaceBefore()).withPrefix(namePrefix), null ); 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 c48da9444b9..9bf6dfdeec5 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 @@ -1248,7 +1248,7 @@ public J visitTypeParameter(TypeParameterTree node, Space fmt) { JContainer.build(sourceBefore("extends"), convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY); - return new J.TypeParameter(randomId(), fmt, Markers.EMPTY, annotations, name, bounds); + return new J.TypeParameter(randomId(), fmt, Markers.EMPTY, annotations, emptyList(), name, bounds); } private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) { diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java index aa78dbc4194..6a09a47cd85 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17JavadocVisitor.java @@ -534,6 +534,7 @@ public Tree visitParam(ParamTree node, List<Javadoc> body) { Space.EMPTY, Markers.EMPTY, emptyList(), + emptyList(), visitIdentifier(node.getName(), whitespaceBefore()).withPrefix(namePrefix), null ); 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 778dec66d3a..0d4c32a57a2 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 @@ -1323,7 +1323,7 @@ public J visitTypeParameter(TypeParameterTree node, Space fmt) { JContainer.build(sourceBefore("extends"), convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY); - return new J.TypeParameter(randomId(), fmt, Markers.EMPTY, annotations, name, bounds); + return new J.TypeParameter(randomId(), fmt, Markers.EMPTY, annotations, emptyList(), name, bounds); } private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) { diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java index a0ecceb6bbc..b3276d3e748 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21JavadocVisitor.java @@ -534,6 +534,7 @@ public Tree visitParam(ParamTree node, List<Javadoc> body) { Space.EMPTY, Markers.EMPTY, emptyList(), + emptyList(), visitIdentifier(node.getName(), whitespaceBefore()).withPrefix(namePrefix), null ); 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 89957d024e3..84d0d7b1b4e 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 @@ -1323,7 +1323,7 @@ public J visitTypeParameter(TypeParameterTree node, Space fmt) { JContainer.build(sourceBefore("extends"), convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY); - return new J.TypeParameter(randomId(), fmt, Markers.EMPTY, annotations, name, bounds); + return new J.TypeParameter(randomId(), fmt, Markers.EMPTY, annotations, emptyList(), name, bounds); } private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) { diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java index 74e47da605d..594455096be 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8JavadocVisitor.java @@ -501,6 +501,7 @@ public Tree visitParam(ParamTree node, List<Javadoc> body) { Space.EMPTY, Markers.EMPTY, emptyList(), + emptyList(), visitIdentifier(node.getName(), whitespaceBefore()).withPrefix(namePrefix), null ); 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 c8c7ac5d89d..af7db16ad0a 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 @@ -1239,7 +1239,7 @@ public J visitTypeParameter(TypeParameterTree node, Space fmt) { JContainer.build(sourceBefore("extends"), convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY); - return new J.TypeParameter(randomId(), fmt, Markers.EMPTY, annotations, name, bounds); + return new J.TypeParameter(randomId(), fmt, Markers.EMPTY, annotations, emptyList(), name, bounds); } private <T extends TypeTree & Expression> T buildName(String fullyQualifiedName) { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index 7d10a24ea65..fe17ec029d3 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -5258,6 +5258,10 @@ final class TypeParameter implements J { @Getter List<Annotation> annotations; + @With + @Getter + List<J.Modifier> modifiers; + /** * Will be either a {@link TypeTree} or {@link Wildcard}. Wildcards aren't possible in * every context where type parameters may be defined (e.g. not possible on new statements). @@ -5308,7 +5312,7 @@ public JContainer<TypeTree> getBounds() { } public TypeParameter withBounds(@Nullable JContainer<TypeTree> bounds) { - return t.bounds == bounds ? t : new TypeParameter(t.id, t.prefix, t.markers, t.annotations, t.name, bounds); + return t.bounds == bounds ? t : new TypeParameter(t.id, t.prefix, t.markers, t.annotations, t.modifiers, t.name, bounds); } } } From 172657ab2aa19de3c6f91f983f88eaa9aa8d5f89 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Fri, 17 Nov 2023 10:20:07 +0100 Subject: [PATCH 424/447] Add BitbucketBuildEnvironment for better progress indicators (#3685) As per https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ --- .../marker/ci/BitbucketBuildEnvironment.java | 58 +++++++++++++++++++ .../marker/ci/BuildEnvironment.java | 21 +++++-- 2 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 rewrite-core/src/main/java/org/openrewrite/marker/ci/BitbucketBuildEnvironment.java diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/ci/BitbucketBuildEnvironment.java b/rewrite-core/src/main/java/org/openrewrite/marker/ci/BitbucketBuildEnvironment.java new file mode 100644 index 00000000000..b6b8bfcb9cf --- /dev/null +++ b/rewrite-core/src/main/java/org/openrewrite/marker/ci/BitbucketBuildEnvironment.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.marker.ci; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import lombok.With; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.marker.GitProvenance; + +import java.util.UUID; +import java.util.function.UnaryOperator; + +import static java.util.Collections.emptyList; +import static org.openrewrite.Tree.randomId; + +@Value +@EqualsAndHashCode(callSuper = false) +public class BitbucketBuildEnvironment implements BuildEnvironment{ + @With + UUID id; + String httpOrigin; + String branch; + String sha; + + public static BitbucketBuildEnvironment build(UnaryOperator<String> environment) { + return new BitbucketBuildEnvironment( + randomId(), + environment.apply("BITBUCKET_GIT_HTTP_ORIGIN"), + environment.apply("BITBUCKET_BRANCH"), + environment.apply("BITBUCKET_COMMIT")); + } + + @Override + public GitProvenance buildGitProvenance() throws IncompleteGitConfigException { + if (StringUtils.isBlank(httpOrigin) + || StringUtils.isBlank(branch) + || StringUtils.isBlank(sha)) { + throw new IncompleteGitConfigException(); + } else { + return new GitProvenance(UUID.randomUUID(), httpOrigin, branch, sha, + null, null, emptyList()); + } + } +} diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/ci/BuildEnvironment.java b/rewrite-core/src/main/java/org/openrewrite/marker/ci/BuildEnvironment.java index d0e01737b99..3d877efd8d3 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/ci/BuildEnvironment.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/ci/BuildEnvironment.java @@ -25,20 +25,29 @@ public interface BuildEnvironment extends Marker { @Nullable static BuildEnvironment build(UnaryOperator<String> environment) { + if (environment.apply("BITBUCKET_COMMIT") != null) { + return BitbucketBuildEnvironment.build(environment); + } if (environment.apply("CUSTOM_CI") != null) { return CustomBuildEnvironment.build(environment); - } else if (environment.apply("BUILD_NUMBER") != null && environment.apply("JOB_NAME") != null) { + } + if (environment.apply("BUILD_NUMBER") != null && environment.apply("JOB_NAME") != null) { return JenkinsBuildEnvironment.build(environment); - } else if (environment.apply("GITLAB_CI") != null) { + } + if (environment.apply("GITLAB_CI") != null) { return GitlabBuildEnvironment.build(environment); - } else if (environment.apply("CI") != null && environment.apply("GITHUB_ACTION") != null + } + if (environment.apply("CI") != null && environment.apply("GITHUB_ACTION") != null && environment.apply("GITHUB_RUN_ID") != null) { return GithubActionsBuildEnvironment.build(environment); - } else if (environment.apply("DRONE") != null) { + } + if (environment.apply("DRONE") != null) { return DroneBuildEnvironment.build(environment); - } else if (environment.apply("CIRCLECI") != null) { + } + if (environment.apply("CIRCLECI") != null) { return CircleCiBuildEnvironment.build(environment); - } else if (environment.apply("TRAVIS") != null) { + } + if (environment.apply("TRAVIS") != null) { return TravisBuildEnvironment.build(environment); } return null; From eee01ddc30f343f98c355a8c9344081c228c692e Mon Sep 17 00:00:00 2001 From: api-from-the-ion <145679039+api-from-the-ion@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:05:22 +0100 Subject: [PATCH 425/447] Do not log `ClassCastException` in MavenParser in case of malformed POM (#3701) * [#3699] WIP, Wrote the test and the fix (cherry picked from commit b8545e186e957df9058ce5444a20d24718f46024) * [#3699] Small optimisations * Test ParseError by directly invoking the MavenParser --------- Co-authored-by: Tim te Beek <tim@moderne.io> --- .../org/openrewrite/maven/MavenParser.java | 14 +++++++---- .../openrewrite/maven/MavenParserTest.java | 24 ++++++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java index 8e1209e0c9c..eef8f1f0e2d 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/MavenParser.java @@ -82,12 +82,18 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re pom.getProperties().put("project.basedir", baseDir); pom.getProperties().put("basedir", baseDir); - Xml.Document xml = (Xml.Document) new MavenXmlParser() + SourceFile sourceFile = new MavenXmlParser() .parseInputs(singletonList(source), relativeTo, ctx) .iterator().next(); - projectPoms.put(xml, pom); - projectPomsByPath.put(pomPath, pom); + if (sourceFile instanceof Xml.Document) { + Xml.Document xml = (Xml.Document) sourceFile; + + projectPoms.put(xml, pom); + projectPomsByPath.put(pomPath, pom); + } else { + parsed.add(sourceFile); + } } catch (Throwable t) { ctx.getOnError().accept(t); parsed.add(ParseError.build(this, source, relativeTo, ctx, t)); @@ -185,7 +191,7 @@ public Builder mavenConfig(@Nullable Path mavenConfig) { if (mavenConfig != null && mavenConfig.toFile().exists()) { try { String mavenConfigText = new String(Files.readAllBytes(mavenConfig)); - Matcher matcher = Pattern.compile("(?:$|\\s)-P\\s+([^\\s]+)").matcher(mavenConfigText); + Matcher matcher = Pattern.compile("(?:$|\\s)-P\\s+(\\S+)").matcher(mavenConfigText); if (matcher.find()) { String[] profiles = matcher.group(1).split(","); return activeProfiles(profiles); diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java index 06e7456f934..ab1db7fdbc1 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/MavenParserTest.java @@ -27,6 +27,7 @@ import org.openrewrite.maven.internal.MavenParsingException; import org.openrewrite.maven.tree.*; import org.openrewrite.test.RewriteTest; +import org.openrewrite.tree.ParseError; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -853,7 +854,6 @@ void mirrorsAndAuth() throws IOException { var password = "password"; try (MockWebServer mockRepo = new MockWebServer()) { mockRepo.setDispatcher(new Dispatcher() { - @SuppressWarnings("NullableProblems") @Override public MockResponse dispatch(RecordedRequest request) { MockResponse resp = new MockResponse(); @@ -2237,4 +2237,26 @@ void exclusions() { )) ); } + + @Test + void malformedPom() { + String malformedPomXml = """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.11</version>&gt; + </dependency> + </dependencies> + </project> + """; + assertThat(MavenParser.builder().build().parse(malformedPomXml)) + .singleElement() + .isInstanceOf(ParseError.class); + } } From 97bcf4f0bd0d975fa157a59b81842a5a846a8169 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Sun, 19 Nov 2023 11:56:26 +0100 Subject: [PATCH 426/447] Resolve dependency version after type and classifier (#3704) When resolving a dependency then a transitive dependency could be a managed dependency with a placeholder classifier. This placeholder must then be resolved before the version can be resolved, as it otherwise won't be found and results in a `MavenDownloadingException`. --- .../openrewrite/maven/tree/ResolvedPom.java | 18 ++-- .../maven/tree/ResolvedPomTest.java | 93 +++++++++++++++++++ 2 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/tree/ResolvedPomTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java index 8f6375cae43..d3b6569c50c 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java @@ -769,15 +769,6 @@ private Dependency getValues(Dependency dep, int depth) { return d; } - String version = d.getVersion(); - if (d.getVersion() == null || depth > 0) { - // dependency management overrides transitive dependency versions - version = getManagedVersion(d.getGroupId(), d.getArtifactId(), d.getType(), d.getClassifier()); - if (version == null) { - version = d.getVersion(); - } - } - String scope; if (d.getScope() == null) { Scope parsedScope = getManagedScope(d.getGroupId(), d.getArtifactId(), d.getType(), d.getClassifier()); @@ -797,6 +788,15 @@ private Dependency getValues(Dependency dep, int depth) { if (d.getType() != null) { d = d.withType(getValue(d.getType())); } + String version = d.getVersion(); + if (d.getVersion() == null || depth > 0) { + // dependency management overrides transitive dependency versions + version = getManagedVersion(d.getGroupId(), d.getArtifactId(), d.getType(), d.getClassifier()); + if (version == null) { + version = d.getVersion(); + } + } + return d .withGav(d.getGav().withVersion(version)) .withScope(scope); diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/tree/ResolvedPomTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/ResolvedPomTest.java new file mode 100644 index 00000000000..add21d1e79d --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/tree/ResolvedPomTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.maven.tree; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.maven.Assertions.pomXml; + +class ResolvedPomTest implements RewriteTest { + + @Test + void resolveDependencyWithPlaceholderClassifier() { + rewriteRun( + pomXml( + """ + <project> + <groupId>org.example</groupId> + <artifactId>foo-parent</artifactId> + <version>1</version> + <properties> + <netty.version>4.1.101.Final</netty.version> + <netty-transport-native-epoll-classifier>linux-x86_64</netty-transport-native-epoll-classifier> + </properties> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-transport-native-epoll</artifactId> + <classifier>${netty-transport-native-epoll-classifier}</classifier> + <version>${netty.version}</version> + </dependency> + </dependencies> + </dependencyManagement> + </project> + """, + spec -> spec.path("pom.xml") + ), + pomXml( + """ + <project> + <groupId>org.example</groupId> + <artifactId>foo</artifactId> + <version>1</version> + <parent> + <groupId>org.example</groupId> + <artifactId>foo-parent</artifactId> + <version>1</version> + </parent> + <dependencies> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-transport-native-epoll</artifactId> + <classifier>${netty-transport-native-epoll-classifier}</classifier> + </dependency> + </dependencies> + </project> + """, + spec -> spec.path("foo/pom.xml") + ), + pomXml( + """ + <project> + <groupId>org.example</groupId> + <artifactId>bar</artifactId> + <version>1</version> + <dependencies> + <dependency> + <groupId>org.example</groupId> + <artifactId>foo</artifactId> + <version>1</version> + </dependency> + </dependencies> + </project> + """, + spec -> spec.path("bar/pom.xml") + ) + ); + } +} \ No newline at end of file From e908b09f87d945f5ce0198a0f8f826347732864e Mon Sep 17 00:00:00 2001 From: Michael Keppler <bananeweizen@gmx.de> Date: Sun, 19 Nov 2023 13:32:45 +0100 Subject: [PATCH 427/447] Ignore methods with type parameters in UseStaticImport (#3706) Fixes #3705. --- .../openrewrite/java/UseStaticImportTest.java | 20 +++++++++++++++++++ .../org/openrewrite/java/UseStaticImport.java | 3 +++ 2 files changed, 23 insertions(+) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/UseStaticImportTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/UseStaticImportTest.java index b5b331d48b3..dda2e94b6c9 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/UseStaticImportTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/UseStaticImportTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.ExecutionContext; +import org.openrewrite.Issue; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.test.RewriteTest; @@ -73,6 +74,25 @@ void test() { ); } + @Issue("https://github.com/openrewrite/rewrite/issues/3705") + @Test + void ignoreMethodsWithTypeParameter() { + rewriteRun( + spec -> spec.recipe(new UseStaticImport("java.util.Collections emptyList()")), + java( + """ + import java.util.Collections; + import java.util.List; + + public class Reproducer { + public void methodWithTypeParameter() { + List<Object> list = Collections.<Object>emptyList(); + } + } + """ + ) + ); + } @Test void methodInvocationsHavingNullSelect() { rewriteRun( diff --git a/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java b/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java index 70136b9c483..c24666e1755 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java @@ -59,6 +59,9 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu MethodMatcher methodMatcher = new MethodMatcher(methodPattern); J.MethodInvocation m = super.visitMethodInvocation(method, ctx); if (methodMatcher.matches(m)) { + if (m.getTypeParameters() != null && !m.getTypeParameters().isEmpty()) { + return m; + } if (m.getMethodType() != null) { JavaType.FullyQualified receiverType = m.getMethodType().getDeclaringType(); maybeRemoveImport(receiverType); From f19b05a896ba6bfe00a2b5cc6ed0ee769c00ec83 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 19 Nov 2023 16:34:50 -0500 Subject: [PATCH 428/447] Remove default `jar` packaging when using `ChangePackaging` (#3708) --- .../openrewrite/maven/ChangePackaging.java | 4 ++-- .../maven/ChangePackagingTest.java | 24 +++++++++++++++++++ .../java/org/openrewrite/xml/tree/Xml.java | 5 ++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangePackaging.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangePackaging.java index 06164faa63c..af516de272e 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangePackaging.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangePackaging.java @@ -73,7 +73,7 @@ public Xml visitDocument(Xml.Document document, ExecutionContext ctx) { return document; } document = document.withMarkers(document.getMarkers().withMarkers(ListUtils.map(document.getMarkers().getMarkers(), m -> { - if(m instanceof MavenResolutionResult) { + if (m instanceof MavenResolutionResult) { return getResolutionResult().withPom(pom.withRequested(pom.getRequested().withPackaging(packaging))); } return m; @@ -85,7 +85,7 @@ public Xml visitDocument(Xml.Document document, ExecutionContext ctx) { public Xml visitTag(Xml.Tag tag, ExecutionContext context) { Xml.Tag t = (Xml.Tag) super.visitTag(tag, context); if (PROJECT_MATCHER.matches(getCursor())) { - if (packaging == null) { + if (packaging == null || "jar".equals(packaging)) { t = filterTagChildren(t, it -> !"packaging".equals(it.getName())); } else { t = addOrUpdateChild(t, Xml.Tag.build("\n<packaging>" + packaging + "</packaging>"), getCursor().getParentOrThrow()); diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangePackagingTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangePackagingTest.java index cb9ea71b7de..2b4894457b8 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangePackagingTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangePackagingTest.java @@ -96,4 +96,28 @@ void changePackaging() { ) ); } + + @Test + void changePackagingRemovingDefault() { + rewriteRun( + spec -> spec.recipe(new ChangePackaging("*", "*", "jar")), + pomXml( +""" + <project> + <groupId>org.example</groupId> + <artifactId>foo</artifactId> + <version>1.0</version> + <packaging>war</packaging> + </project> + """, + """ + <project> + <groupId>org.example</groupId> + <artifactId>foo</artifactId> + <version>1.0</version> + </project> + """ + ) + ); + } } diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/tree/Xml.java b/rewrite-xml/src/main/java/org/openrewrite/xml/tree/Xml.java index 901ff50e765..9df4ea3a42e 100755 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/tree/Xml.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/tree/Xml.java @@ -597,6 +597,11 @@ public String getPrefix() { public <P> Xml acceptXml(XmlVisitor<P> v, P p) { return v.visitComment(this, p); } + + @Override + public String toString() { + return "<!--" + text + "-->"; + } } @Value From 31d9eaa705c1ffc2ca471f3c40b36a49af586173 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Mon, 20 Nov 2023 12:56:01 +0100 Subject: [PATCH 429/447] Replicate `FindAndReplace` `No group with name {name}` (#3387) * Replicate `FindAndReplace` `No group with name {name}` As per https://github.com/openrewrite/rewrite/issues/3377#issuecomment-1614637746 * Escape dollar signs when not using a regex * Apply suggestions from code review Co-authored-by: Joan Viladrosa <joan@moderne.io> --------- Co-authored-by: Joan Viladrosa <joan@moderne.io> --- .../main/java/org/openrewrite/text/FindAndReplace.java | 7 ++++++- .../java/org/openrewrite/text/FindAndReplaceTest.java | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java index 899a47c22e6..40aa0f0b258 100644 --- a/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java +++ b/rewrite-core/src/main/java/org/openrewrite/text/FindAndReplace.java @@ -134,7 +134,11 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { if (!matcher.find()) { return sourceFile; } - String newText = matcher.replaceAll(replace); + String replacement = replace; + if (!Boolean.TRUE.equals(regex)) { + replacement = replacement.replace("$", "\\$"); + } + String newText = matcher.replaceAll(replacement); return plainText.withText(newText) .withMarkers(sourceFile.getMarkers().add(new AlreadyReplaced(randomId(), find, replace))); } @@ -150,4 +154,5 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { } return visitor; } + } diff --git a/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java b/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java index 460445645c3..9bc0072c140 100644 --- a/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/text/FindAndReplaceTest.java @@ -99,6 +99,16 @@ void noRecursive() { ); } + @Test + void dollarsignsTolerated() { + String find = "This is text ${dynamic}."; + String replace = "This is text ${dynamic}. Stuff"; + rewriteRun( + spec -> spec.recipe(new FindAndReplace(find, replace, null, null, null, null, null)).cycles(1), + text(find, replace) + ); + } + @Value @EqualsAndHashCode(callSuper = true) static class MultiFindAndReplace extends Recipe { From a19cc9cd5051543ca5f3f9ea3468b728c16c5c50 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Mon, 20 Nov 2023 17:05:30 +0100 Subject: [PATCH 430/447] Load services through class loader of caller The caller supplies the service type using which `JavaSourceFile#service()` can instantiate a service of the desired type and also make sure it gets loaded using the corresponding class loader. --- .../openrewrite/java/tree/JavaSourceFile.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java index 581924e6cfc..6a06d8ba2cd 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaSourceFile.java @@ -22,6 +22,7 @@ import org.openrewrite.java.service.AutoFormatService; import org.openrewrite.java.service.ImportService; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.util.List; @@ -55,14 +56,19 @@ public interface JavaSourceFile extends J { SourceFile withSourcePath(Path path); @Incubating(since = "8.2.0") - @SuppressWarnings("unchecked") default <S> S service(Class<S> service) { - if (ImportService.class.getName().equals(service.getName())) { - return (S) new ImportService(); - } else if (AutoFormatService.class.getName().equals(service.getName())) { - return (S) new AutoFormatService(); + try { + // use name indirection due to possibility of multiple class loaders being used + if (ImportService.class.getName().equals(service.getName())) { + return service.getConstructor().newInstance(); + } else if (AutoFormatService.class.getName().equals(service.getName())) { + return service.getConstructor().newInstance(); + } else { + throw new UnsupportedOperationException("Service " + service + " not supported"); + } + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); } - throw new UnsupportedOperationException("Service " + service + " not supported"); } interface Padding { From cc8ff8e47cd5204bf6ec2dc8a8ddf82f3478ac7f Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Mon, 20 Nov 2023 17:59:28 +0100 Subject: [PATCH 431/447] reorder constructor arguments too (#3713) --- .../java/ReorderMethodArgumentsTest.java | 45 +++++++++++++++++++ .../java/ReorderMethodArguments.java | 41 +++++++++++++++-- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java index e17ce740684..f9261c7dc1c 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ReorderMethodArgumentsTest.java @@ -167,4 +167,49 @@ public void test() { ) ); } + + @Test + void reorderArgumentsInConstructors() { + rewriteRun( + spec -> spec.recipes( + new ReorderMethodArguments("a.A <constructor>(String, Integer, Integer)", + new String[]{"n", "m", "s"}, null, null, null)), + java( + """ + package a; + public class A { + public A(String s, Integer m, Integer n) {} + public A(Integer n, Integer m, String s) {} + } + """ + ), + java( + """ + import a.*; + public class B { + public void test() { + A a = new A( + "mystring", + 1, + 2 + ); + } + } + """, + """ + import a.*; + public class B { + public void test() { + A a = new A( + 2, + 1, + "mystring" + ); + } + } + """ + ) + ); + } + } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java b/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java index 405b4c70a56..bf81cb990e2 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ReorderMethodArguments.java @@ -122,15 +122,23 @@ private ReorderMethodArgumentsVisitor(MethodMatcher methodMatcher) { @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + return (J.MethodInvocation) visitMethodCall(super.visitMethodInvocation(method, ctx)); + } + + @Override + public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext executionContext) { + return (J.NewClass) visitMethodCall(super.visitNewClass(newClass, executionContext)); + } + private MethodCall visitMethodCall(MethodCall m) { if (methodMatcher.matches(m) && m.getMethodType() != null) { @SuppressWarnings("ConstantConditions") List<String> paramNames = oldParameterNames == null || oldParameterNames.length == 0 ? m.getMethodType().getParameterNames() : asList(oldParameterNames); - List<JRightPadded<Expression>> originalArgs = m.getPadding().getArguments().getPadding().getElements(); + List<JRightPadded<Expression>> originalArgs = getPaddedArguments(m); + int resolvedParamCount = m.getMethodType().getParameterTypes().size(); int i = 0; @@ -178,8 +186,7 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu } if (changed) { - m = m.getPadding() - .withArguments(m.getPadding().getArguments().getPadding().withElements(reordered)) + m = withPaddedArguments(m, reordered) .withMethodType(m.getMethodType() .withParameterNames(reorderedNames) .withParameterTypes(reorderedTypes) @@ -188,5 +195,31 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu } return m; } + + private List<JRightPadded<Expression>> getPaddedArguments(MethodCall m) { + if (m instanceof J.MethodInvocation) { + return ((J.MethodInvocation) m).getPadding().getArguments().getPadding().getElements(); + } else if (m instanceof J.NewClass) { + return ((J.NewClass) m).getPadding().getArguments().getPadding().getElements(); + } else { + throw new IllegalArgumentException("Unknown MethodCall type"); + } + } + + private MethodCall withPaddedArguments(MethodCall m, List<JRightPadded<Expression>> reordered) { + if (m instanceof J.MethodInvocation) { + J.MethodInvocation mi = (J.MethodInvocation) m; + return mi.getPadding().withArguments( + mi.getPadding().getArguments().getPadding().withElements(reordered) + ); + } else if (m instanceof J.NewClass) { + J.NewClass nc = (J.NewClass) m; + return nc.getPadding().withArguments( + nc.getPadding().getArguments().getPadding().withElements(reordered) + ); + } else { + throw new IllegalArgumentException("Unknown MethodCall type"); + } + } } } From 4f040664be8a836dbaf1d03a4bfa22c1f7988593 Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Mon, 20 Nov 2023 09:37:14 -0800 Subject: [PATCH 432/447] Add missing visit J.TypeParameter.modifiers method in JavaVisitor --- rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index dab2a19af94..8a5ad94e4cc 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -1191,6 +1191,7 @@ public J visitTypeParameter(J.TypeParameter typeParam, P p) { t = t.withPrefix(visitSpace(t.getPrefix(), Space.Location.TYPE_PARAMETERS_PREFIX, p)); t = t.withMarkers(visitMarkers(t.getMarkers(), p)); t = t.withAnnotations(ListUtils.map(t.getAnnotations(), a -> visitAndCast(a, p))); + t = t.withModifiers(ListUtils.map(t.getModifiers(), m -> visitAndCast(m, p))); t = t.withName(visitAndCast(t.getName(), p)); if (t.getName() instanceof NameTree) { t = t.withName((Expression) visitTypeName((NameTree) t.getName(), p)); From 9965bbf5f26d3492100086d38cb9647071833108 Mon Sep 17 00:00:00 2001 From: Kun Li <kun@moderne.io> Date: Mon, 20 Nov 2023 09:41:00 -0800 Subject: [PATCH 433/447] Add safe null check for visiting J.TypeParameter's modifiers --- .../src/main/java/org/openrewrite/java/JavaVisitor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index 8a5ad94e4cc..b0013e9be57 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -1191,7 +1191,11 @@ public J visitTypeParameter(J.TypeParameter typeParam, P p) { t = t.withPrefix(visitSpace(t.getPrefix(), Space.Location.TYPE_PARAMETERS_PREFIX, p)); t = t.withMarkers(visitMarkers(t.getMarkers(), p)); t = t.withAnnotations(ListUtils.map(t.getAnnotations(), a -> visitAndCast(a, p))); - t = t.withModifiers(ListUtils.map(t.getModifiers(), m -> visitAndCast(m, p))); + + if (t.getModifiers() != null && !t.getModifiers().isEmpty()) { + t = t.withModifiers(ListUtils.map(t.getModifiers(), m -> visitAndCast(m, p))); + } + t = t.withName(visitAndCast(t.getName(), p)); if (t.getName() instanceof NameTree) { t = t.withName((Expression) visitTypeName((NameTree) t.getName(), p)); From 552a4a8ab07e97e2d00117a8ef1ee4621107fdfa Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 21 Nov 2023 09:36:00 +0100 Subject: [PATCH 434/447] Support intersection type casts (#3652) * Support intersection type casts Fixes: #3651 * Add missing `ReloadableJava21JavadocVisitor` * Complete type attribution for `var` variables * Completed Java 8, 11, and 21 * Let `J.IntersectionType` implement `Expression` * Extend `JavaTypeVisitor` for `Intersection` * Implement `TypeUtils#isAssignableTo()` for `Intersection` --- .../ReloadableJava11ParserVisitor.java | 8 ++ .../isolated/ReloadableJava11TypeMapping.java | 18 +++- .../ReloadableJava11TypeSignatureBuilder.java | 8 ++ .../ReloadableJava17ParserVisitor.java | 8 ++ .../isolated/ReloadableJava17TypeMapping.java | 18 +++- .../ReloadableJava17TypeSignatureBuilder.java | 8 ++ .../ReloadableJava21ParserVisitor.java | 8 ++ .../isolated/ReloadableJava21TypeMapping.java | 18 +++- .../ReloadableJava21TypeSignatureBuilder.java | 8 ++ .../java/ReloadableJava8ParserVisitor.java | 8 ++ .../java/ReloadableJava8TypeMapping.java | 18 +++- .../ReloadableJava8TypeSignatureBuilder.java | 8 ++ .../openrewrite/java/tree/TypeCastTest.java | 57 ++++++++++++ .../openrewrite/java/tree/TypeUtilsTest.java | 45 +++++++++- .../org/openrewrite/java/JavaIsoVisitor.java | 5 ++ .../org/openrewrite/java/JavaPrinter.java | 8 ++ .../org/openrewrite/java/JavaTypeVisitor.java | 8 ++ .../org/openrewrite/java/JavaVisitor.java | 9 ++ .../java/UnsafeJavaTypeVisitor.java | 15 ++-- .../java/org/openrewrite/java/tree/J.java | 87 +++++++++++++++++++ .../org/openrewrite/java/tree/JavaType.java | 41 +++++++++ .../java/org/openrewrite/java/tree/Space.java | 1 + .../org/openrewrite/java/tree/TypeUtils.java | 13 +++ 23 files changed, 415 insertions(+), 10 deletions(-) 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 9bf6dfdeec5..e5832ec3e2a 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 @@ -721,6 +721,14 @@ public J visitInstanceOf(InstanceOfTree node, Space fmt) { typeMapping.type(node)); } + @Override + public J visitIntersectionType(IntersectionTypeTree node, Space fmt) { + JContainer<TypeTree> bounds = node.getBounds().isEmpty() ? null : + JContainer.build(EMPTY, + convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY); + return new J.IntersectionType(randomId(), fmt, Markers.EMPTY, bounds); + } + @Override public J visitLabeledStatement(LabeledStatementTree node, Space fmt) { skip(node.getLabel().toString()); diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java index 9ed451f58dc..2d281314ca3 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java @@ -27,6 +27,7 @@ import org.openrewrite.java.tree.TypeUtils; import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeMirror; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -59,7 +60,9 @@ public JavaType type(@Nullable com.sun.tools.javac.code.Type type) { return existing; } - if (type instanceof Type.ClassType) { + if (type instanceof Type.IntersectionClassType) { + return intersectionType((Type.IntersectionClassType) type, signature); + } else if (type instanceof Type.ClassType) { return classType((Type.ClassType) type, signature); } else if (type instanceof Type.TypeVar) { return generic((Type.TypeVar) type, signature); @@ -78,6 +81,19 @@ public JavaType type(@Nullable com.sun.tools.javac.code.Type type) { throw new UnsupportedOperationException("Unknown type " + type.getClass().getName()); } + private JavaType intersectionType(Type.IntersectionClassType type, String signature) { + JavaType.Intersection intersection = new JavaType.Intersection(null); + typeCache.put(signature, intersection); + JavaType[] types = new JavaType[type.getBounds().size()]; + List<? extends TypeMirror> bounds = type.getBounds(); + for (int i = 0; i < bounds.size(); i++) { + TypeMirror bound = bounds.get(i); + types[i] = type((Type) bound); + } + intersection.unsafeSet(types); + return intersection; + } + private JavaType array(Type type, String signature) { JavaType.Array arr = new JavaType.Array(null, null); typeCache.put(signature, arr); diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeSignatureBuilder.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeSignatureBuilder.java index 9a7de3556b6..7e1a478cbad 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeSignatureBuilder.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeSignatureBuilder.java @@ -23,6 +23,7 @@ import org.openrewrite.java.tree.JavaType; import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeMirror; import java.util.HashSet; import java.util.Set; import java.util.StringJoiner; @@ -39,6 +40,13 @@ public String signature(@Nullable Object t) { private String signature(@Nullable Type type) { if (type == null || type instanceof Type.UnknownType || type instanceof NullType) { return "{undefined}"; + } else if (type instanceof Type.IntersectionClassType) { + Type.IntersectionClassType intersectionClassType = (Type.IntersectionClassType) type; + StringJoiner joiner = new StringJoiner(" & "); + for (TypeMirror typeArg : intersectionClassType.getBounds()) { + joiner.add(signature(typeArg)); + } + return joiner.toString(); } else if (type instanceof Type.ClassType) { try { return ((Type.ClassType) type).typarams_field != null && ((Type.ClassType) type).typarams_field.length() > 0 ? parameterizedSignature(type) : classSignature(type); 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 0d4c32a57a2..66facd58890 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 @@ -780,6 +780,14 @@ public J visitInstanceOf(InstanceOfTree node, Space fmt) { type); } + @Override + public J visitIntersectionType(IntersectionTypeTree node, Space fmt) { + JContainer<TypeTree> bounds = node.getBounds().isEmpty() ? null : + JContainer.build(EMPTY, + convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY); + return new J.IntersectionType(randomId(), fmt, Markers.EMPTY, bounds); + } + @Override public J visitLabeledStatement(LabeledStatementTree node, Space fmt) { skip(node.getLabel().toString()); diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java index 6fbab97af01..efcf11f961b 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java @@ -27,6 +27,7 @@ import org.openrewrite.java.tree.TypeUtils; import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeMirror; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -56,7 +57,9 @@ public JavaType type(@Nullable com.sun.tools.javac.code.Type type) { return existing; } - if (type instanceof Type.ClassType) { + if (type instanceof Type.IntersectionClassType) { + return intersectionType((Type.IntersectionClassType) type, signature); + } else if (type instanceof Type.ClassType) { return classType((Type.ClassType) type, signature); } else if (type instanceof Type.TypeVar) { return generic((Type.TypeVar) type, signature); @@ -75,6 +78,19 @@ public JavaType type(@Nullable com.sun.tools.javac.code.Type type) { throw new UnsupportedOperationException("Unknown type " + type.getClass().getName()); } + private JavaType intersectionType(Type.IntersectionClassType type, String signature) { + JavaType.Intersection intersection = new JavaType.Intersection(null); + typeCache.put(signature, intersection); + JavaType[] types = new JavaType[type.getBounds().size()]; + List<? extends TypeMirror> bounds = type.getBounds(); + for (int i = 0; i < bounds.size(); i++) { + TypeMirror bound = bounds.get(i); + types[i] = type((Type) bound); + } + intersection.unsafeSet(types); + return intersection; + } + private JavaType array(Type type, String signature) { JavaType.Array arr = new JavaType.Array(null, null); typeCache.put(signature, arr); diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeSignatureBuilder.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeSignatureBuilder.java index e652771e9da..63260a7ca79 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeSignatureBuilder.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeSignatureBuilder.java @@ -23,6 +23,7 @@ import org.openrewrite.java.tree.JavaType; import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeMirror; import java.util.HashSet; import java.util.Set; import java.util.StringJoiner; @@ -39,6 +40,13 @@ public String signature(@Nullable Object t) { private String signature(@Nullable Type type) { if (type == null || type instanceof Type.UnknownType || type instanceof NullType) { return "{undefined}"; + } else if (type instanceof Type.IntersectionClassType) { + Type.IntersectionClassType intersectionClassType = (Type.IntersectionClassType) type; + StringJoiner joiner = new StringJoiner(" & "); + for (TypeMirror typeArg : intersectionClassType.getBounds()) { + joiner.add(signature(typeArg)); + } + return joiner.toString(); } else if (type instanceof Type.ClassType) { try { return ((Type.ClassType) type).typarams_field != null && ((Type.ClassType) type).typarams_field.length() > 0 ? parameterizedSignature(type) : classSignature(type); 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 84d0d7b1b4e..c6941958674 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 @@ -780,6 +780,14 @@ public J visitInstanceOf(InstanceOfTree node, Space fmt) { type); } + @Override + public J visitIntersectionType(IntersectionTypeTree node, Space fmt) { + JContainer<TypeTree> bounds = node.getBounds().isEmpty() ? null : + JContainer.build(EMPTY, + convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY); + return new J.IntersectionType(randomId(), fmt, Markers.EMPTY, bounds); + } + @Override public J visitLabeledStatement(LabeledStatementTree node, Space fmt) { skip(node.getLabel().toString()); diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java index 046153bb5a1..84d79693393 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java @@ -27,6 +27,7 @@ import org.openrewrite.java.tree.TypeUtils; import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeMirror; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -56,7 +57,9 @@ public JavaType type(@Nullable com.sun.tools.javac.code.Type type) { return existing; } - if (type instanceof Type.ClassType) { + if (type instanceof Type.IntersectionClassType) { + return intersectionType((Type.IntersectionClassType) type, signature); + } else if (type instanceof Type.ClassType) { return classType((Type.ClassType) type, signature); } else if (type instanceof Type.TypeVar) { return generic((Type.TypeVar) type, signature); @@ -75,6 +78,19 @@ public JavaType type(@Nullable com.sun.tools.javac.code.Type type) { throw new UnsupportedOperationException("Unknown type " + type.getClass().getName()); } + private JavaType intersectionType(Type.IntersectionClassType type, String signature) { + JavaType.Intersection intersection = new JavaType.Intersection(null); + typeCache.put(signature, intersection); + JavaType[] types = new JavaType[type.getBounds().size()]; + List<? extends TypeMirror> bounds = type.getBounds(); + for (int i = 0; i < bounds.size(); i++) { + TypeMirror bound = bounds.get(i); + types[i] = type((Type) bound); + } + intersection.unsafeSet(types); + return intersection; + } + private JavaType array(Type type, String signature) { JavaType.Array arr = new JavaType.Array(null, null); typeCache.put(signature, arr); diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeSignatureBuilder.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeSignatureBuilder.java index 2d2ccd15955..10d806231d5 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeSignatureBuilder.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeSignatureBuilder.java @@ -23,6 +23,7 @@ import org.openrewrite.java.tree.JavaType; import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeMirror; import java.util.HashSet; import java.util.Set; import java.util.StringJoiner; @@ -39,6 +40,13 @@ public String signature(@Nullable Object t) { private String signature(@Nullable Type type) { if (type == null || type instanceof Type.UnknownType || type instanceof NullType) { return "{undefined}"; + } else if (type instanceof Type.IntersectionClassType) { + Type.IntersectionClassType intersectionClassType = (Type.IntersectionClassType) type; + StringJoiner joiner = new StringJoiner(" & "); + for (TypeMirror typeArg : intersectionClassType.getBounds()) { + joiner.add(signature(typeArg)); + } + return joiner.toString(); } else if (type instanceof Type.ClassType) { try { return ((Type.ClassType) type).typarams_field != null && ((Type.ClassType) type).typarams_field.length() > 0 ? parameterizedSignature(type) : classSignature(type); 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 af7db16ad0a..1011383b59b 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 @@ -719,6 +719,14 @@ public J visitInstanceOf(InstanceOfTree node, Space fmt) { typeMapping.type(node)); } + @Override + public J visitIntersectionType(IntersectionTypeTree node, Space fmt) { + JContainer<TypeTree> bounds = node.getBounds().isEmpty() ? null : + JContainer.build(EMPTY, + convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY); + return new J.IntersectionType(randomId(), fmt, Markers.EMPTY, bounds); + } + @Override public J visitLabeledStatement(LabeledStatementTree node, Space fmt) { skip(node.getLabel().toString()); diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java index 44196ea7e7b..1d613393e97 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java @@ -26,6 +26,7 @@ import org.openrewrite.java.tree.TypeUtils; import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeMirror; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -58,7 +59,9 @@ public JavaType type(@Nullable com.sun.tools.javac.code.Type type) { return existing; } - if (type instanceof Type.ClassType) { + if (type instanceof Type.IntersectionClassType) { + return intersectionType((Type.IntersectionClassType) type, signature); + } else if (type instanceof Type.ClassType) { return classType((Type.ClassType) type, signature); } else if (type instanceof Type.TypeVar) { return generic((Type.TypeVar) type, signature); @@ -79,6 +82,19 @@ public JavaType type(@Nullable com.sun.tools.javac.code.Type type) { throw new UnsupportedOperationException("Unknown type " + type.getClass().getName()); } + private JavaType intersectionType(Type.IntersectionClassType type, String signature) { + JavaType.Intersection intersection = new JavaType.Intersection(null); + typeCache.put(signature, intersection); + JavaType[] types = new JavaType[type.getBounds().size()]; + List<? extends TypeMirror> bounds = type.getBounds(); + for (int i = 0; i < bounds.size(); i++) { + TypeMirror bound = bounds.get(i); + types[i] = type((Type) bound); + } + intersection.unsafeSet(types); + return intersection; + } + private JavaType array(Type type, String signature) { JavaType.Array arr = new JavaType.Array(null, null); typeCache.put(signature, arr); diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeSignatureBuilder.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeSignatureBuilder.java index ef5a63a9ab1..3262e8c2ad4 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeSignatureBuilder.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeSignatureBuilder.java @@ -22,6 +22,7 @@ import org.openrewrite.java.tree.JavaType; import javax.lang.model.type.NullType; +import javax.lang.model.type.TypeMirror; import java.util.HashSet; import java.util.Set; import java.util.StringJoiner; @@ -38,6 +39,13 @@ public String signature(@Nullable Object t) { private String signature(@Nullable Type type) { if (type == null || type instanceof Type.UnknownType || type instanceof NullType) { return "{undefined}"; + } else if (type instanceof Type.IntersectionClassType) { + Type.IntersectionClassType intersectionClassType = (Type.IntersectionClassType) type; + StringJoiner joiner = new StringJoiner(" & "); + for (TypeMirror typeArg : intersectionClassType.getBounds()) { + joiner.add(signature(typeArg)); + } + return joiner.toString(); } else if (type instanceof Type.ClassType) { try { return ((Type.ClassType) type).typarams_field != null && ((Type.ClassType) type).typarams_field.length() > 0 ? parameterizedSignature(type) : classSignature(type); diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeCastTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeCastTest.java index 011930d4e7b..5ce398b476c 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeCastTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeCastTest.java @@ -16,8 +16,10 @@ package org.openrewrite.java.tree; import org.junit.jupiter.api.Test; +import org.openrewrite.java.MinimumJava11; import org.openrewrite.test.RewriteTest; +import static org.assertj.core.api.Assertions.assertThat; import static org.openrewrite.java.Assertions.java; class TypeCastTest implements RewriteTest { @@ -35,4 +37,59 @@ class Test { ) ); } + + @Test + void intersectionCast() { + rewriteRun( + java( + """ + import java.io.Serializable; + import java.util.function.BiFunction; + + class Test { + Serializable s = (Serializable & BiFunction<Integer, Integer, Integer>) Integer::sum; + } + """ + ) + ); + } + + @MinimumJava11 + @Test + void intersectionCastAssignedToVar() { + rewriteRun( + java( + """ + import java.io.Serializable; + import java.util.function.BiFunction; + + class Test { + void m() { + var s = (Serializable & BiFunction<Integer, Integer, Integer>) Integer::sum; + } + } + """, + spec -> spec.afterRecipe(cu -> { + J.MethodDeclaration m = (J.MethodDeclaration) cu.getClasses().get(0).getBody().getStatements().get(0); + J.VariableDeclarations s = (J.VariableDeclarations) m.getBody().getStatements().get(0); + assertThat(s.getType()).isInstanceOf(JavaType.Intersection.class); + JavaType.Intersection intersection = (JavaType.Intersection) s.getType(); + assertThat(intersection.getBounds()).satisfiesExactly( + b1 -> assertThat(b1).satisfies( + t -> assertThat(t).isInstanceOf(JavaType.Class.class), + t -> assertThat(((JavaType.Class) t).getFullyQualifiedName()).isEqualTo("java.io.Serializable") + ), + b2 -> assertThat(b2).satisfies( + t -> assertThat(t).isInstanceOf(JavaType.Parameterized.class), + t -> assertThat(((JavaType.Parameterized) t).getFullyQualifiedName()).isEqualTo("java.util.function.BiFunction"), + t -> assertThat(((JavaType.Parameterized) t).getTypeParameters()).hasSize(3), + t -> assertThat(((JavaType.Parameterized) t).getTypeParameters()).allSatisfy( + p -> assertThat(((JavaType.Class) p).getFullyQualifiedName()).isEqualTo("java.lang.Integer") + ) + ) + ); + }) + ) + ); + } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java index 7758f99be21..541a53609b5 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/TypeUtilsTest.java @@ -253,7 +253,7 @@ void isAssignableToGenericTypeVariable() { """ import java.util.Map; import java.util.function.Supplier; - + class Test { <K, V> void m(Supplier<? extends Map<K, ? extends V>> map) { } @@ -277,4 +277,47 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Objec ) ); } + + @SuppressWarnings("RedundantCast") + @Test + void isAssignableFromIntersection() { + rewriteRun( + java( + """ + import java.io.Serializable; + + class Test { + Object o1 = (Serializable & Runnable) null; + } + """, + spec -> spec.afterRecipe(cu -> new JavaIsoVisitor<>() { + @Override + public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, Object o) { + JavaType variableType = variable.getVariableType().getType(); + assertThat(variableType).satisfies( + type -> assertThat(type).isInstanceOf(JavaType.Class.class), + type -> assertThat(((JavaType.Class) type).getFullyQualifiedName()).isEqualTo("java.lang.Object") + ); + J.TypeCast typeCast = (J.TypeCast) variable.getInitializer(); + assertThat(typeCast.getType()).satisfies( + type -> assertThat(type).isInstanceOf(JavaType.Intersection.class), + type -> assertThat(((JavaType.Intersection) type).getBounds()).satisfiesExactly( + bound -> assertThat(((JavaType.Class) bound).getFullyQualifiedName()).isEqualTo("java.io.Serializable"), + bound -> assertThat(((JavaType.Class) bound).getFullyQualifiedName()).isEqualTo("java.lang.Runnable") + ), + type -> assertThat(((JavaType.Intersection) type).getBounds()).allSatisfy( + bound -> { + assertThat(TypeUtils.isAssignableTo(bound, type)).isTrue(); + assertThat(TypeUtils.isAssignableTo(((JavaType.FullyQualified) bound).getFullyQualifiedName(), type)).isTrue(); + } + ), + type -> assertThat(TypeUtils.isAssignableTo(JavaType.ShallowClass.build("java.lang.Object"), type)).isTrue(), + type -> assertThat(TypeUtils.isAssignableTo("java.lang.Object", type)).isTrue() + ); + return variable; + } + }.visit(cu, new InMemoryExecutionContext())) + ) + ); + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaIsoVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaIsoVisitor.java index 6bff0eee3ef..f9d8916a930 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaIsoVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaIsoVisitor.java @@ -194,6 +194,11 @@ public J.InstanceOf visitInstanceOf(J.InstanceOf instanceOf, P p) { return (J.InstanceOf) super.visitInstanceOf(instanceOf, p); } + @Override + public J.IntersectionType visitIntersectionType(J.IntersectionType intersectionType, P p) { + return (J.IntersectionType) super.visitIntersectionType(intersectionType, p); + } + @Override public J.Label visitLabel(J.Label label, P p) { return (J.Label) super.visitLabel(label, p); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java index db21d010b73..51e44132e6b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaPrinter.java @@ -661,6 +661,14 @@ public J visitInstanceOf(InstanceOf instanceOf, PrintOutputCapture<P> p) { return instanceOf; } + @Override + public J visitIntersectionType(IntersectionType intersectionType, PrintOutputCapture<P> p) { + beforeSyntax(intersectionType, Space.Location.INTERSECTION_TYPE_PREFIX, p); + visitContainer("", intersectionType.getPadding().getBounds(), JContainer.Location.TYPE_BOUNDS, "&", "", p); + afterSyntax(intersectionType, p); + return intersectionType; + } + @Override public J visitLabel(Label label, PrintOutputCapture<P> p) { beforeSyntax(label, Space.Location.LABEL_PREFIX, p); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaTypeVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaTypeVisitor.java index c8941bcca46..fbedd386d8e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaTypeVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaTypeVisitor.java @@ -75,6 +75,8 @@ public JavaType visit(@Nullable JavaType javaType, P p) { javaType = visitClass((JavaType.Class) javaType, p); } else if (javaType instanceof JavaType.GenericTypeVariable) { javaType = visitGenericTypeVariable((JavaType.GenericTypeVariable) javaType, p); + } else if (javaType instanceof JavaType.Intersection) { + javaType = visitIntersection((JavaType.Intersection) javaType, p); } else if (javaType instanceof JavaType.MultiCatch) { javaType = visitMultiCatch((JavaType.MultiCatch) javaType, p); } else if (javaType instanceof JavaType.Parameterized) { @@ -129,6 +131,12 @@ public JavaType visitGenericTypeVariable(JavaType.GenericTypeVariable generic, P return g; } + public JavaType visitIntersection(JavaType.Intersection intersection, P p) { + JavaType.Intersection i = intersection; + i = i.withBounds(ListUtils.map(i.getBounds(), bound -> visit(bound, p))); + return i; + } + /** * This does not visit the declaring type to avoid a visitor cycle. * diff --git a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java index b0013e9be57..a519435f565 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/JavaVisitor.java @@ -735,6 +735,15 @@ public J visitInstanceOf(J.InstanceOf instanceOf, P p) { return i; } + public J visitIntersectionType(J.IntersectionType intersectionType, P p) { + J.IntersectionType i = intersectionType; + i = i.withPrefix(visitSpace(i.getPrefix(), Space.Location.INTERSECTION_TYPE_PREFIX, p)); + i = i.withMarkers(visitMarkers(i.getMarkers(), p)); + i = i.getPadding().withBounds(visitContainer(i.getPadding().getBounds(), JContainer.Location.TYPE_BOUNDS, p)); + i = i.withType(visitType(i.getType(), p)); + return i; + } + public J visitLabel(J.Label label, P p) { J.Label l = label; l = l.withPrefix(visitSpace(l.getPrefix(), Space.Location.LABEL_PREFIX, p)); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java index 24df461784e..37c56d8e72f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/UnsafeJavaTypeVisitor.java @@ -58,6 +58,11 @@ public JavaType visitGenericTypeVariable(JavaType.GenericTypeVariable generic, P ); } + @Override + public JavaType visitIntersection(Intersection intersection, P p) { + return intersection.unsafeSet(mapInPlace(intersection.getBounds().toArray(EMPTY_JAVA_TYPE_ARRAY), t -> visit(t, p))); + } + @Override public JavaType visitMethod(JavaType.Method method, P p) { return method.unsafeSet( @@ -69,6 +74,11 @@ public JavaType visitMethod(JavaType.Method method, P p) { ); } + @Override + public JavaType visitMultiCatch(JavaType.MultiCatch multiCatch, P p) { + return multiCatch.unsafeSet(mapInPlace(multiCatch.getThrowableTypes().toArray(EMPTY_JAVA_TYPE_ARRAY), t -> visit(t, p))); + } + @Override public JavaType visitVariable(JavaType.Variable variable, P p) { return variable.unsafeSet( @@ -78,11 +88,6 @@ public JavaType visitVariable(JavaType.Variable variable, P p) { ); } - @Override - public JavaType visitMultiCatch(JavaType.MultiCatch multiCatch, P p) { - return multiCatch.unsafeSet(mapInPlace(multiCatch.getThrowableTypes().toArray(EMPTY_JAVA_TYPE_ARRAY), t -> visit(t, p))); - } - private static <T extends JavaType> T[] mapInPlace(T[] ls, UnaryOperator<T> map) { for (int i = 0; i < ls.length; i++) { ls[i] = map.apply(ls[i]); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index fe17ec029d3..5630a9706ad 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -2923,6 +2923,93 @@ public InstanceOf withExpr(JRightPadded<Expression> expression) { } } + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) + @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) + @RequiredArgsConstructor + @AllArgsConstructor(access = AccessLevel.PRIVATE) + final class IntersectionType implements J, TypeTree, Expression { + @Nullable + @NonFinal + transient WeakReference<Padding> padding; + + @With + @EqualsAndHashCode.Include + @Getter + UUID id; + + @With + @Getter + Space prefix; + + @With + @Getter + Markers markers; + + JContainer<TypeTree> bounds; + + public List<TypeTree> getBounds() { + return bounds.getElements(); + } + + public IntersectionType withBounds(List<TypeTree> bounds) { + return getPadding().withBounds(JContainer.withElementsNullable(this.bounds, bounds)); + } + + @Override + public <P> J acceptJava(JavaVisitor<P> v, P p) { + return v.visitIntersectionType(this, p); + } + + @SuppressWarnings("unchecked") + @Override + public IntersectionType withType(@Nullable JavaType type) { + // cannot overwrite type directly, perform this operation on each bound separately + return this; + } + + @Override + public JavaType getType() { + return new JavaType.Intersection(bounds.getPadding().getElements().stream() + .filter(Objects::nonNull) + .map(b -> b.getElement().getType()) + .collect(toList())); + } + + @Override + public CoordinateBuilder.Expression getCoordinates() { + return new CoordinateBuilder.Expression(this); + } + + public Padding getPadding() { + Padding p; + if (this.padding == null) { + p = new Padding(this); + this.padding = new WeakReference<>(p); + } else { + p = this.padding.get(); + if (p == null || p.t != this) { + p = new Padding(this); + this.padding = new WeakReference<>(p); + } + } + return p; + } + + @RequiredArgsConstructor + public static class Padding { + private final IntersectionType t; + + @Nullable + public JContainer<TypeTree> getBounds() { + return t.bounds; + } + + public IntersectionType withBounds(@Nullable JContainer<TypeTree> bounds) { + return t.bounds == bounds ? t : new IntersectionType(t.id, t.prefix, t.markers, bounds); + } + } + } + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) @RequiredArgsConstructor diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java index 2759aefba45..3a5c672cd26 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java @@ -145,6 +145,47 @@ public MultiCatch unsafeSet(JavaType[] throwableTypes) { } } + class Intersection implements JavaType { + public Intersection(@Nullable List<JavaType> bounds) { + this.bounds = arrayOrNullIfEmpty(bounds, EMPTY_JAVA_TYPE_ARRAY); + } + + Intersection(@Nullable JavaType[] throwableTypes) { + this.bounds = nullIfEmpty(throwableTypes); + } + + @JsonCreator + Intersection() { + } + + private JavaType[] bounds; + + public List<JavaType> getBounds() { + if (bounds == null) { + return Collections.emptyList(); + } + return Arrays.asList(bounds); + } + + public Intersection withBounds(@Nullable List<JavaType> bounds) { + JavaType[] boundsArray = arrayOrNullIfEmpty(bounds, EMPTY_JAVA_TYPE_ARRAY); + if (Arrays.equals(boundsArray, this.bounds)) { + return this; + } + return new Intersection(boundsArray); + } + + public Intersection unsafeSet(List<JavaType> bounds) { + this.bounds = arrayOrNullIfEmpty(bounds, EMPTY_JAVA_TYPE_ARRAY); + return this; + } + + public Intersection unsafeSet(JavaType[] bounds) { + this.bounds = ListUtils.nullIfEmpty(bounds); + return this; + } + } + abstract class FullyQualified implements JavaType { public abstract String getFullyQualifiedName(); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java index 6ae31e43796..2d761bbc120 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/Space.java @@ -366,6 +366,7 @@ public enum Location { IMPORT_SUFFIX, INSTANCEOF_PREFIX, INSTANCEOF_SUFFIX, + INTERSECTION_TYPE_PREFIX, LABEL_PREFIX, LABEL_SUFFIX, LAMBDA_ARROW_PREFIX, diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java index cb0142f8d73..dde0e904535 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/TypeUtils.java @@ -212,6 +212,13 @@ public static boolean isAssignableTo(@Nullable JavaType to, @Nullable JavaType f } else if (toFq.getFullyQualifiedName().equals("java.lang.Object")) { return true; } + } else if (from instanceof JavaType.Intersection) { + for (JavaType intersectionType : ((JavaType.Intersection) from).getBounds()) { + if (isAssignableTo(to, intersectionType)) { + return true; + } + } + return false; } return !(from instanceof JavaType.GenericTypeVariable) && isAssignableTo(toFq.getFullyQualifiedName(), from); } else if (to instanceof JavaType.GenericTypeVariable) { @@ -334,6 +341,12 @@ public static boolean isAssignableTo(String to, @Nullable JavaType from) { return isAssignableTo(to, ((JavaType.Variable) from).getType()); } else if (from instanceof JavaType.Method) { return isAssignableTo(to, ((JavaType.Method) from).getReturnType()); + } else if (from instanceof JavaType.Intersection) { + for (JavaType bound : ((JavaType.Intersection) from).getBounds()) { + if (isAssignableTo(to, bound)) { + return true; + } + } } } catch (Exception e) { return false; From 8282949f8d53c192918c68fc0becccb300879bc1 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Tue, 21 Nov 2023 10:35:07 +0100 Subject: [PATCH 435/447] Support optional version selector in ParentPomInsight (#3715) --- .../maven/search/ParentPomInsight.java | 40 ++++++-- .../maven/search/ParentPomInsightTest.java | 95 ++++++++++++++++++- 2 files changed, 123 insertions(+), 12 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java b/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java index d54b992279c..38ab534a8f6 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/search/ParentPomInsight.java @@ -17,19 +17,15 @@ import lombok.EqualsAndHashCode; import lombok.Value; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Option; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.SearchResult; import org.openrewrite.maven.MavenIsoVisitor; import org.openrewrite.maven.table.ParentPomsInUse; import org.openrewrite.maven.tree.ResolvedPom; +import org.openrewrite.semver.Semver; import org.openrewrite.xml.tree.Xml; -import java.util.UUID; - -import static org.openrewrite.Tree.randomId; import static org.openrewrite.internal.StringUtils.matchesGlob; @EqualsAndHashCode(callSuper = true) @@ -47,7 +43,14 @@ public class ParentPomInsight extends Recipe { example = "spring-boot-starter-*") String artifactIdPattern; - UUID searchId = randomId(); + @Option(displayName = "Version", + description = "Match only dependencies with the specified version. " + + "Node-style [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors) may be used." + + "All versions are searched by default.", + example = "1.x", + required = false) + @Nullable + String version; @Override public String getDisplayName() { @@ -59,6 +62,16 @@ public String getDescription() { return "Find Maven parents matching a `groupId` and `artifactId`."; } + + @Override + public Validated<Object> validate() { + Validated<Object> v = super.validate(); + if (version != null) { + v = v.and(Semver.validate(version, null)); + } + return v; + } + @Override public TreeVisitor<?, ExecutionContext> getVisitor() { return new MavenIsoVisitor<ExecutionContext>() { @@ -70,10 +83,17 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { String groupId = resolvedPom.getValue(tag.getChildValue("groupId").orElse(null)); String artifactId = resolvedPom.getValue(tag.getChildValue("artifactId").orElse(null)); if (matchesGlob(groupId, groupIdPattern) && matchesGlob(artifactId, artifactIdPattern)) { - String version = resolvedPom.getValue(tag.getChildValue("version").orElse(null)); + String parentVersion = resolvedPom.getValue(tag.getChildValue("version").orElse(null)); + if (version != null) { + if (!Semver.validate(version, null).getValue() + .isValid(null, parentVersion)) { + return t; + } + } + // Found a parent pom that matches the criteria String relativePath = tag.getChildValue("relativePath").orElse(null); inUse.insertRow(ctx, new ParentPomsInUse.Row( - resolvedPom.getArtifactId(), groupId, artifactId, version, relativePath)); + resolvedPom.getArtifactId(), groupId, artifactId, parentVersion, relativePath)); return SearchResult.found(t); } } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java index 21a9bf29424..7101ed97a6b 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/search/ParentPomInsightTest.java @@ -28,7 +28,7 @@ class ParentPomInsightTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe(new ParentPomInsight("org.springframework.boot", "spring-boot-starter-parent")); + spec.recipe(new ParentPomInsight("org.springframework.boot", "spring-boot-starter-parent", null)); } @DocumentExample @@ -89,7 +89,7 @@ void findParent() { void multiModuleOnlyRoot() { rewriteRun( spec -> spec - .recipe(new ParentPomInsight("*", "*")) + .recipe(new ParentPomInsight("*", "*", null)) .dataTableAsCsv(ParentPomsInUse.class.getName(), """ projectArtifactId,groupId,artifactId,version,relativePath sample,org.springframework.boot,"spring-boot-starter-parent",2.5.0, @@ -203,4 +203,95 @@ void multiModuleOnlyRoot() { ) ); } + + @Test + void matchNonSnapshot() { + rewriteRun( + spec -> spec + .recipe(new ParentPomInsight("*", "*", "~2")) + .dataTableAsCsv(ParentPomsInUse.class.getName(), """ + projectArtifactId,groupId,artifactId,version,relativePath + sample,org.springframework.boot,"spring-boot-starter-parent",2.5.0, + """), + mavenProject("sample", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0-SNAPSHOT</version> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.5.0</version> + </parent> + + <modules> + <module>module1</module> + <module>module2</module> + </modules> + </project> + """, + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0-SNAPSHOT</version> + + <!--~~>--><parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.5.0</version> + </parent> + + <modules> + <module>module1</module> + <module>module2</module> + </modules> + </project> + """ + ), + mavenProject("module1", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0-SNAPSHOT</version> + <relativePath>../</relativePath> + </parent> + <artifactId>module1</artifactId> + </project> + """, + spec -> spec.path("module1/pom.xml") + )), + mavenProject("module2", + pomXml( + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.sample</groupId> + <artifactId>sample</artifactId> + <version>1.0.0-SNAPSHOT</version> + <relativePath>../</relativePath> + </parent> + <artifactId>module2</artifactId> + </project> + """, + spec -> spec.path("module2/pom.xml") + ) + ) + ) + ); + } } From c83adee5a9462e0774bc3fc267cf3c465e4ec1b2 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Tue, 21 Nov 2023 14:03:34 +0100 Subject: [PATCH 436/447] Change annotation attribute name to support Swagger migration (#3720) * Change annotation attribute name to support Swagger migration * Update ChangeAnnotationAttributeName.java * Make all fields required * Extract annotation matcher to field --- .../java/ChangeAnnotationAttributeName.java | 76 +++++++++++++++++++ .../ChangeAnnotationAttributeNameTest.java | 41 ++++++++++ 2 files changed, 117 insertions(+) create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/ChangeAnnotationAttributeName.java create mode 100644 rewrite-java/src/test/java/org/openrewrite/java/ChangeAnnotationAttributeNameTest.java diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeAnnotationAttributeName.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeAnnotationAttributeName.java new file mode 100644 index 00000000000..76c26097610 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeAnnotationAttributeName.java @@ -0,0 +1,76 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; + +@Value +@EqualsAndHashCode(callSuper = true) +public class ChangeAnnotationAttributeName extends Recipe { + @Override + public String getDisplayName() { + return "Change annotation attribute name"; + } + + @Override + public String getDescription() { + return "Some annotations accept arguments. This recipe renames an existing attribute."; + } + + @Option(displayName = "Annotation Type", + description = "The fully qualified name of the annotation.", + example = "org.junit.Test") + String annotationType; + + @Option(displayName = "Old attribute name", + description = "The name of attribute to change.", + example = "timeout") + String oldAttributeName; + + @Option(displayName = "New attribute name", + description = "The new attribute name to use.", + example = "waitFor") + String newAttributeName; + + @Override + public TreeVisitor<?, ExecutionContext> getVisitor() { + return Preconditions.check(new UsesType<>(annotationType, false), new JavaIsoVisitor<ExecutionContext>() { + private final AnnotationMatcher annotationMatcher = new AnnotationMatcher(annotationType); + @Override + public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext context) { + J.Annotation a = super.visitAnnotation(annotation, context); + if (!annotationMatcher.matches(a)) { + return a; + } + return a.withArguments(ListUtils.map(a.getArguments(), arg -> { + if (arg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) arg; + J.Identifier variable = (J.Identifier) assignment.getVariable(); + if (oldAttributeName.equals(variable.getSimpleName())) { + return assignment.withVariable(variable.withSimpleName(newAttributeName)); + } + } + return arg; + })); + } + }); + } +} diff --git a/rewrite-java/src/test/java/org/openrewrite/java/ChangeAnnotationAttributeNameTest.java b/rewrite-java/src/test/java/org/openrewrite/java/ChangeAnnotationAttributeNameTest.java new file mode 100644 index 00000000000..190fbc6acac --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/ChangeAnnotationAttributeNameTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 the original author or authors. + * <p> + * 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 + * <p> + * https://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ChangeAnnotationAttributeNameTest implements RewriteTest { + @Test + void renameAttributeName() { + rewriteRun( + spec -> spec.recipe(new ChangeAnnotationAttributeName("java.lang.Deprecated", "since", "asOf")), + //language=java + java( + """ + @Deprecated(since = "1.0") + class A {} + """, + """ + @Deprecated(asOf = "1.0") + class A {} + """ + ) + ); + } +} \ No newline at end of file From 04c79ff7466474bfb04d64090d531d34fcd197e3 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Tue, 21 Nov 2023 14:49:23 +0100 Subject: [PATCH 437/447] allow initialize the recipe multiple times (#3696) * allow initialize the recipe multiple times to properly load the recipes from YamlResourceLoader * fixed test * fixed lazy validation --- .../openrewrite/config/DeclarativeRecipe.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java b/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java index e213360c10d..a4a8ca20568 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/DeclarativeRecipe.java @@ -70,13 +70,13 @@ public boolean causesAnotherCycle() { private final List<Recipe> preconditions = new ArrayList<>(); public void addPrecondition(Recipe recipe) { - preconditions.add(recipe); + uninitializedPreconditions.add(recipe); } @JsonIgnore - private Validated<Object> validation = Validated.test("initialization", - "initialize(..) must be called on DeclarativeRecipe prior to use.", - this, r -> uninitializedRecipes.isEmpty()); + private Validated<Object> validation = Validated.none(); + @JsonIgnore + private Validated<Object> initValidation = null; @Override public Duration getEstimatedEffortPerOccurrence() { @@ -85,11 +85,13 @@ public Duration getEstimatedEffortPerOccurrence() { } public void initialize(Collection<Recipe> availableRecipes, Map<String, List<Contributor>> recipeToContributors) { + initValidation = Validated.none(); initialize(uninitializedRecipes, recipeList, availableRecipes, recipeToContributors); initialize(uninitializedPreconditions, preconditions, availableRecipes, recipeToContributors); } private void initialize(List<Recipe> uninitialized, List<Recipe> initialized, Collection<Recipe> availableRecipes, Map<String, List<Contributor>> recipeToContributors) { + initialized.clear(); for (int i = 0; i < uninitialized.size(); i++) { Recipe recipe = uninitialized.get(i); if (recipe instanceof LazyLoadedRecipe) { @@ -103,7 +105,7 @@ private void initialize(List<Recipe> uninitialized, List<Recipe> initialized, Co } initialized.add(subRecipe); } else { - validation = validation.and( + initValidation = initValidation.and( invalid(name + ".recipeList" + "[" + i + "] (in " + source + ")", recipeFqn, @@ -118,7 +120,6 @@ private void initialize(List<Recipe> uninitialized, List<Recipe> initialized, Co initialized.add(recipe); } } - uninitialized.clear(); } @Value @@ -304,7 +305,11 @@ public void addValidation(Validated<Object> validated) { @Override public Validated<Object> validate() { - return validation; + return Validated.<Object>test("initialization", + "initialize(..) must be called on DeclarativeRecipe prior to use.", + this, r -> initValidation != null) + .and(validation) + .and(initValidation); } @Value From a3e42ada53fe81a2bab1755e2902a1ddf8c10445 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 21 Nov 2023 15:23:08 +0100 Subject: [PATCH 438/447] Groovy: Fix parsing of parenthesized ranges --- .../org/openrewrite/groovy/GroovyParserVisitor.java | 3 ++- .../java/org/openrewrite/groovy/tree/RangeTest.java | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index 521452b8234..f0ee7f11ae8 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -1470,7 +1470,8 @@ public void visitMethodCallExpression(MethodCallExpression call) { ImplicitDot implicitDot = null; JRightPadded<Expression> select = null; if (!call.isImplicitThis()) { - Expression selectExpr = visit(call.getObjectExpression()); + Expression selectExpr = insideParentheses(call.getObjectExpression(), + prefix -> this.<Expression>visit(call.getObjectExpression()).withPrefix(prefix)); int saveCursor = cursor; Space afterSelect = whitespace(); if (source.charAt(cursor) == '.' || source.charAt(cursor) == '?' || source.charAt(cursor) == '*') { diff --git a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RangeTest.java b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RangeTest.java index e08704e4ef8..bdea313eb33 100644 --- a/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RangeTest.java +++ b/rewrite-groovy/src/test/java/org/openrewrite/groovy/tree/RangeTest.java @@ -33,4 +33,17 @@ void rangeExpression() { ) ); } + + @Test + void parenthesized() { + rewriteRun( + groovy( + """ + ( 8..19 ).each { majorVersion -> + if (majorVersion == 9) return + } + """ + ) + ); + } } From 4f64ad30ab23a5941a3dab1136fd6b56ec7333c5 Mon Sep 17 00:00:00 2001 From: Joan Viladrosa <joan@moderne.io> Date: Tue, 21 Nov 2023 15:59:44 +0100 Subject: [PATCH 439/447] fixed possible NPE when required methodPatternChain not provided (#3721) --- .../src/main/java/org/openrewrite/java/SimplifyMethodChain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java b/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java index 5b85ef3af3d..e64925ceabb 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/SimplifyMethodChain.java @@ -61,7 +61,7 @@ public String getDescription() { public Validated<Object> validate() { return super.validate().and(Validated.test("methodPatternChain", "Requires more than one pattern", - methodPatternChain, c -> c.size() > 1)); + methodPatternChain, c -> c != null && c.size() > 1)); } @Override From bc98ea02ef4b8f13bd1e1c4cb1981d9fcf0022d1 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Tue, 21 Nov 2023 16:20:54 +0100 Subject: [PATCH 440/447] More idiomatic parentheses handling in Groovy parser --- .../openrewrite/groovy/GroovyParserVisitor.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java index f0ee7f11ae8..b1ad61f3509 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParserVisitor.java @@ -1470,8 +1470,7 @@ public void visitMethodCallExpression(MethodCallExpression call) { ImplicitDot implicitDot = null; JRightPadded<Expression> select = null; if (!call.isImplicitThis()) { - Expression selectExpr = insideParentheses(call.getObjectExpression(), - prefix -> this.<Expression>visit(call.getObjectExpression()).withPrefix(prefix)); + Expression selectExpr = visit(call.getObjectExpression()); int saveCursor = cursor; Space afterSelect = whitespace(); if (source.charAt(cursor) == '.' || source.charAt(cursor) == '?' || source.charAt(cursor) == '*') { @@ -1691,10 +1690,10 @@ public void visitPropertyExpression(PropertyExpression prop) { @Override public void visitRangeExpression(RangeExpression range) { - queue.add(new G.Range(randomId(), whitespace(), Markers.EMPTY, + queue.add(insideParentheses(range, fmt -> new G.Range(randomId(), fmt, Markers.EMPTY, visit(range.getFrom()), JLeftPadded.build(range.isInclusive()).withBefore(sourceBefore(range.isInclusive() ? ".." : "..>")), - visit(range.getTo()))); + visit(range.getTo())))); } @Override @@ -2314,10 +2313,11 @@ private <G2 extends J> JRightPadded<G2> maybeSemicolon(G2 g) { private String name() { int i = cursor; - char c = source.charAt(i); - while (Character.isJavaIdentifierPart(c) || c == '.' || c == '*') { - i++; - c = source.charAt(i); + for (; i < source.length(); i++) { + char c = source.charAt(i); + if (!(Character.isJavaIdentifierPart(c) || c == '.' || c == '*')) { + break; + } } String result = source.substring(cursor, i); cursor += i - cursor; From 7ed7222b17f689d57c9392fd6430f82fe301da1b Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Tue, 21 Nov 2023 21:07:19 +0100 Subject: [PATCH 441/447] Parse files that start with Jenkinsfile as Groovy --- .../src/main/java/org/openrewrite/groovy/GroovyParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java index f9ed2f83e89..4a52ea654aa 100644 --- a/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java +++ b/rewrite-groovy/src/main/java/org/openrewrite/groovy/GroovyParser.java @@ -181,7 +181,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re @Override public boolean accept(Path path) { return path.toString().endsWith(".groovy") || - path.toFile().getName().equals("Jenkinsfile"); + path.toFile().getName().startsWith("Jenkinsfile"); } @Override From 775c2d5f90282e897a1f63175f0d3ab18bd17820 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Wed, 22 Nov 2023 04:24:33 -0500 Subject: [PATCH 442/447] Tentative fix for missing "//" operator on `XPathMatcher` (#3707) * Tentative fix for missing "//" operator on `XPathMatcher` * Remove empty comment line --------- Co-authored-by: Tim te Beek <tim@moderne.io> --- .../org/openrewrite/xml/XPathMatcher.java | 51 +++++++++++++++---- .../org/openrewrite/xml/XPathMatcherTest.java | 36 +++++++++++-- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/XPathMatcher.java b/rewrite-xml/src/main/java/org/openrewrite/xml/XPathMatcher.java index 59fde602234..bd7a0ae8d16 100644 --- a/rewrite-xml/src/main/java/org/openrewrite/xml/XPathMatcher.java +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/XPathMatcher.java @@ -15,6 +15,7 @@ */ package org.openrewrite.xml; +import org.openrewrite.internal.StringUtils; import org.openrewrite.Cursor; import org.openrewrite.xml.search.FindTags; import org.openrewrite.xml.tree.Xml; @@ -23,6 +24,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -37,12 +39,20 @@ * As a result, '.' and '..' are not recognized. */ public class XPathMatcher { + // Regular expression to support conditional tags like `plugin[artifactId='maven-compiler-plugin']` + private static final Pattern PATTERN = Pattern.compile("([-\\w]+)\\[([-\\w]+)='([-\\w]+)']"); private final String expression; public XPathMatcher(String expression) { this.expression = expression; } + /** + * Checks if the given XPath expression matches the provided cursor. + * + * @param cursor the cursor representing the XML document + * @return true if the expression matches the cursor, false otherwise + */ public boolean matches(Cursor cursor) { List<Xml.Tag> path = cursor.getPathAsStream() .filter(p -> p instanceof Xml.Tag) @@ -59,8 +69,8 @@ public boolean matches(Cursor cursor) { String part = parts.get(i); if (part.startsWith("@")) { if (!(cursor.getValue() instanceof Xml.Attribute && - (((Xml.Attribute) cursor.getValue()).getKeyAsString().equals(part.substring(1))) || - "*".equals(part.substring(1)))) { + (((Xml.Attribute) cursor.getValue()).getKeyAsString().equals(part.substring(1))) || + "*".equals(part.substring(1)))) { return false; } @@ -78,6 +88,31 @@ public boolean matches(Cursor cursor) { Collections.reverse(path); String[] parts = expression.substring(1).split("/"); + // Deal with the two forward slashes in the expression; works, but I'm not proud of it. + if (expression.contains("//") && Arrays.stream(parts).anyMatch(StringUtils::isBlank)) { + int blankPartIndex = Arrays.asList(parts).indexOf(""); + int doubleSlashIndex = expression.indexOf("//"); + + if (path.size() > blankPartIndex && path.size() >= parts.length - 1) { + String newExpression; + if (Objects.equals(path.get(blankPartIndex).getName(), parts[blankPartIndex + 1])) { + newExpression = String.format( + "%s/%s", + expression.substring(0, doubleSlashIndex), + expression.substring(doubleSlashIndex + 2) + ); + } else { + newExpression = String.format( + "%s/%s/%s", + expression.substring(0, doubleSlashIndex), + path.get(blankPartIndex).getName(), + expression.substring(doubleSlashIndex + 2) + ); + } + return new XPathMatcher(newExpression).matches(cursor); + } + } + if (parts.length > path.size() + 1) { return false; } @@ -88,10 +123,7 @@ public boolean matches(Cursor cursor) { Xml.Tag tag = i < path.size() ? path.get(i) : null; String partName; - // to support conditional tags like `plugin[artifactId='maven-compiler-plugin']` - String regex = "([-\\w]+)\\[([-\\w]+)='([-\\w]+)'\\]"; - Pattern pattern = Pattern.compile(regex); - Matcher matcher = pattern.matcher(part); + Matcher matcher = PATTERN.matcher(part); if (tag != null && matcher.matches()) { String name = matcher.group(1); String subTag = matcher.group(2); @@ -109,14 +141,13 @@ public boolean matches(Cursor cursor) { partName = part; } - if (part.startsWith("@")) { return cursor.getValue() instanceof Xml.Attribute && - (((Xml.Attribute) cursor.getValue()).getKeyAsString().equals(part.substring(1)) || - "*".equals(part.substring(1))); + (((Xml.Attribute) cursor.getValue()).getKeyAsString().equals(part.substring(1)) || + "*".equals(part.substring(1))); } - if (path.size() < i + 1 || (!tag.getName().equals(partName) && !"*".equals(part))) { + if (path.size() < i + 1 || (tag != null && !tag.getName().equals(partName) && !"*".equals(part))) { return false; } } diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/XPathMatcherTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/XPathMatcherTest.java index ff8c668801e..5e5fd36e563 100755 --- a/rewrite-xml/src/test/java/org/openrewrite/xml/XPathMatcherTest.java +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/XPathMatcherTest.java @@ -43,7 +43,7 @@ class XPathMatcherTest { """ ).toList().get(0); - private final SourceFile pomXml = new XmlParser().parse( + private final SourceFile pomXml1 = new XmlParser().parse( """ <project> <groupId>com.mycompany.app</groupId> @@ -66,6 +66,30 @@ class XPathMatcherTest { """ ).toList().get(0); + private final SourceFile pomXml2 = new XmlParser().parse( + """ + <project> + <groupId>com.mycompany.app</groupId> + <artifactId>my-app</artifactId> + <version>1</version> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.0</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> + </project> + """ + ).toList().get(0); @Test void matchAbsolute() { @@ -102,11 +126,15 @@ void matchRelativeAttribute() { @Test void matchPom() { assertThat(match("/project/build/plugins/plugin/configuration/source", - pomXml)).isTrue(); + pomXml1)).isTrue(); assertThat(match("/project/build/plugins/plugin[artifactId='maven-compiler-plugin']/configuration/source", - pomXml)).isTrue(); + pomXml1)).isTrue(); assertThat(match("/project/build/plugins/plugin[artifactId='somethingElse']/configuration/source", - pomXml)).isFalse(); + pomXml1)).isFalse(); + assertThat(match("/project/build//plugins/plugin/configuration/source", + pomXml1)).isTrue(); + assertThat(match("/project/build//plugins/plugin/configuration/source", + pomXml2)).isTrue(); } private boolean match(String xpath, SourceFile x) { From a9ab03f5be5bcc5e5679fffb3d3c6268a3dd174f Mon Sep 17 00:00:00 2001 From: Shannon Pamperl <shanman190@gmail.com> Date: Wed, 22 Nov 2023 03:55:20 -0600 Subject: [PATCH 443/447] AutoFormat bug with method parameter alignment when using tabs (#3724) * Test case reproducing autoformat bug for continuation indents and constructor arguments * Properly calculate indentation when using tabs * Fix off-by-1-error --------- Co-authored-by: Knut Wannheden <knut@moderne.io> --- .../java/format/TabsAndIndentsTest.java | 21 +++++++++++++++++++ .../java/format/TabsAndIndentsVisitor.java | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java index 8269b77ce67..902c143ac04 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/format/TabsAndIndentsTest.java @@ -173,6 +173,27 @@ private void firstArgOnNewLine( ); } + @Test + void alignMethodDeclarationParamsWhenContinuationIndentUsingTabs() { + rewriteRun( + tabsAndIndents(style -> style.withUseTabCharacter(true)), + java( + """ + import java.util.*; + + class Foo { + Foo( + String var1, + String var2, + String var3 + ) { + } + } + """ + ) + ); + } + // https://rules.sonarsource.com/java/tag/confusing/RSPEC-3973 @DocumentExample @SuppressWarnings("SuspiciousIndentAfterControlStatement") diff --git a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java index fb4ca5940a2..268a9d3258e 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/format/TabsAndIndentsVisitor.java @@ -235,7 +235,7 @@ public <T> JRightPadded<T> visitRightPadded(@Nullable JRightPadded<T> right, JRi if (method != null) { int alignTo; if (firstArg.getPrefix().getLastWhitespace().contains("\n")) { - alignTo = firstArg.getPrefix().getLastWhitespace().length() - 1; + alignTo = getLengthOfWhitespace(firstArg.getPrefix().getLastWhitespace()); } else { String source = method.print(getCursor()); int firstArgIndex = source.indexOf(firstArg.print(getCursor())); From 1055b336be6a8a1edc40e98e0b7bc574ee60fb04 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Wed, 22 Nov 2023 11:12:38 +0100 Subject: [PATCH 444/447] Update preconditions hint in yaml recipes (#3725) --- .../recipes/RemoveApplicabilityTestFromYamlRecipeTest.java | 4 ++-- .../recipes/RemoveApplicabilityTestFromYamlRecipe.java | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipeTest.java index f3960e9c199..d6affe9483a 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipeTest.java @@ -56,7 +56,7 @@ void commentOutApplicabilityTest() { description: x. tags: - testing - # Applicability tests are no longer supported for yaml recipes, please remove or require migrating the recipe to Java code + # Applicability tests are no longer supported for yaml recipes, please move to using preconditions # applicability: # anySource: # - org.openrewrite.java.testing.mockito.UsesMockitoAll @@ -94,7 +94,7 @@ void commentOutSingleSourceApplicability() { description: x. tags: - testing - # Applicability tests are no longer supported for yaml recipes, please remove or require migrating the recipe to Java code + # Applicability tests are no longer supported for yaml recipes, please move to using preconditions # applicability: # singleSource: # - org.openrewrite.java.testing.mockito.UsesMockitoAll diff --git a/rewrite-java/src/main/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipe.java b/rewrite-java/src/main/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipe.java index 183ef75f3f3..f42155f8b8b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipe.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipe.java @@ -38,8 +38,8 @@ public String getDisplayName() { @Override public String getDescription() { - return "Remove the applicability test from the YAML recipe when migrating from Rewrite 7 to 8, as it is no " + - "longer supported and may require migrating the recipe to Java code."; + return "Remove the applicability test from the YAML recipe when migrating from Rewrite 7 to 8, " + + "as these have been replaced by preconditions."; } @Override @@ -68,8 +68,7 @@ public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionC } }; - String commentText = "Applicability tests are no longer supported for yaml recipes, please remove or require " + - "migrating the recipe to Java code"; + String commentText = "Applicability tests are no longer supported for yaml recipes, please move to using preconditions"; return Preconditions.check(yamlRecipeCheckVisitor, new CommentOutProperty("applicability", commentText).getVisitor()); } From 4005e970a139000baaa40e1213211b2872656591 Mon Sep 17 00:00:00 2001 From: Knut Wannheden <knut@moderne.io> Date: Wed, 22 Nov 2023 13:40:25 +0100 Subject: [PATCH 445/447] Migrate single-source applicability tests to preconditions (#3726) Co-authored-by: Tim te Beek <tim@moderne.io> --- .../MigrateJavaTemplateToRewrite8Test.java | 2 +- ...veApplicabilityTestFromYamlRecipeTest.java | 21 ++++---- ...RemoveApplicabilityTestFromYamlRecipe.java | 48 ++++++++++++------- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateJavaTemplateToRewrite8Test.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateJavaTemplateToRewrite8Test.java index a7bff9a5b60..259e8613a3e 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateJavaTemplateToRewrite8Test.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/MigrateJavaTemplateToRewrite8Test.java @@ -138,7 +138,7 @@ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext context) { @SuppressWarnings("all") @Test - void templaveVariable() { + void templateVariable() { rewriteRun( java( """ diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipeTest.java index d6affe9483a..b2116e31b8f 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipeTest.java @@ -31,7 +31,7 @@ public void defaults(RecipeSpec spec) { @DocumentExample("Comment out applicability from yaml recipes as it's no longer supported and could require user to transform it to java.") @Test - void commentOutApplicabilityTest() { + void commentOutAnySourceApplicabilityTest() { //language=yaml rewriteRun( yaml( @@ -45,6 +45,8 @@ void commentOutApplicabilityTest() { applicability: anySource: - org.openrewrite.java.testing.mockito.UsesMockitoAll + singleSource: + - org.openrewrite.java.testing.mockito.UsesMockitoAll recipeList: - org.openrewrite.java.Recipe1 - org.openrewrite.java.Recipe2 @@ -56,20 +58,21 @@ void commentOutApplicabilityTest() { description: x. tags: - testing - # Applicability tests are no longer supported for yaml recipes, please move to using preconditions - # applicability: - # anySource: - # - org.openrewrite.java.testing.mockito.UsesMockitoAll + # Applicability tests are no longer supported for yaml recipes, please remove or require migrating the recipe to Java code + # anySource: + # - org.openrewrite.java.testing.mockito.UsesMockitoAll recipeList: - org.openrewrite.java.Recipe1 - org.openrewrite.java.Recipe2 + preconditions: + - org.openrewrite.java.testing.mockito.UsesMockitoAll """ ) ); } @Test - void commentOutSingleSourceApplicability() { + void migrateSingleSourceApplicability() { //language=yaml rewriteRun( yaml( @@ -94,13 +97,11 @@ void commentOutSingleSourceApplicability() { description: x. tags: - testing - # Applicability tests are no longer supported for yaml recipes, please move to using preconditions - # applicability: - # singleSource: - # - org.openrewrite.java.testing.mockito.UsesMockitoAll recipeList: - org.openrewrite.java.Recipe1 - org.openrewrite.java.Recipe2 + preconditions: + - org.openrewrite.java.testing.mockito.UsesMockitoAll """ ) ); diff --git a/rewrite-java/src/main/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipe.java b/rewrite-java/src/main/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipe.java index f42155f8b8b..6bc49d72428 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipe.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/recipes/RemoveApplicabilityTestFromYamlRecipe.java @@ -15,17 +15,18 @@ */ package org.openrewrite.java.recipes; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Preconditions; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; +import org.openrewrite.internal.lang.Nullable; import org.openrewrite.marker.SearchResult; +import org.openrewrite.yaml.ChangePropertyKey; import org.openrewrite.yaml.CommentOutProperty; import org.openrewrite.yaml.YamlIsoVisitor; import org.openrewrite.yaml.tree.Yaml; import org.openrewrite.yaml.tree.YamlKey; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; @@ -33,13 +34,13 @@ public class RemoveApplicabilityTestFromYamlRecipe extends Recipe { @Override public String getDisplayName() { - return "Remove applicability test from Yaml recipe"; + return "Remove any-source applicability and migrate single-source applicability tests in YAML recipe"; } @Override public String getDescription() { - return "Remove the applicability test from the YAML recipe when migrating from Rewrite 7 to 8, " + - "as these have been replaced by preconditions."; + return "Removes any-source applicability tests from YAML recipes, as the are no " + + "longer supported in Rewrite 8 and migrates single-source applicability tests to preconditions."; } @Override @@ -53,23 +54,36 @@ public TreeVisitor<?, ExecutionContext> getVisitor() { @Override public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext ctx) { List<String> keys = getCursor().getPathAsStream() - .filter(Yaml.Mapping.Entry.class::isInstance) - .map(Yaml.Mapping.Entry.class::cast) - .map(Yaml.Mapping.Entry::getKey) - .map(YamlKey::getValue) - .collect(Collectors.toList()); + .filter(Yaml.Mapping.Entry.class::isInstance) + .map(Yaml.Mapping.Entry.class::cast) + .map(Yaml.Mapping.Entry::getKey) + .map(YamlKey::getValue) + .collect(Collectors.toList()); Collections.reverse(keys); String prop = String.join(".", keys); - if (prop.equals("applicability.singleSource") || (prop.equals("applicability.anySource"))) { + if (prop.equals("applicability.anySource") || prop.equals("applicability.singleSource")) { return SearchResult.found(entry); } return super.visitMappingEntry(entry, ctx); } }; - String commentText = "Applicability tests are no longer supported for yaml recipes, please move to using preconditions"; - return Preconditions.check(yamlRecipeCheckVisitor, new CommentOutProperty("applicability", - commentText).getVisitor()); + return Preconditions.check(yamlRecipeCheckVisitor, new YamlIsoVisitor<ExecutionContext>() { + @Override + public Yaml visit(@Nullable Tree tree, ExecutionContext ctx) { + Tree visited = new ChangePropertyKey( + "applicability.singleSource", + "preconditions", + false, + null + ).getVisitor().visit(tree, ctx); + + String commentText = "Applicability tests are no longer supported for yaml recipes, please remove or require " + + "migrating the recipe to Java code"; + return (Yaml) new CommentOutProperty("applicability.anySource", commentText).getVisitor().visit(visited, ctx); + } + } + ); } } From d9ea65b505c98cbd6edd50bb144f184f5a33eea6 Mon Sep 17 00:00:00 2001 From: Tim te Beek <tim@moderne.io> Date: Wed, 22 Nov 2023 14:28:24 +0100 Subject: [PATCH 446/447] Prevent NPE as reported due to null normalized repository (#3727) Possibly due to mirrors applied with property placeholders --- .../maven/internal/MavenPomDownloader.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java index 8feca1d100a..de80a2fed6d 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/MavenPomDownloader.java @@ -466,8 +466,8 @@ public Pom download(GroupArtifactVersion gav, // The requested gav might itself have an unresolved placeholder in the version, so also check raw values for (Pom projectPom : projectPoms.values()) { if (gav.getGroupId().equals(projectPom.getGroupId()) && - gav.getArtifactId().equals(projectPom.getArtifactId()) && - (gav.getVersion().equals(projectPom.getVersion()) || projectPom.getVersion().equals(projectPom.getValue(gav.getVersion())))) { + gav.getArtifactId().equals(projectPom.getArtifactId()) && + (gav.getVersion().equals(projectPom.getVersion()) || projectPom.getVersion().equals(projectPom.getValue(gav.getVersion())))) { return projectPom; } } @@ -647,7 +647,7 @@ private Collection<MavenRepository> distinctNormalizedRepositories(List<MavenRep @Nullable ResolvedPom containingPom, @Nullable String acceptsVersion) { Set<MavenRepository> normalizedRepositories = new LinkedHashSet<>(); - if(addDefaultRepositories) { + if (addDefaultRepositories) { normalizedRepositories.add(ctx.getLocalRepository()); } @@ -665,8 +665,11 @@ private Collection<MavenRepository> distinctNormalizedRepositories(List<MavenRep normalizedRepositories.add(normalizedRepo); } } - if(addDefaultRepositories) { - normalizedRepositories.add(normalizeRepository(MavenRepository.MAVEN_CENTRAL, containingPom)); + if (addDefaultRepositories) { + MavenRepository normalizedRepo = normalizeRepository(MavenRepository.MAVEN_CENTRAL, containingPom); + if (normalizedRepo != null) { + normalizedRepositories.add(normalizedRepo); + } } return normalizedRepositories; } @@ -762,12 +765,12 @@ public MavenRepository normalizeRepository(MavenRepository originalRepository, @ } catch (Throwable e) { // ok to fall through here and cache a null if (nullReasonConsumer != null) { - nullReasonConsumer.accept(t); + nullReasonConsumer.accept(e); } } } } - if(normalized == null) { + if (normalized == null) { if (nullReasonConsumer != null) { nullReasonConsumer.accept(t); } From aa84b6138c71ad5222d0d2f670a7f6a56a78e335 Mon Sep 17 00:00:00 2001 From: Michael Keppler <bananeweizen@gmx.de> Date: Wed, 22 Nov 2023 20:28:30 +0100 Subject: [PATCH 447/447] Don't use static import on collision with local method (#3729) If a class declares a method with the same name as a candidate for a static import, then that will always lead to compile errors if the static import is applied. Therefore the recipe must check for not having any similarly named method locally. --- .../openrewrite/java/UseStaticImportTest.java | 23 +++++++++++++++++++ .../org/openrewrite/java/UseStaticImport.java | 6 ++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/UseStaticImportTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/UseStaticImportTest.java index dda2e94b6c9..88b52122cef 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/UseStaticImportTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/UseStaticImportTest.java @@ -93,6 +93,29 @@ public void methodWithTypeParameter() { ) ); } + + @Test + void sameMethodLocallyNoStaticImport() { + rewriteRun( + spec -> spec.recipe(new UseStaticImport("java.util.Collections emptyList()")), + java( + """ + import java.util.Collections; + import java.util.List; + + public class SameMethodNameLocally { + public void avoidCollision() { + List<Object> list = Collections.emptyList(); + } + + private int emptyList(String canHaveDifferentArguments) { + } + } + """ + ) + ); + } + @Test void methodInvocationsHavingNullSelect() { rewriteRun( diff --git a/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java b/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java index c24666e1755..68d19a0df8a 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/UseStaticImport.java @@ -18,6 +18,7 @@ import lombok.*; import lombok.experimental.FieldDefaults; import org.openrewrite.*; +import org.openrewrite.java.search.DeclaresMethod; import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -50,7 +51,10 @@ public String getDescription() { @Override public TreeVisitor<?, ExecutionContext> getVisitor() { - return Preconditions.check(new UsesMethod<>(methodPattern), new UseStaticImportVisitor()); + int indexSpace = methodPattern.indexOf(' '); + int indexBrace = methodPattern.indexOf('(', indexSpace); + String identicalMethodName = "* " + methodPattern.substring(indexSpace, indexBrace) + "(..)"; + return Preconditions.check(Preconditions.and(new UsesMethod<>(methodPattern), Preconditions.not(new DeclaresMethod<>(identicalMethodName))), new UseStaticImportVisitor()); } private class UseStaticImportVisitor extends JavaIsoVisitor<ExecutionContext> {