Better rewrite recipe integration into IDE quick fix and assist

This commit is contained in:
aboyko
2022-06-22 15:17:14 -04:00
committed by aboyko
parent a02f8d378d
commit 722a8cf6d5
47 changed files with 1687 additions and 1466 deletions

View File

@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.commons.java;
import java.io.File;
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -145,4 +146,25 @@ public class SpringProjectUtil {
}
return null;
}
public static Predicate<IJavaProject> springBootVersionGreaterOrEqual(int major, int minor, int patch) {
return project -> {
Version version = getDependencyVersion(project, SPRING_BOOT);
if (version == null) {
return false;
}
if (major > version.getMajor()) {
return false;
}
if (major == version.getMajor()) {
if (minor > version.getMinor()) {
return false;
}
if (minor == version.getMinor()) {
return patch <= version.getPatch();
}
}
return true;
};
}
}

View File

@@ -666,7 +666,9 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
@Override
public void checkPointCollecting() {
// publish what has been collected so far
documents.setQuickfixes(docId, quickfixes);
documents.publishDiagnostics(docId, diagnostics);
log.debug("Reconcile checkpoint sent {} diagnostics", diagnostics.size());
}
@Override

View File

@@ -43,6 +43,16 @@
<artifactId>rewrite-java</artifactId>
<version>${rewrite-version}</version>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java-11</artifactId>
<version>${rewrite-version}</version>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java-17</artifactId>
<version>${rewrite-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
@@ -81,11 +91,6 @@
<version>${rewrite-spring-version}</version>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java-11</artifactId>
<version>${rewrite-version}</version>
</dependency>
</dependencies>

View File

@@ -57,7 +57,7 @@ public class LoadUtils {
Recipe recipe = constructRecipe(recipeClazz, d.getOptions());
return recipe;
} catch (ClassNotFoundException e) {
DeclarativeRecipe recipe = new DeclarativeRecipe(d.getName(), d.getDisplayName(), d.getDescription(), d.getTags(), d.getEstimatedEffortPerOccurrence(), d.getSource());
DeclarativeRecipe recipe = new DeclarativeRecipe(d.getName(), d.getDisplayName(), d.getDescription(), d.getTags(), d.getEstimatedEffortPerOccurrence(), d.getSource(), false);
for (RecipeDescriptor subDescriptor : d.getRecipeList()) {
recipe.doNext(createRecipe(subDescriptor));
}

View File

@@ -0,0 +1,60 @@
package org.springframework.ide.vscode.commons.rewrite.java;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openrewrite.java.tree.J.Annotation;
import org.openrewrite.java.tree.JavaType.FullyQualified;
import org.openrewrite.java.tree.TypeUtils;
import com.google.common.collect.ImmutableList;
public class AnnotationHierarchies {
public static Collection<FullyQualified> getDirectSuperAnnotations(FullyQualified type, Predicate<FullyQualified> ignore) {
List<FullyQualified> annotations = type.getAnnotations();
if (annotations != null && !annotations.isEmpty()) {
ImmutableList.Builder<FullyQualified> superAnnotations = ImmutableList.builder();
for (FullyQualified ab : annotations) {
if (ignore == null || !ignore.test(ab)) {
superAnnotations.add(ab);
}
}
return superAnnotations.build();
}
return ImmutableList.of();
}
public static Set<String> getTransitiveSuperAnnotations(FullyQualified type, Predicate<FullyQualified> ignore) {
Set<String> seen = new HashSet<>();
if (type != null) {
findTransitiveSupers(type, seen, ignore).collect(Collectors.toList());
}
return seen;
}
public static Stream<FullyQualified> findTransitiveSupers(FullyQualified type, Set<String> seen, Predicate<FullyQualified> ignore) {
String qname = type.getFullyQualifiedName();
if (seen.add(qname)) {
return Stream.concat(Stream.of(type), getDirectSuperAnnotations(type, ignore).stream()
.flatMap(superAnnotation -> findTransitiveSupers(superAnnotation, seen, ignore)));
}
return Stream.empty();
}
public static boolean isSubtypeOf(Annotation annotation, String fqAnnotationTypeName) {
FullyQualified annotationType = TypeUtils.asFullyQualified(annotation.getType());
if (annotationType != null) {
return findTransitiveSupers(annotationType, new HashSet<>(), null)
.anyMatch(superType -> fqAnnotationTypeName.equals(superType.getFullyQualifiedName()));
}
return false;
}
}

View File

@@ -25,6 +25,7 @@ import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.RemoveAnnotationVisitor;
import org.openrewrite.java.search.UsesType;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.J.Block;
import org.openrewrite.java.tree.J.ClassDeclaration;
@@ -36,15 +37,14 @@ import org.openrewrite.java.tree.Statement;
import org.openrewrite.java.tree.TypeTree;
import org.openrewrite.java.tree.TypeUtils;
public class ConvertAutowiredParameterIntoConstructorParameter extends Recipe {
public class ConvertAutowiredFieldIntoConstructorParameter extends Recipe {
private static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
private String classFqName;
private String fieldName;
public ConvertAutowiredParameterIntoConstructorParameter(String classFqName, String fieldName) {
super();
public ConvertAutowiredFieldIntoConstructorParameter(String classFqName, String fieldName) {
this.classFqName = classFqName;
this.fieldName = fieldName;
}
@@ -53,6 +53,11 @@ public class ConvertAutowiredParameterIntoConstructorParameter extends Recipe {
public String getDisplayName() {
return "Convert autowired field into constructor parameter";
}
@Override
protected TreeVisitor<?, ExecutionContext> getSingleSourceApplicableTest() {
return new UsesType<ExecutionContext>(AUTOWIRED);
}
@Override
protected TreeVisitor<?, ExecutionContext> getVisitor() {
@@ -72,7 +77,7 @@ public class ConvertAutowiredParameterIntoConstructorParameter extends Recipe {
VariableDeclarations mv = multiVariable;
if (blockCursor != null && blockCursor.getParent().getValue() instanceof ClassDeclaration
&& multiVariable.getVariables().size() == 1
&& fieldName.equals(multiVariable.getVariables().get(0).getName().printTrimmed())) {
&& fieldName.equals(multiVariable.getVariables().get(0).getSimpleName())) {
mv = (VariableDeclarations) new RemoveAnnotationVisitor(new AnnotationMatcher("@" + AUTOWIRED)).visit(multiVariable, p);
doAfterVisit(new AddContructorParameterVisitor(classFqName, fieldName, multiVariable.getTypeExpression()));

View File

@@ -0,0 +1,81 @@
package org.springframework.ide.vscode.commons.rewrite.java;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import org.openrewrite.marker.Marker;
public class FixAssistMarker implements Marker {
private UUID id;
private UUID scope;
private String recipeId;
private Map<String, Object> parameters = Collections.emptyMap();
public FixAssistMarker(UUID id) {
super();
this.id = id;
}
@Override
public UUID getId() {
return id;
}
@SuppressWarnings("unchecked")
@Override
public FixAssistMarker withId(UUID id) {
this.id = id;
return this;
}
public FixAssistMarker withScope(UUID scope) {
this.scope = scope;
return this;
}
public UUID getScope() {
return scope;
}
public FixAssistMarker withRecipeId(String recipeId) {
this.recipeId = recipeId;
return this;
}
public String getRecipeId() {
return recipeId;
}
public FixAssistMarker withParameters(Map<String, Object> parameters) {
this.parameters = parameters;
return this;
}
public Map<String, Object> getParameters() {
return parameters;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
FixAssistMarker other = (FixAssistMarker) obj;
return Objects.equals(id, other.id);
}
}

View File

@@ -16,6 +16,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -23,12 +24,17 @@ import org.openrewrite.ExecutionContext;
import org.openrewrite.InMemoryExecutionContext;
import org.openrewrite.Parser;
import org.openrewrite.Recipe;
import org.openrewrite.Result;
import org.openrewrite.Tree;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.RecipeIntrospectionUtils;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.UpdateSourcePositions;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.J.CompilationUnit;
import org.openrewrite.marker.Range;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -172,34 +178,34 @@ public class ORAstUtils {
//
// }
//
// public static J findAstNodeAt(CompilationUnit cu, int offset) {
// AtomicReference<J> f = new AtomicReference<>();
// new JavaIsoVisitor<AtomicReference<J>>() {
// public J visit(Tree tree, AtomicReference<J> found) {
// if (tree == null) {
// return null;
// }
// if (found.get() == null && tree instanceof J) {
// J node = (J) tree;
// Range range = node.getMarkers().findFirst(Range.class).orElse(null);
// if (range != null
// && range.getStart().getOffset() <= offset
// && offset <= range.getEnd().getOffset()) {
// super.visit(tree, found);
// if (found.get() == null) {
// found.set(node);
// return node;
// }
// } else {
// return (J) tree;
// }
// }
// return (J) tree;
// };
// }.visitNonNull(cu, f);
// return f.get();
// }
//
public static J findAstNodeAt(CompilationUnit cu, int offset) {
AtomicReference<J> f = new AtomicReference<>();
new JavaIsoVisitor<AtomicReference<J>>() {
public J visit(Tree tree, AtomicReference<J> found) {
if (tree == null) {
return null;
}
if (found.get() == null && tree instanceof J) {
J node = (J) tree;
Range range = node.getMarkers().findFirst(Range.class).orElse(null);
if (range != null
&& range.getStart().getOffset() <= offset
&& offset <= range.getEnd().getOffset()) {
super.visit(tree, found);
if (found.get() == null) {
found.set(node);
return node;
}
} else {
return (J) tree;
}
}
return (J) tree;
};
}.visitNonNull(cu, f);
return f.get();
}
// @SuppressWarnings("unchecked")
// public static <T> T findNode(J node, Class<T> clazz) {
// if (clazz.isInstance(node)) {
@@ -214,20 +220,18 @@ public class ORAstUtils {
public static List<CompilationUnit> parse(JavaParser parser, Iterable<Path> sourceFiles) {
InMemoryExecutionContext ctx = new InMemoryExecutionContext(e -> log.error("", e));
ctx.putMessage(JavaParser.SKIP_SOURCE_SET_TYPE_GENERATION, true);
// ctx.putMessage(JavaParser.SKIP_SOURCE_SET_TYPE_GENERATION, true);
List<CompilationUnit> cus = parser.parse(sourceFiles, null, ctx);
return cus;
// List<Result> results = new UpdateSourcePositions().doNext(new MarkParentRecipe()).run(cus);
// return results.stream().map(r -> r.getAfter() == null ? r.getBefore() : r.getAfter()).map(CompilationUnit.class::cast).collect(Collectors.toList());
List<Result> results = new UpdateSourcePositions()/*.doNext(new MarkParentRecipe())*/.run(cus);
return results.stream().map(r -> r.getAfter() == null ? r.getBefore() : r.getAfter()).map(CompilationUnit.class::cast).collect(Collectors.toList());
}
public static List<CompilationUnit> parseInputs(JavaParser parser, Iterable<Parser.Input> inputs) {
InMemoryExecutionContext ctx = new InMemoryExecutionContext(e -> log.error("", e));
ctx.putMessage(JavaParser.SKIP_SOURCE_SET_TYPE_GENERATION, true);
// ctx.putMessage(JavaParser.SKIP_SOURCE_SET_TYPE_GENERATION, true);
List<CompilationUnit> cus = parser.parseInputs(inputs, null, ctx);
return cus;
// List<Result> results = new UpdateSourcePositions().doNext(new MarkParentRecipe()).run(cus);
// return results.stream().map(r -> r.getAfter() == null ? r.getBefore() : r.getAfter()).map(CompilationUnit.class::cast).collect(Collectors.toList());
List<Result> results = new UpdateSourcePositions()/*.doNext(new MarkParentRecipe())*/.run(cus);
return results.stream().map(r -> r.getAfter() == null ? r.getBefore() : r.getAfter()).map(CompilationUnit.class::cast).collect(Collectors.toList());
}
public static J.EnumValueSet getEnumValues(J.ClassDeclaration classDecl) {
@@ -260,17 +264,6 @@ public class ORAstUtils {
return fqName;
}
@SuppressWarnings("unchecked")
private static TreeVisitor<?, ExecutionContext> getVisitor(Recipe r) {
try {
Method m = Recipe.class.getDeclaredMethod("getVisitor");
m.setAccessible(true);
return (TreeVisitor<?, ExecutionContext>) m.invoke(r);
} catch (Exception e) {
return null;
}
}
@SuppressWarnings("unchecked")
private static List<TreeVisitor<J, ExecutionContext>> getAfterVisitors(TreeVisitor<J, ExecutionContext> visitor) {
try {
@@ -298,7 +291,7 @@ public class ORAstUtils {
@SuppressWarnings("unchecked")
public static Recipe nodeRecipe(Recipe r, Predicate<J> condition) {
return new NodeRecipe((JavaVisitor<ExecutionContext>) getVisitor(r), condition);
return new NodeRecipe((JavaVisitor<ExecutionContext>) RecipeIntrospectionUtils.recipeVisitor(r), condition);
}
private static class NodeRecipe extends Recipe {

View File

@@ -1,131 +0,0 @@
/*******************************************************************************
* Copyright (c) 2022 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.rewrite.maven;
import java.util.Objects;
import java.util.Optional;
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.MavenVisitor;
import org.openrewrite.xml.AddToTagVisitor;
import org.openrewrite.xml.ChangeTagValueVisitor;
import org.openrewrite.xml.RemoveContentVisitor;
import org.openrewrite.xml.tree.Xml;
public class ChangeDependencyClassifier extends Recipe {
@Option(displayName = "Group",
description = "The first part of a dependency coordinate 'com.google.guava:guava:VERSION'.",
example = "com.google.guava")
String groupId;
@Option(displayName = "Artifact",
description = "The second part of a dependency coordinate 'com.google.guava:guava:VERSION'.",
example = "guava")
String artifactId;
/**
* If null, strips the scope from an existing dependency.
*/
@Option(displayName = "New classifier",
description = "Classifier to apply to specified Maven dependency. " +
"May be omitted, which indicates that no classifier should be added and any existing scope be removed from the dependency.",
example = "jar",
required = false)
@Nullable
String newClassifier;
@Override
public String getDisplayName() {
return "Change Maven dependency classifier";
}
@Override
public String getDescription() {
return "Add or alter the classifier of the specified dependency.";
}
@Override
protected TreeVisitor<?, ExecutionContext> getVisitor() {
return new MavenVisitor<ExecutionContext>() {
@Override
public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
if (isDependencyTag()) {
if (groupId.equals(tag.getChildValue("groupId").orElse(getResolutionResult().getPom().getGroupId())) &&
artifactId.equals(tag.getChildValue("artifactId").orElse(null))) {
Optional<Xml.Tag> scope = tag.getChild("classifier");
if (scope.isPresent()) {
if (newClassifier == null) {
doAfterVisit(new RemoveContentVisitor<>(scope.get(), false));
} else if (!newClassifier.equals(scope.get().getValue().orElse(null))) {
doAfterVisit(new ChangeTagValueVisitor<>(scope.get(), newClassifier));
}
} else if (newClassifier != null) {
doAfterVisit(new AddToTagVisitor<>(tag, Xml.Tag.build("<classifier>" + newClassifier + "</classifier>")));
}
}
}
return super.visitTag(tag, ctx);
}
};
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getArtifactId() {
return artifactId;
}
public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}
public String getNewClassifier() {
return newClassifier;
}
public void setNewClassifier(String newClassifier) {
this.newClassifier = newClassifier;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(artifactId, groupId, newClassifier);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
ChangeDependencyClassifier other = (ChangeDependencyClassifier) obj;
return Objects.equals(artifactId, other.artifactId) && Objects.equals(groupId, other.groupId)
&& Objects.equals(newClassifier, other.newClassifier);
}
}

View File

@@ -18,6 +18,10 @@ name: org.openrewrite.java.spring.boot3.MavenPomUpgrade
displayName: Upgrade Maven Pom to Spring Boot 3.0 from 2.x
description: 'Upgrade Maven Pom to Spring Boot 3.0 from prior 2.x version.'
recipeList:
- org.openrewrite.maven.ChangeDependencyClassifier:
groupId: org.ehcache
artifactId: ehcache
newClassifier: jakarta
- org.openrewrite.maven.UpgradeDependencyVersion:
groupId: org.springframework.boot
artifactId: "*"

View File

@@ -33,9 +33,10 @@ public class LoadUtilsTest {
@Test
public void createRecipeTest() throws Exception {
RecipeDescriptor recipeDescriptor = env.listRecipeDescriptors().stream().filter(d -> "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0".equals(d.getName())).findFirst().orElse(null);
Recipe r = env.listRecipes().stream().filter(d -> "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0".equals(d.getName())).findFirst().orElse(null);
RecipeDescriptor recipeDescriptor = r.getDescriptor();
assertNotNull(recipeDescriptor);
Recipe r = LoadUtils.createRecipe(recipeDescriptor);
r = LoadUtils.createRecipe(recipeDescriptor);
assertTrue(r instanceof DeclarativeRecipe);
assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", r.getName());
@@ -48,9 +49,9 @@ public class LoadUtilsTest {
assertEquals("org.openrewrite.java.spring.boot3.MavenPomUpgrade", pomRecipe.getName());
assertEquals("Upgrade Maven Pom to Spring Boot 3.0 from prior 2.x version.", pomRecipe.getDescription());
assertEquals("Upgrade Maven Pom to Spring Boot 3.0 from 2.x", pomRecipe.getDisplayName());
assertEquals(3, pomRecipe.getRecipeList().size());
assertEquals(4, pomRecipe.getRecipeList().size());
r = pomRecipe.getRecipeList().get(0);
r = pomRecipe.getRecipeList().get(1);
assertTrue(r instanceof UpgradeDependencyVersion);
UpgradeDependencyVersion upgradeDependencyRecipe = (UpgradeDependencyVersion) r;
assertEquals("org.openrewrite.maven.UpgradeDependencyVersion", upgradeDependencyRecipe.getName());

View File

@@ -109,8 +109,8 @@
<commons-codec-version>1.13</commons-codec-version>
<!-- Rewrite specific properties -->
<rewrite-version>7.23.0</rewrite-version>
<rewrite-spring-version>4.21.0</rewrite-spring-version>
<rewrite-version>7.24.1</rewrite-version>
<rewrite-spring-version>4.22.1</rewrite-spring-version>
<rewrite-jackson.version>2.13.2</rewrite-jackson.version>
<signing.skip>true</signing.skip>