Better rewrite recipe integration into IDE quick fix and assist
This commit is contained in:
@@ -47,20 +47,6 @@ public class SpringBootLanguageServer extends STS4LanguageServerProcessStreamCon
|
||||
args.add("-Xmx1024m");
|
||||
args.add("-XX:TieredStopAtLevel=1");
|
||||
|
||||
args.add("--add-modules=ALL-SYSTEM");
|
||||
args.add("--add-exports");
|
||||
args.add("jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED");
|
||||
args.add("--add-exports");
|
||||
args.add("jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED");
|
||||
args.add("--add-exports");
|
||||
args.add("jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED");
|
||||
args.add("--add-exports");
|
||||
args.add("jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED");
|
||||
args.add("--add-exports");
|
||||
args.add("jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED");
|
||||
args.add("--add-exports");
|
||||
args.add("jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED");
|
||||
|
||||
addCustomJVMArgs(args);
|
||||
|
||||
return args;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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: "*"
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -48,7 +48,7 @@ import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessConnec
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessConnectorRemote.RemoteBootAppData;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessConnectorService;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDataProvider;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
@@ -219,8 +219,8 @@ public class BootLanguageServerBootApp {
|
||||
return SourceLinkFactory.createSourceLinks(server, cuCache, params.projectFinder);
|
||||
}
|
||||
|
||||
@Bean ORCompilationUnitCache orcuCache(SimpleLanguageServer server, BootLanguageServerParams params) {
|
||||
return new ORCompilationUnitCache(params.projectFinder, server, params.projectObserver);
|
||||
@Bean RewriteCompilationUnitCache orcuCache(SimpleLanguageServer server, BootLanguageServerParams params, RewriteRecipeRepository repo) {
|
||||
return new RewriteCompilationUnitCache(params.projectFinder, server, params.projectObserver);
|
||||
}
|
||||
|
||||
@Bean CompilationUnitCache cuCache(SimpleLanguageServer server, BootLanguageServerParams params) {
|
||||
@@ -302,8 +302,8 @@ public class BootLanguageServerBootApp {
|
||||
return new FutureProjectFinder(projectFinder, projectObserver);
|
||||
}
|
||||
|
||||
@Bean RewriteRefactorings rewriteRefactorings(ApplicationContext appContext) {
|
||||
return new RewriteRefactorings();
|
||||
@Bean RewriteRefactorings rewriteRefactorings(SimpleLanguageServer server, JavaProjectFinder projectFinder, RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache) {
|
||||
return new RewriteRefactorings(server.getTextDocumentService(), projectFinder, recipeRepo, cuCache);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -12,22 +12,12 @@ package org.springframework.ide.vscode.boot.app;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.AutowiredConstructorReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.BeanMethodNotPublicReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteCodeActionHandler;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNoPublicCodeAction;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.ConvertAutowiredField;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMapping;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMappings;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.UnnecessarySpringExtensionCodeAction;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.quickfix.AutowiredConstructorQuickFixHandler;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.quickfix.BeanMethodNoPublicQuickFixHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
|
||||
@@ -38,56 +28,27 @@ public class RewriteConfig implements InitializingBean {
|
||||
private SimpleLanguageServer server;
|
||||
|
||||
@Autowired
|
||||
private JavaProjectFinder projectFinder;
|
||||
private RewriteRecipeRepository recipeRepo;
|
||||
|
||||
@Autowired
|
||||
private ORCompilationUnitCache orCuCache;
|
||||
private RewriteRefactorings rewriteRefactorings;
|
||||
|
||||
@Bean
|
||||
ConvertAutowiredField convertAutowiredField(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, RewriteRecipeRepository recipesRepo,
|
||||
ORCompilationUnitCache orCuCache) {
|
||||
return new ConvertAutowiredField(server, projectFinder, rewriteRefactorings, orCuCache);
|
||||
}
|
||||
|
||||
@ConditionalOnClass({org.openrewrite.java.spring.NoRequestMappingAnnotation.class})
|
||||
@Bean
|
||||
NoRequestMapping noRequestMapping(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, RewriteRecipeRepository recipesRepo,
|
||||
ORCompilationUnitCache orCuCache) {
|
||||
return new NoRequestMapping(server, projectFinder, rewriteRefactorings, orCuCache);
|
||||
}
|
||||
|
||||
@ConditionalOnClass({org.openrewrite.java.spring.NoRequestMappingAnnotation.class})
|
||||
@Bean
|
||||
NoRequestMappings noRequestMappings(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, RewriteRecipeRepository recipesRepo,
|
||||
ORCompilationUnitCache orCuCache) {
|
||||
return new NoRequestMappings(server, projectFinder, rewriteRefactorings, orCuCache);
|
||||
}
|
||||
|
||||
@ConditionalOnClass({org.openrewrite.java.spring.BeanMethodsNotPublic.class})
|
||||
@Bean
|
||||
BeanMethodsNoPublicCodeAction beanMethodsNoPublic(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, RewriteRecipeRepository recipesRepo,
|
||||
ORCompilationUnitCache orCuCache) {
|
||||
return new BeanMethodsNoPublicCodeAction(server, projectFinder, rewriteRefactorings, orCuCache);
|
||||
}
|
||||
|
||||
@ConditionalOnClass({org.openrewrite.java.spring.boot2.UnnecessarySpringExtension.class})
|
||||
@Bean
|
||||
UnnecessarySpringExtensionCodeAction unnecessarySpringExtension(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, RewriteRecipeRepository recipesRepo,
|
||||
ORCompilationUnitCache orCuCache) {
|
||||
return new UnnecessarySpringExtensionCodeAction(server, projectFinder, rewriteRefactorings, orCuCache);
|
||||
RewriteCodeActionHandler rewriteCodeActionHandler(RewriteCompilationUnitCache cuCache, RewriteRecipeRepository recipeRepo) {
|
||||
return new RewriteCodeActionHandler(cuCache, recipeRepo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
QuickfixRegistry registry = server.getQuickfixRegistry();
|
||||
|
||||
registry.register(AutowiredConstructorReconciler.REMOVE_UNNECESSARY_AUTOWIRED_FROM_CONSTRUCTOR, new AutowiredConstructorQuickFixHandler(server, projectFinder, orCuCache));
|
||||
registry.register(BeanMethodNotPublicReconciler.REMOVE_PUBLIC_FROM_BEAN_METHOD, new BeanMethodNoPublicQuickFixHandler(server, projectFinder, orCuCache));
|
||||
recipeRepo.loaded.thenAccept(v ->
|
||||
recipeRepo.getProblemRecipeDescriptors().forEach(d -> {
|
||||
if (recipeRepo.getRecipe(d.getRecipeId()).isPresent()) {
|
||||
registry.register(d.getRecipeId(), rewriteRefactorings);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.springframework.ide.vscode.boot.java.handlers.BootJavaWorkspaceSymbol
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HighlightProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeAction;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.ActiveProfilesProvider;
|
||||
@@ -51,10 +51,15 @@ import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessConnec
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDataProvider;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveHoverUpdater;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessTracker;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JavaReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.LiveAppURLSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxHandlerCodeLensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouteHighlightProdivder;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringLiveChangeDetectionWatchdog;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValueHoverProvider;
|
||||
@@ -179,12 +184,24 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
highlightsEngine = createDocumentHighlightEngine(indexer);
|
||||
documents.onDocumentHighlight(highlightsEngine);
|
||||
|
||||
reconcileEngine = new BootJavaReconcileEngine(server, cuCache, projectFinder);
|
||||
JdtReconciler jdtReconciler = new JdtReconciler(cuCache);
|
||||
|
||||
RewriteCompilationUnitCache orCompilationUnitCache = appContext.getBean(RewriteCompilationUnitCache.class);
|
||||
|
||||
RewriteReconciler rewriteJavaReconciler = new RewriteReconciler(
|
||||
appContext.getBean(RewriteRecipeRepository.class),
|
||||
orCompilationUnitCache,
|
||||
server.getQuickfixRegistry()
|
||||
);
|
||||
|
||||
reconcileEngine = new BootJavaReconcileEngine(projectFinder, new JavaReconciler[] {
|
||||
jdtReconciler,
|
||||
rewriteJavaReconciler
|
||||
});
|
||||
|
||||
codeActionProvider = new BootJavaCodeActionProvider(
|
||||
projectFinder,
|
||||
cuCache,
|
||||
appContext.getBeansOfType(JavaCodeAction.class).values());
|
||||
appContext.getBeansOfType(JavaCodeActionHandler.class).values());
|
||||
|
||||
config.addListener(ignore -> {
|
||||
log.info("update live process tracker settings - start");
|
||||
@@ -205,7 +222,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
liveChangeDetectionWatchdog.disableHighlights();
|
||||
}
|
||||
|
||||
reconcileEngine.setSpelExpressionSyntaxValidationEnabled(config.isSpelExpressionValidationEnabled());
|
||||
jdtReconciler.setSpelExpressionSyntaxValidationEnabled(config.isSpelExpressionValidationEnabled());
|
||||
|
||||
log.info("update live process tracker settings - done");
|
||||
});
|
||||
|
||||
@@ -10,22 +10,21 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.handlers;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.NodeFinder;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionCapabilities;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.CodeActionHandler;
|
||||
@@ -34,31 +33,28 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class BootJavaCodeActionProvider implements CodeActionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BootJavaCodeActionProvider.class);
|
||||
|
||||
final private JavaProjectFinder projectFinder;
|
||||
final private CompilationUnitCache cuCache;
|
||||
private Collection<JavaCodeAction> javaCodeActions;
|
||||
final private Collection<JavaCodeActionHandler> handlers;
|
||||
|
||||
public BootJavaCodeActionProvider(JavaProjectFinder projectFinder, CompilationUnitCache cuCache, Collection<JavaCodeAction> javaCodeActions) {
|
||||
public BootJavaCodeActionProvider(JavaProjectFinder projectFinder, Collection<JavaCodeActionHandler> handlers) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.cuCache = cuCache;
|
||||
this.javaCodeActions = javaCodeActions;
|
||||
this.handlers = handlers;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Either<Command, CodeAction>> handle(CancelChecker cancelToken, CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region) {
|
||||
Optional<IJavaProject> project = projectFinder.find(doc.getId());
|
||||
if (project.isPresent()) {
|
||||
return cuCache.withCompilationUnit(project.get(), URI.create(doc.getId().getUri()), cu -> {
|
||||
ASTNode found = NodeFinder.perform(cu, region.getOffset(), region.getLength());
|
||||
List<Either<Command, CodeAction>> codeActions = new ArrayList<>();
|
||||
for (JavaCodeAction jca : javaCodeActions) {
|
||||
List<Either<Command, CodeAction>> cas = jca.getCodeActions(capabilities, context, doc, region, project.get(), cu, found);
|
||||
if (cas != null) {
|
||||
codeActions.addAll(cas);
|
||||
}
|
||||
return handlers.stream().flatMap(handler -> {
|
||||
try {
|
||||
return handler.handle(project.get(), cancelToken, capabilities, context, doc, region).stream();
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
return Stream.empty();
|
||||
}
|
||||
return codeActions;
|
||||
});
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -10,166 +10,80 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.handlers;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MarkerAnnotation;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationParamReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.AutowiredConstructorReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.BeanMethodNotPublicReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.UnnecessarySpringExtensionReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.value.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JavaReconciler;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class BootJavaReconcileEngine implements IReconcileEngine {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BootJavaReconcileEngine.class);
|
||||
|
||||
// annotations with SpEL expression params
|
||||
public static final String SPRING_CACHEABLE = "org.springframework.cache.annotation.Cacheable";
|
||||
public static final String SPRING_CACHE_EVICT = "org.springframework.cache.annotation.CacheEvict";
|
||||
|
||||
public static final String SPRING_EVENT_LISTENER = "org.springframework.context.event.EventListener";
|
||||
|
||||
public static final String SPRING_PRE_AUTHORIZE = "org.springframework.security.access.prepost.PreAuthorize";
|
||||
public static final String SPRING_PRE_FILTER = "org.springframework.security.access.prepost.PreFilter";
|
||||
public static final String SPRING_POST_AUTHORIZE = "org.springframework.security.access.prepost.PostAuthorize";
|
||||
public static final String SPRING_POST_FILTER= "org.springframework.security.access.prepost.PostFilter";
|
||||
|
||||
public static final String SPRING_CONDITIONAL_ON_EXPRESSION = "org.springframework.boot.autoconfigure.condition.ConditionalOnExpression";
|
||||
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final CompilationUnitCache compilationUnitCache;
|
||||
private final AnnotationReconciler[] reconcilers;
|
||||
private final SpelExpressionReconciler spelExpressionReconciler;
|
||||
private final QuickfixRegistry quickfixRegistry;
|
||||
private JavaReconciler[] javaReconcilers;
|
||||
|
||||
public BootJavaReconcileEngine(SimpleLanguageServer server, CompilationUnitCache compilationUnitCache, JavaProjectFinder projectFinder) {
|
||||
this.compilationUnitCache = compilationUnitCache;
|
||||
public BootJavaReconcileEngine(JavaProjectFinder projectFinder, JavaReconciler[] javaReconcilers) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.quickfixRegistry = server.getQuickfixRegistry();
|
||||
|
||||
this.spelExpressionReconciler = new SpelExpressionReconciler();
|
||||
|
||||
this.reconcilers = new AnnotationReconciler[] {
|
||||
|
||||
new AnnotationParamReconciler(Constants.SPRING_VALUE, null, "#{", "}", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(Constants.SPRING_VALUE, "value", "#{", "}", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_CACHEABLE, "key", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CACHEABLE, "condition", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CACHEABLE, "unless", "", "", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_CACHE_EVICT, "key", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CACHE_EVICT, "condition", "", "", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_EVENT_LISTENER, "condition", "", "", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_PRE_AUTHORIZE, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_PRE_AUTHORIZE, "value", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_PRE_FILTER, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_PRE_FILTER, "value", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_POST_AUTHORIZE, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_POST_AUTHORIZE, "value", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_POST_FILTER, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_POST_FILTER, "value", "", "", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", "", spelExpressionReconciler),
|
||||
|
||||
new AutowiredConstructorReconciler(quickfixRegistry),
|
||||
new BeanMethodNotPublicReconciler(quickfixRegistry),
|
||||
new UnnecessarySpringExtensionReconciler()
|
||||
};
|
||||
}
|
||||
|
||||
public void setSpelExpressionSyntaxValidationEnabled(boolean spelExpressionValidationEnabled) {
|
||||
this.spelExpressionReconciler.setEnabled(spelExpressionValidationEnabled);
|
||||
this.javaReconcilers = javaReconcilers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconcile(final IDocument doc, final IProblemCollector problemCollector) {
|
||||
IJavaProject project = projectFinder.find(new TextDocumentIdentifier(doc.getUri())).orElse(null);
|
||||
URI uri = URI.create(doc.getUri());
|
||||
|
||||
if (project != null) {
|
||||
|
||||
try {
|
||||
problemCollector.beginCollecting();
|
||||
|
||||
compilationUnitCache.withCompilationUnit(project, uri, cu -> {
|
||||
if (cu != null) {
|
||||
reconcileAST(project, doc, cu, problemCollector);
|
||||
AtomicInteger count = new AtomicInteger(0);
|
||||
|
||||
IProblemCollector recolerProblemCollector = new IProblemCollector() {
|
||||
|
||||
@Override
|
||||
public void beginCollecting() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void endCollecting() {
|
||||
// If count is reached then let the endCollecting be called when all completable futures resolved
|
||||
if (count.incrementAndGet() < javaReconcilers.length) {
|
||||
problemCollector.checkPointCollecting();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void accept(ReconcileProblem problem) {
|
||||
problemCollector.accept(problem);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
finally {
|
||||
};
|
||||
|
||||
CompletableFuture<?>[] futures = Arrays.stream(javaReconcilers)
|
||||
.map(jr -> CompletableFuture.runAsync(() -> jr.reconcile(project, doc, recolerProblemCollector)).exceptionally(t -> null))
|
||||
.toArray(CompletableFuture[]::new);
|
||||
|
||||
CompletableFuture.allOf(futures).thenAccept(a -> problemCollector.endCollecting()).get();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
} finally {
|
||||
problemCollector.endCollecting();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void reconcileAST(IJavaProject project, IDocument doc, CompilationUnit cu, IProblemCollector problemCollector) {
|
||||
cu.accept(new ASTVisitor() {
|
||||
|
||||
@Override
|
||||
public boolean visit(SingleMemberAnnotation node) {
|
||||
try {
|
||||
visitAnnotation(project, doc, node, problemCollector);
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(NormalAnnotation node) {
|
||||
try {
|
||||
visitAnnotation(project, doc, node, problemCollector);
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MarkerAnnotation node) {
|
||||
try {
|
||||
visitAnnotation(project, doc, node, problemCollector);
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
protected void visitAnnotation(IJavaProject project, IDocument doc, Annotation node, IProblemCollector problemCollector) {
|
||||
ITypeBinding typeBinding = node.resolveTypeBinding();
|
||||
|
||||
if (typeBinding != null) {
|
||||
for (int i = 0; i < reconcilers.length; i++) {
|
||||
reconcilers[i].visit(project, doc, node, typeBinding, problemCollector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.handlers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionCapabilities;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public interface JavaCodeActionHandler {
|
||||
|
||||
List<Either<Command, CodeAction>> handle(IJavaProject project, CancelChecker cancelToken, CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.reconcilers;
|
||||
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
public interface JavaReconciler {
|
||||
|
||||
void reconcile(IJavaProject project, IDocument doc, IProblemCollector problemCollector);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.reconcilers;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MarkerAnnotation;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SpelExpressionReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.value.Constants;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
public class JdtReconciler implements JavaReconciler {
|
||||
// annotations with SpEL expression params
|
||||
public static final String SPRING_CACHEABLE = "org.springframework.cache.annotation.Cacheable";
|
||||
public static final String SPRING_CACHE_EVICT = "org.springframework.cache.annotation.CacheEvict";
|
||||
|
||||
public static final String SPRING_EVENT_LISTENER = "org.springframework.context.event.EventListener";
|
||||
|
||||
public static final String SPRING_PRE_AUTHORIZE = "org.springframework.security.access.prepost.PreAuthorize";
|
||||
public static final String SPRING_PRE_FILTER = "org.springframework.security.access.prepost.PreFilter";
|
||||
public static final String SPRING_POST_AUTHORIZE = "org.springframework.security.access.prepost.PostAuthorize";
|
||||
public static final String SPRING_POST_FILTER= "org.springframework.security.access.prepost.PostFilter";
|
||||
|
||||
public static final String SPRING_CONDITIONAL_ON_EXPRESSION = "org.springframework.boot.autoconfigure.condition.ConditionalOnExpression";
|
||||
|
||||
private final CompilationUnitCache compilationUnitCache;
|
||||
private final AnnotationReconciler[] reconcilers;
|
||||
private final SpelExpressionReconciler spelExpressionReconciler;
|
||||
|
||||
public JdtReconciler(CompilationUnitCache compilationUnitCache) {
|
||||
this.compilationUnitCache = compilationUnitCache;
|
||||
this.spelExpressionReconciler = new SpelExpressionReconciler();
|
||||
|
||||
this.reconcilers = new AnnotationReconciler[] {
|
||||
|
||||
new AnnotationParamReconciler(Constants.SPRING_VALUE, null, "#{", "}", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(Constants.SPRING_VALUE, "value", "#{", "}", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_CACHEABLE, "key", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CACHEABLE, "condition", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CACHEABLE, "unless", "", "", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_CACHE_EVICT, "key", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CACHE_EVICT, "condition", "", "", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_EVENT_LISTENER, "condition", "", "", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_PRE_AUTHORIZE, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_PRE_AUTHORIZE, "value", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_PRE_FILTER, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_PRE_FILTER, "value", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_POST_AUTHORIZE, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_POST_AUTHORIZE, "value", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_POST_FILTER, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_POST_FILTER, "value", "", "", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, null, "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", "", spelExpressionReconciler),
|
||||
};
|
||||
}
|
||||
|
||||
public void setSpelExpressionSyntaxValidationEnabled(boolean spelExpressionValidationEnabled) {
|
||||
this.spelExpressionReconciler.setEnabled(spelExpressionValidationEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconcile(IJavaProject project, final IDocument doc, final IProblemCollector problemCollector) {
|
||||
URI uri = URI.create(doc.getUri());
|
||||
compilationUnitCache.withCompilationUnit(project, uri, cu -> {
|
||||
if (cu != null) {
|
||||
reconcileAST(project, doc, cu, problemCollector);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void reconcileAST(IJavaProject project, IDocument doc, CompilationUnit cu, IProblemCollector problemCollector) {
|
||||
cu.accept(new ASTVisitor() {
|
||||
|
||||
@Override
|
||||
public boolean visit(SingleMemberAnnotation node) {
|
||||
try {
|
||||
visitAnnotation(project, doc, node, problemCollector);
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(NormalAnnotation node) {
|
||||
try {
|
||||
visitAnnotation(project, doc, node, problemCollector);
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MarkerAnnotation node) {
|
||||
try {
|
||||
visitAnnotation(project, doc, node, problemCollector);
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
protected void visitAnnotation(IJavaProject project, IDocument doc, Annotation node, IProblemCollector problemCollector) {
|
||||
ITypeBinding typeBinding = node.resolveTypeBinding();
|
||||
|
||||
if (typeBinding != null) {
|
||||
for (int i = 0; i < reconcilers.length; i++) {
|
||||
reconcilers[i].visit(project, doc, node, typeBinding, problemCollector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +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.boot.java.reconcilers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.Expression;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeLiteral;
|
||||
import org.springframework.ide.vscode.boot.java.SpringJavaProblemType;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.java.Version;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
public class UnnecessarySpringExtensionReconciler implements AnnotationReconciler {
|
||||
|
||||
private static final List<String> SPRING_BOOT_TEST_ANNOTATIONS = Arrays.asList(
|
||||
"org.springframework.boot.test.context.SpringBootTest",
|
||||
"org.springframework.boot.test.autoconfigure.jdbc.JdbcTest",
|
||||
"org.springframework.boot.test.autoconfigure.web.client.RestClientTest",
|
||||
"org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest",
|
||||
"org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest",
|
||||
"org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest",
|
||||
"org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest",
|
||||
"org.springframework.boot.test.autoconfigure.jooq.JooqTest",
|
||||
"org.springframework.boot.test.autoconfigure.json.JsonTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest"
|
||||
);
|
||||
|
||||
private static final String EXTEND_WITH_ANNOTATION = "org.junit.jupiter.api.extension.ExtendWith";
|
||||
|
||||
private static final String SPRING_EXTENSION_ANNOTATION = "org.springframework.test.context.junit.jupiter.SpringExtension";
|
||||
|
||||
@Override
|
||||
public void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding,
|
||||
IProblemCollector problemCollector) {
|
||||
if (isUnnecessarySpringExtensionAnnotation(project, node, typeBinding)) {
|
||||
ReconcileProblemImpl problem = new ReconcileProblemImpl(
|
||||
SpringJavaProblemType.JAVA_TEST_SPRING_EXTENSION, "Unnecessary @SpringExtension",
|
||||
node.getStartPosition(), node.getLength());
|
||||
problemCollector.accept(problem);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isUnnecessarySpringExtensionAnnotation(IJavaProject project, Annotation node, ITypeBinding typeBinding) {
|
||||
if (EXTEND_WITH_ANNOTATION.equals(typeBinding.getQualifiedName())) {
|
||||
Version v = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT);
|
||||
// Since Boot 2.1
|
||||
if ((v.getMajor() == 2 && v.getMinor() >= 1) || v.getMajor() > 2) {
|
||||
return hasSpringTestSiblingAnnotation(node) && hasSpringExtensionAnnotationParameter(node);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hasSpringTestSiblingAnnotation(Annotation node) {
|
||||
if (node.getParent() instanceof TypeDeclaration) {
|
||||
TypeDeclaration typeDecl = ((TypeDeclaration) node.getParent());
|
||||
for (Object m : typeDecl.modifiers()) {
|
||||
if (m instanceof Annotation) {
|
||||
IAnnotationBinding annotationBinding = ((Annotation)m).resolveAnnotationBinding();
|
||||
if (annotationBinding != null) {
|
||||
ITypeBinding annotationType = annotationBinding.getAnnotationType();
|
||||
if (annotationType != null && SPRING_BOOT_TEST_ANNOTATIONS.contains(annotationBinding.getAnnotationType().getQualifiedName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean hasSpringExtensionAnnotationParameter(Annotation node) {
|
||||
if (node instanceof SingleMemberAnnotation) {
|
||||
return isSpringExtensionExpression(((SingleMemberAnnotation) node).getValue());
|
||||
} else if (node instanceof NormalAnnotation) {
|
||||
List<MemberValuePair> params = ((NormalAnnotation) node).values();
|
||||
for (MemberValuePair param : params) {
|
||||
if ("value".equals(param.getName().getIdentifier())) {
|
||||
return isSpringExtensionExpression(param.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isSpringExtensionExpression(Expression o) {
|
||||
if (o instanceof TypeLiteral) {
|
||||
ITypeBinding binding = ((TypeLiteral) o).getType().resolveBinding();
|
||||
return binding != null && SPRING_EXTENSION_ANNOTATION.equals(binding.getQualifiedName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.java.JavaVisitor;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
|
||||
public interface RecipeCodeActionDescriptor {
|
||||
|
||||
String getRecipeId();
|
||||
|
||||
String getLabel(RecipeScope s);
|
||||
|
||||
RecipeScope[] getScopes();
|
||||
|
||||
JavaVisitor<ExecutionContext> getMarkerVisitor();
|
||||
|
||||
boolean isApplicable(IJavaProject project);
|
||||
|
||||
static String buildLabel(String label, RecipeScope s) {
|
||||
switch (s) {
|
||||
case FILE:
|
||||
return label + " in file";
|
||||
case PROJECT:
|
||||
return label + " in project";
|
||||
default:
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite;
|
||||
|
||||
public enum RecipeScope {
|
||||
NODE,
|
||||
FILE,
|
||||
PROJECT
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionCapabilities;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.CodeActionResolveSupportCapabilities;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.InMemoryExecutionContext;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
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;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.reconcile.RecipeSpringJavaProblemDescriptor;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient.Client;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class RewriteCodeActionHandler implements JavaCodeActionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RewriteCodeActionHandler.class);
|
||||
|
||||
final private RewriteCompilationUnitCache cuCache;
|
||||
final private RewriteRecipeRepository recipeRepo;
|
||||
|
||||
public RewriteCodeActionHandler(RewriteCompilationUnitCache cuCache, RewriteRecipeRepository recipeRepo) {
|
||||
this.cuCache = cuCache;
|
||||
this.recipeRepo = recipeRepo;
|
||||
}
|
||||
|
||||
protected static boolean isResolve(CodeActionCapabilities capabilities, String property) {
|
||||
if (capabilities != null) {
|
||||
CodeActionResolveSupportCapabilities resolveSupport = capabilities.getResolveSupport();
|
||||
if (resolveSupport != null) {
|
||||
List<String> properties = resolveSupport.getProperties();
|
||||
return properties != null && properties.contains(property);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSupported(CodeActionCapabilities capabilities, CodeActionContext context) {
|
||||
// Default case is anything non-quick fix related.
|
||||
if (isResolve(capabilities, "edit")) {
|
||||
if (context.getOnly() != null) {
|
||||
return context.getOnly().contains(CodeActionKind.Refactor);
|
||||
} else {
|
||||
if (LspClient.currentClient() == Client.ECLIPSE) {
|
||||
// Eclipse would have no diagnostics in the context for QuickAssists refactoring. Diagnostics will be around for QuickFix only
|
||||
return context.getDiagnostics().isEmpty();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Either<Command, CodeAction>> handle(IJavaProject project, CancelChecker cancelToken,
|
||||
CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region) {
|
||||
try {
|
||||
|
||||
// Wait for recipe repo to load if not loaded - should be loaded by the time we get here.
|
||||
recipeRepo.loaded.get();
|
||||
|
||||
List<RecipeCodeActionDescriptor> descriptors = recipeRepo.getCodeActionRecipeDescriptors().stream()
|
||||
// If Recipe not present - don't show quick assist as it won't be handled without the Rewrite recipe present
|
||||
.filter(d -> recipeRepo.getRecipe(d.getRecipeId()).isPresent())
|
||||
.filter(d -> d.isApplicable(project))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
if (isSupported(capabilities, context)) {
|
||||
CompilationUnit cu = cuCache.getCU(project, URI.create(doc.getUri()));
|
||||
if (cu != null) {
|
||||
|
||||
cu = recipeRepo.mark(descriptors, cu);
|
||||
|
||||
List<CodeAction> codeActions = new ArrayList<>();
|
||||
new JavaIsoVisitor<ExecutionContext>() {
|
||||
public J visit(Tree tree, ExecutionContext ctx) {
|
||||
if (tree == null) {
|
||||
return null;
|
||||
}
|
||||
if (tree instanceof J) {
|
||||
J node = (J) tree;
|
||||
Range range = node.getMarkers().findFirst(Range.class).orElse(null);
|
||||
if (range != null
|
||||
&& range.getStart().getOffset() <= region.getOffset()
|
||||
&& region.getOffset() + region.getLength() <= range.getEnd().getOffset()) {
|
||||
|
||||
for (FixAssistMarker m : node.getMarkers().findAll(FixAssistMarker.class)) {
|
||||
for (CodeAction ca : createCodeActions(doc, m, node)) {
|
||||
codeActions.add(ca);
|
||||
}
|
||||
}
|
||||
return super.visit(tree, ctx);
|
||||
} else {
|
||||
return (J) tree;
|
||||
}
|
||||
}
|
||||
return (J) tree;
|
||||
};
|
||||
}.visitNonNull(cu, new InMemoryExecutionContext());
|
||||
return codeActions.stream().map(ca -> Either.<Command, CodeAction>forRight(ca)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private CodeAction[] createCodeActions(IDocument doc, FixAssistMarker m, J astNode) {
|
||||
if (astNode != null) {
|
||||
Range range = astNode.getMarkers().findFirst(Range.class).orElse(null);
|
||||
RecipeCodeActionDescriptor descriptor = recipeRepo.getCodeActionRecipeDescriptor(m.getRecipeId());
|
||||
if (descriptor != null && descriptor.getScopes() != null) {
|
||||
// Fix descriptor code actions may have the overlapping scopes with assist descriptors. Quick fixes are provided separately hence overlapping scopes will produces duplicates quick assist.
|
||||
// Therefore, need to compute recipe scopes that don't overlap with quick fix descriptor recipe scopes.
|
||||
RecipeSpringJavaProblemDescriptor fixDescriptor = recipeRepo.getProblemRecipeDescriptor(m.getRecipeId());
|
||||
return Arrays.stream(descriptor.getScopes())
|
||||
.filter(s -> fixDescriptor == null || !Arrays.asList(fixDescriptor.getScopes()).contains(s))
|
||||
.map(s -> createCodeActionFromScope(doc, descriptor, s, m, range))
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(CodeAction[]::new);
|
||||
}
|
||||
}
|
||||
return new CodeAction[0];
|
||||
}
|
||||
|
||||
private CodeAction createCodeActionFromScope(IDocument doc, RecipeCodeActionDescriptor descriptor,
|
||||
RecipeScope s, FixAssistMarker m, Range range) {
|
||||
CodeAction ca = new CodeAction();
|
||||
ca.setKind(CodeActionKind.Refactor);
|
||||
ca.setTitle(descriptor.getLabel(s));
|
||||
ca.setData(new RewriteRefactorings.Data(
|
||||
m.getRecipeId(),
|
||||
doc.getUri(),
|
||||
s,
|
||||
m.getScope() == null ? null : m.getScope().toString(),
|
||||
m.getParameters() == null ? Collections.emptyMap() : m.getParameters()
|
||||
));
|
||||
return ca;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -50,16 +49,14 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
|
||||
import reactor.core.Disposable;
|
||||
|
||||
public class ORCompilationUnitCache implements DocumentContentProvider, Disposable {
|
||||
public class RewriteCompilationUnitCache implements DocumentContentProvider, Disposable {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompilationUnitCache.class);
|
||||
|
||||
private static final long CU_ACCESS_EXPIRATION = 1;
|
||||
private static final long CU_ACCESS_EXPIRATION = 5;
|
||||
private JavaProjectFinder projectFinder;
|
||||
private ProjectObserver projectObserver;
|
||||
|
||||
@@ -69,22 +66,22 @@ public class ORCompilationUnitCache implements DocumentContentProvider, Disposab
|
||||
private final Cache<URI, CompilationUnit> uriToCu;
|
||||
private final Cache<IJavaProject, Set<URI>> projectToDocs;
|
||||
private final Cache<IJavaProject, JavaParser> javaParsers;
|
||||
|
||||
public ORCompilationUnitCache(JavaProjectFinder projectFinder, SimpleLanguageServer server, ProjectObserver projectObserver) {
|
||||
|
||||
public RewriteCompilationUnitCache(JavaProjectFinder projectFinder, SimpleLanguageServer server, ProjectObserver projectObserver) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.projectObserver = projectObserver;
|
||||
|
||||
// PT 154618835 - Avoid retaining the CU in the cache as it consumes memory if it hasn't been
|
||||
// accessed after some time
|
||||
this.uriToCu = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(CU_ACCESS_EXPIRATION, TimeUnit.MINUTES)
|
||||
.removalListener(new RemovalListener<URI, CompilationUnit>() {
|
||||
|
||||
@Override
|
||||
public void onRemoval(RemovalNotification<URI, CompilationUnit> notification) {
|
||||
invalidateCuForJavaFile(notification.getKey().toString());
|
||||
}
|
||||
})
|
||||
// .expireAfterWrite(CU_ACCESS_EXPIRATION, TimeUnit.MINUTES)
|
||||
// .removalListener(new RemovalListener<URI, CompilationUnit>() {
|
||||
//
|
||||
// @Override
|
||||
// public void onRemoval(RemovalNotification<URI, CompilationUnit> notification) {
|
||||
// invalidateCuForJavaFile(notification.getKey().toString());
|
||||
// }
|
||||
// })
|
||||
.build();
|
||||
this.projectToDocs = CacheBuilder.newBuilder().build();
|
||||
this.javaParsers = CacheBuilder.newBuilder().build();
|
||||
@@ -211,19 +208,11 @@ public class ORCompilationUnitCache implements DocumentContentProvider, Disposab
|
||||
return IOUtils.toString(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not need to be via callback - kept the same in order to keep the same API to replace JDT with Rewrite in distant future
|
||||
*/
|
||||
public <T> T withCompilationUnit(IJavaProject project, URI uri, Function<CompilationUnit, T> requestor) {
|
||||
logger.info("CU Cache: work item submitted for doc {}", uri.toString());
|
||||
|
||||
if (project != null) {
|
||||
|
||||
CompilationUnit cu = null;
|
||||
|
||||
try {
|
||||
cu = uriToCu.get(uri, () -> {
|
||||
JavaParser javaParser = loadJavaParser(project);
|
||||
public CompilationUnit getCU(IJavaProject project, URI uri) {
|
||||
try {
|
||||
if (project != null) {
|
||||
return uriToCu.get(uri, () -> {
|
||||
JavaParser javaParser = /*loadJavaParser(project)*/createJavaParser(project);
|
||||
Input input = new Input(Paths.get(uri), () -> {
|
||||
try {
|
||||
return new ByteArrayInputStream(fetchContent(uri).getBytes());
|
||||
@@ -234,36 +223,40 @@ public class ORCompilationUnitCache implements DocumentContentProvider, Disposab
|
||||
|
||||
List<CompilationUnit> cus = ORAstUtils.parseInputs(javaParser, List.of(input));
|
||||
|
||||
logger.info("CU Cache: created new AST for {}", uri.toString());
|
||||
CompilationUnit cu = cus.get(0);
|
||||
|
||||
|
||||
if (cu != null) {
|
||||
projectToDocs.get(project, () -> new HashSet<>()).add(uri);
|
||||
}
|
||||
|
||||
return cus.get(0);
|
||||
return cu;
|
||||
});
|
||||
|
||||
if (cu != null) {
|
||||
projectToDocs.get(project, () -> new HashSet<>()).add(uri);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not need to be via callback - kept the same in order to keep the same API to replace JDT with Rewrite in distant future
|
||||
*/
|
||||
public <T> T withCompilationUnit(IJavaProject project, URI uri, Function<CompilationUnit, T> requestor) {
|
||||
logger.info("CU Cache: work item submitted for doc {}", uri.toString());
|
||||
CompilationUnit cu = getCU(project, uri);
|
||||
if (cu != null) {
|
||||
try {
|
||||
logger.info("CU Cache: start work on AST for {}", uri.toString());
|
||||
return requestor.apply(cu);
|
||||
} catch (CancellationException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
|
||||
if (cu != null) {
|
||||
try {
|
||||
logger.info("CU Cache: start work on AST for {}", uri.toString());
|
||||
return requestor.apply(cu);
|
||||
}
|
||||
catch (CancellationException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
finally {
|
||||
logger.info("CU Cache: end work on AST for {}", uri.toString());
|
||||
}
|
||||
} finally {
|
||||
logger.info("CU Cache: end work on AST for {}", uri.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return requestor.apply(null);
|
||||
}
|
||||
|
||||
@@ -38,13 +38,24 @@ import org.openrewrite.InMemoryExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.Result;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.Validated;
|
||||
import org.openrewrite.config.Environment;
|
||||
import org.openrewrite.config.RecipeDescriptor;
|
||||
import org.openrewrite.internal.RecipeIntrospectionUtils;
|
||||
import org.openrewrite.java.JavaParser;
|
||||
import org.openrewrite.java.tree.J.CompilationUnit;
|
||||
import org.openrewrite.maven.MavenParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.AutowiredFieldIntoConstructorParameterCodeAction;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNotPublicCodeAction;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMappingAnnotationCodeAction;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.UnnecessarySpringExtensionCodeAction;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.reconcile.BeanMethodNotPublicProblem;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.reconcile.NoAutowiredOnConstructorProblem;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.reconcile.RecipeSpringJavaProblemDescriptor;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.reconcile.UnnecessarySpringExtensionProblem;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
@@ -91,7 +102,20 @@ public class RewriteRecipeRepository {
|
||||
private static Gson serializationGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Duration.class, new DurationTypeConverter())
|
||||
.create();
|
||||
|
||||
|
||||
private List<RecipeCodeActionDescriptor> codeActionDescriptors = List.of(
|
||||
new AutowiredFieldIntoConstructorParameterCodeAction(),
|
||||
new BeanMethodsNotPublicCodeAction(),
|
||||
new NoRequestMappingAnnotationCodeAction(),
|
||||
new UnnecessarySpringExtensionCodeAction()
|
||||
);
|
||||
|
||||
private List<RecipeSpringJavaProblemDescriptor> javaProblemDescriptors = List.of(
|
||||
new BeanMethodNotPublicProblem(),
|
||||
new NoAutowiredOnConstructorProblem(),
|
||||
new UnnecessarySpringExtensionProblem()
|
||||
);
|
||||
|
||||
public RewriteRecipeRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder) {
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
@@ -136,6 +160,57 @@ public class RewriteRecipeRepository {
|
||||
return Optional.ofNullable(recipes.get(name));
|
||||
}
|
||||
|
||||
public RecipeSpringJavaProblemDescriptor getProblemRecipeDescriptor(String id) {
|
||||
for (RecipeSpringJavaProblemDescriptor d : javaProblemDescriptors) {
|
||||
if (id.equals(d.getRecipeId())) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public RecipeCodeActionDescriptor getCodeActionRecipeDescriptor(String id) {
|
||||
for (RecipeCodeActionDescriptor d : codeActionDescriptors) {
|
||||
if (id.equals(d.getRecipeId())) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<RecipeSpringJavaProblemDescriptor> getProblemRecipeDescriptors() {
|
||||
return javaProblemDescriptors;
|
||||
}
|
||||
|
||||
public List<RecipeCodeActionDescriptor> getCodeActionRecipeDescriptors() {
|
||||
return codeActionDescriptors;
|
||||
}
|
||||
|
||||
public List<RecipeCodeActionDescriptor> getApplicableCodeActionRecipeDescriptors(IJavaProject project, List<RecipeCodeActionDescriptor> descriptors) {
|
||||
List<RecipeCodeActionDescriptor> filtered = new ArrayList<>(descriptors.size());
|
||||
for (RecipeCodeActionDescriptor d : descriptors) {
|
||||
if (d.isApplicable(project)) {
|
||||
filtered.add(d);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public CompilationUnit mark(List<? extends RecipeCodeActionDescriptor> descriptors, CompilationUnit compilationUnit) {
|
||||
CompilationUnit cu = compilationUnit;
|
||||
for (RecipeCodeActionDescriptor d : descriptors) {
|
||||
Recipe recipe = getRecipe(d.getRecipeId()).orElse(null);
|
||||
if (recipe != null) {
|
||||
TreeVisitor<?, ExecutionContext> isApplicableVisitor = RecipeIntrospectionUtils.recipeSingleSourceApplicableTest(recipe);
|
||||
TreeVisitor<?, ExecutionContext> markVisitor = d.getMarkerVisitor();
|
||||
if (markVisitor != null && (isApplicableVisitor == null || isApplicableVisitor.visit(cu, new InMemoryExecutionContext(e -> log.error("", e))) != cu)) {
|
||||
cu = (CompilationUnit) markVisitor.visit(cu, new InMemoryExecutionContext(e -> log.error("", e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return cu;
|
||||
}
|
||||
|
||||
private static JsonElement recipeToJson(Recipe r) {
|
||||
JsonElement jsonElement = serializationGson.toJsonTree(r.getDescriptor());
|
||||
return jsonElement;
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.InMemoryExecutionContext;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.internal.lang.Nullable;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
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;
|
||||
import org.springframework.ide.vscode.boot.java.SpringJavaProblemType;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JavaReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.Data;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.reconcile.RecipeSpringJavaProblemDescriptor;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixType;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
public class RewriteReconciler implements JavaReconciler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RewriteReconciler.class);
|
||||
|
||||
private RewriteCompilationUnitCache cuCache;
|
||||
|
||||
private QuickfixRegistry quickfixRegistry;
|
||||
|
||||
private RewriteRecipeRepository recipeRepo;
|
||||
|
||||
public RewriteReconciler(RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache, QuickfixRegistry quickfixRegistry) {
|
||||
this.recipeRepo = recipeRepo;
|
||||
this.cuCache = cuCache;
|
||||
this.quickfixRegistry = quickfixRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconcile(IJavaProject project, IDocument doc, IProblemCollector problemCollector) {
|
||||
try {
|
||||
problemCollector.beginCollecting();
|
||||
|
||||
// Wait for recipe repo to load if not loaded - should be loaded by the time we get here.
|
||||
recipeRepo.loaded.get();
|
||||
|
||||
List<RecipeSpringJavaProblemDescriptor> descriptors = recipeRepo.getProblemRecipeDescriptors().stream()
|
||||
.filter(d -> d.isApplicable(project))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!descriptors.isEmpty()) {
|
||||
CompilationUnit cu = cuCache.getCU(project, URI.create(doc.getUri()));
|
||||
|
||||
if (cu != null) {
|
||||
|
||||
cu = recipeRepo.mark(descriptors, cu);
|
||||
|
||||
new JavaIsoVisitor<ExecutionContext>() {
|
||||
|
||||
@Override
|
||||
public @Nullable J visit(@Nullable Tree tree, ExecutionContext context) {
|
||||
J t = super.visit(tree, context);
|
||||
if (t instanceof J) {
|
||||
List<FixAssistMarker> markers = t.getMarkers().findAll(FixAssistMarker.class);
|
||||
for (FixAssistMarker m : markers) {
|
||||
for (ReconcileProblem problem : createProblems(doc, m, t)) {
|
||||
problemCollector.accept(problem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
}.visit(cu, new InMemoryExecutionContext(e -> log.error("", e)));
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
} finally {
|
||||
problemCollector.endCollecting();
|
||||
}
|
||||
}
|
||||
|
||||
private List<ReconcileProblem> createProblems(IDocument doc, FixAssistMarker m, J astNode) {
|
||||
if (astNode != null) {
|
||||
Range range = astNode.getMarkers().findFirst(Range.class).orElse(null);
|
||||
if (range != null) {
|
||||
RecipeSpringJavaProblemDescriptor recipeFixDescriptor = recipeRepo.getProblemRecipeDescriptor(m.getRecipeId());
|
||||
if (recipeFixDescriptor != null && recipeFixDescriptor.getScopes() != null && recipeRepo.getRecipe(recipeFixDescriptor.getRecipeId()).isPresent()) {
|
||||
return Arrays.stream(recipeFixDescriptor.getScopes()).map(s -> createProblemFromScope(doc, recipeFixDescriptor, s, m, range)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private ReconcileProblemImpl createProblemFromScope(IDocument doc, RecipeSpringJavaProblemDescriptor recipeFixDescriptor, RecipeScope s,
|
||||
FixAssistMarker m, Range range) {
|
||||
SpringJavaProblemType problemType = recipeFixDescriptor.getProblemType();
|
||||
ReconcileProblemImpl problem = new ReconcileProblemImpl(problemType, problemType.getLabel(), range.getStart().getOffset(), range.getEnd().getOffset() - range.getStart().getOffset());
|
||||
QuickfixType quickfixType = quickfixRegistry.getQuickfixType(m.getRecipeId());
|
||||
if (quickfixType != null) {
|
||||
problem.addQuickfix(new QuickfixData<>(
|
||||
quickfixType,
|
||||
new Data(m.getRecipeId(), doc.getUri(), s, m.getScope().toString(), m.getParameters()),
|
||||
recipeFixDescriptor.getLabel(s)
|
||||
));
|
||||
}
|
||||
return problem;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,57 +10,183 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.rewrite;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.ResourceOperation;
|
||||
import org.eclipse.lsp4j.TextDocumentEdit;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.Result;
|
||||
import org.openrewrite.config.DeclarativeRecipe;
|
||||
import org.openrewrite.internal.RecipeIntrospectionUtils;
|
||||
import org.openrewrite.java.tree.J;
|
||||
import org.openrewrite.java.tree.J.CompilationUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.CodeActionResolver;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.rewrite.ORDocUtils;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.ORAstUtils;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
public class RewriteRefactorings implements CodeActionResolver {
|
||||
public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RewriteRefactorings.class);
|
||||
|
||||
private Map<String, Function<List<?>, WorkspaceEdit>> refactoringsMap = new ConcurrentHashMap<>();
|
||||
private RewriteRecipeRepository recipeRepo;
|
||||
|
||||
public void addRefactoring(String id, Function<List<?>, WorkspaceEdit> handler) {
|
||||
if (refactoringsMap.containsKey(id)) {
|
||||
throw new IllegalStateException("Refactoring with id '" + id + "' already exists!");
|
||||
}
|
||||
refactoringsMap.put(id, handler);
|
||||
private SimpleTextDocumentService documents;
|
||||
|
||||
private RewriteCompilationUnitCache cuCache;
|
||||
|
||||
private JavaProjectFinder projectFinder;
|
||||
|
||||
private Gson gson;
|
||||
|
||||
public RewriteRefactorings(SimpleTextDocumentService documents, JavaProjectFinder projectFinder, RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache) {
|
||||
this.documents = documents;
|
||||
this.projectFinder = projectFinder;
|
||||
this.recipeRepo = recipeRepo;
|
||||
this.cuCache = cuCache;
|
||||
this.gson = new GsonBuilder()
|
||||
.registerTypeAdapter(RecipeScope.class, (JsonDeserializer<RecipeScope>) (json, type, context) -> {
|
||||
try {
|
||||
return RecipeScope.values()[json.getAsInt()];
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuickfixEdit createEdits(Object p) {
|
||||
if (p instanceof JsonElement) {
|
||||
return new QuickfixEdit(createEdit((JsonElement) p), null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void resolve(CodeAction codeAction) {
|
||||
if (codeAction.getData() instanceof JsonObject) {
|
||||
JsonObject o = (JsonObject) codeAction.getData();
|
||||
if (codeAction.getData() instanceof JsonElement) {
|
||||
try {
|
||||
Data data = new Gson().fromJson(o, Data.class);
|
||||
if (data != null && data.id != null) {
|
||||
Function<List<?>, WorkspaceEdit> handler = refactoringsMap.get(data.id);
|
||||
if (handler != null) {
|
||||
WorkspaceEdit edit = handler.apply(data.arguments);
|
||||
if (edit != null) {
|
||||
codeAction.setEdit(edit);
|
||||
}
|
||||
}
|
||||
WorkspaceEdit edit = createEdit((JsonElement) codeAction.getData());
|
||||
if (edit != null) {
|
||||
codeAction.setEdit(edit);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
log.error("", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WorkspaceEdit createEdit(JsonElement o) {
|
||||
Data data = gson.fromJson(o, Data.class);
|
||||
if (data != null && data.id != null) {
|
||||
return perform(data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private WorkspaceEdit applyRecipe(Recipe r, IJavaProject project, List<J.CompilationUnit> cus) {
|
||||
List<Result> results = r.run(cus);
|
||||
List<Either<TextDocumentEdit, ResourceOperation>> edits = results.stream().filter(res -> res.getAfter() != null).map(res -> {
|
||||
URI docUri = project.getLocationUri().resolve(res.getAfter().getSourcePath().toString());
|
||||
TextDocument doc = documents.getLatestSnapshot(docUri.toString());
|
||||
if (doc == null) {
|
||||
doc = new TextDocument(docUri.toString(), LanguageId.JAVA, 0, res.getBefore() == null ? "" : res.getBefore().printAll());
|
||||
}
|
||||
return ORDocUtils.computeTextDocEdit(doc, res);
|
||||
}).filter(e -> e.isPresent()).map(e -> e.get()).map(e -> Either.<TextDocumentEdit, ResourceOperation>forLeft(e)).collect(Collectors.toList());
|
||||
if (edits.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
WorkspaceEdit workspaceEdit = new WorkspaceEdit();
|
||||
workspaceEdit.setDocumentChanges(edits);
|
||||
return workspaceEdit;
|
||||
}
|
||||
|
||||
private WorkspaceEdit perform(Data data) {
|
||||
Optional<IJavaProject> project = projectFinder.find(new TextDocumentIdentifier(data.docUri));
|
||||
if (project.isPresent()) {
|
||||
boolean projectWide = data.recipeScope == RecipeScope.PROJECT;
|
||||
Recipe r = createRecipe(data);
|
||||
if (projectWide) {
|
||||
return applyRecipe(r, project.get(), cuCache.getCompiulationUnits(project.get()));
|
||||
} else {
|
||||
CompilationUnit cu = cuCache.getCU(project.get(), URI.create(data.docUri));
|
||||
if (cu == null) {
|
||||
throw new IllegalStateException("Cannot parse Java file: " + data.docUri);
|
||||
}
|
||||
return applyRecipe(r, project.get(), List.of(cu));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Recipe createRecipe(Data d) {
|
||||
Recipe r = recipeRepo.getRecipe(d.id).orElse(null);
|
||||
if (!(r instanceof DeclarativeRecipe)) {
|
||||
r = RecipeIntrospectionUtils.constructRecipe(r.getClass());
|
||||
}
|
||||
if (d.params != null) {
|
||||
for (Entry<String, Object> entry : d.params.entrySet()) {
|
||||
try {
|
||||
Field f = r.getClass().getDeclaredField(entry.getKey());
|
||||
f.setAccessible(true);
|
||||
f.set(r, entry.getValue());
|
||||
} catch (Exception e) {
|
||||
log.error("", e);;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (d.recipeScope == RecipeScope.NODE) {
|
||||
UUID astNodeId = UUID.fromString(d.scope);
|
||||
if (astNodeId == null) {
|
||||
throw new IllegalArgumentException("Missing scope AST node!");
|
||||
} else {
|
||||
r = ORAstUtils.nodeRecipe(r, j -> j != null && astNodeId.equals(j.getId()));
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public String id;
|
||||
public List<?> arguments;
|
||||
public Data(String id, List<?> arguments) {
|
||||
public String docUri;
|
||||
public RecipeScope recipeScope;
|
||||
public String scope;
|
||||
public Map<String, Object> params;
|
||||
public Data(String id, String docUri, RecipeScope recipeScope, String scope, Map<String, Object> params) {
|
||||
this.id = id;
|
||||
this.arguments = arguments;
|
||||
this.docUri = docUri;
|
||||
this.recipeScope = recipeScope;
|
||||
this.scope = scope;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,154 +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.boot.java.rewrite.codeaction;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionCapabilities;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.CodeActionResolveSupportCapabilities;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.ResourceOperation;
|
||||
import org.eclipse.lsp4j.TextDocumentEdit;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.Result;
|
||||
import org.openrewrite.java.tree.J;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeAction;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient.Client;
|
||||
import org.springframework.ide.vscode.commons.rewrite.ORDocUtils;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public abstract class AbstractRewriteJavaCodeAction implements JavaCodeAction {
|
||||
|
||||
protected ORCompilationUnitCache orCuCache;
|
||||
protected String codeActionId;
|
||||
protected SimpleLanguageServer server;
|
||||
protected JavaProjectFinder projectFinder;
|
||||
|
||||
public AbstractRewriteJavaCodeAction(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, ORCompilationUnitCache orCuCache, String codeActionId) {
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
this.orCuCache = orCuCache;
|
||||
this.codeActionId = codeActionId;
|
||||
rewriteRefactorings.addRefactoring(codeActionId, this::perform);
|
||||
}
|
||||
|
||||
protected CodeAction createCodeAction(String kind, String title, List<?> arguments) {
|
||||
CodeAction ca = new CodeAction();
|
||||
ca.setKind(kind);
|
||||
ca.setTitle(title);
|
||||
ca.setData(new RewriteRefactorings.Data(codeActionId, arguments));
|
||||
return ca;
|
||||
}
|
||||
|
||||
protected WorkspaceEdit applyRecipe(Recipe r, IJavaProject project, List<J.CompilationUnit> cus) {
|
||||
List<Result> results = r.run(cus);
|
||||
List<Either<TextDocumentEdit, ResourceOperation>> edits = results.stream().filter(res -> res.getAfter() != null).map(res -> {
|
||||
URI docUri = project.getLocationUri().resolve(res.getAfter().getSourcePath().toString());
|
||||
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docUri.toString());
|
||||
if (doc == null) {
|
||||
doc = new TextDocument(docUri.toString(), LanguageId.JAVA, 0, res.getBefore() == null ? "" : res.getBefore().printAll());
|
||||
}
|
||||
return ORDocUtils.computeTextDocEdit(doc, res);
|
||||
}).filter(e -> e.isPresent()).map(e -> e.get()).map(e -> Either.<TextDocumentEdit, ResourceOperation>forLeft(e)).collect(Collectors.toList());
|
||||
if (edits.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
WorkspaceEdit workspaceEdit = new WorkspaceEdit();
|
||||
workspaceEdit.setDocumentChanges(edits);
|
||||
return workspaceEdit;
|
||||
}
|
||||
|
||||
protected static boolean isResolve(CodeActionCapabilities capabilities, String property) {
|
||||
if (capabilities != null) {
|
||||
CodeActionResolveSupportCapabilities resolveSupport = capabilities.getResolveSupport();
|
||||
if (resolveSupport != null) {
|
||||
List<String> properties = resolveSupport.getProperties();
|
||||
return properties != null && properties.contains(property);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isSupported(CodeActionCapabilities capabilities, CodeActionContext context) {
|
||||
// Default case is anything non-quick fix related.
|
||||
if (isResolve(capabilities, "edit")) {
|
||||
if (context.getOnly() != null) {
|
||||
return context.getOnly().contains(CodeActionKind.Refactor);
|
||||
} else {
|
||||
if (LspClient.currentClient() == Client.ECLIPSE) {
|
||||
// Eclipse would have no diagnostics in the context for QuickAssists refactoring. Diagnostics will be around for QuickFix only
|
||||
return context.getDiagnostics().isEmpty();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
final public List<Either<Command, CodeAction>> getCodeActions(CodeActionCapabilities capabilities,
|
||||
CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project, CompilationUnit cu,
|
||||
ASTNode node) {
|
||||
if (isSupported(capabilities, context)) {
|
||||
return provideCodeActions(context, doc, region, project, cu, node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final protected WorkspaceEdit perform(List<?> args, Supplier<Recipe> recipeSupplier) {
|
||||
String docUri = (String) args.get(0);
|
||||
boolean projectWide = ((Boolean) args.get(1)).booleanValue();
|
||||
|
||||
Optional<IJavaProject> project = projectFinder.find(new TextDocumentIdentifier(docUri));
|
||||
|
||||
if (project.isPresent()) {
|
||||
if (projectWide) {
|
||||
return applyRecipe(recipeSupplier.get(), project.get(), orCuCache.getCompiulationUnits(project.get()));
|
||||
} else {
|
||||
return orCuCache.withCompilationUnit(project.get(), URI.create(docUri), cu -> {
|
||||
if (cu == null) {
|
||||
throw new IllegalStateException("Cannot parse Java file: " + docUri);
|
||||
}
|
||||
return applyRecipe(recipeSupplier.get(), project.get(), List.of(cu));
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected abstract List<Either<Command, CodeAction>> provideCodeActions(CodeActionContext context, TextDocument doc,
|
||||
IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite.codeaction;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.openrewrite.Cursor;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
import org.openrewrite.java.JavaVisitor;
|
||||
import org.openrewrite.java.tree.J.Block;
|
||||
import org.openrewrite.java.tree.J.ClassDeclaration;
|
||||
import org.openrewrite.java.tree.J.VariableDeclarations;
|
||||
import org.openrewrite.java.tree.JavaType.FullyQualified;
|
||||
import org.openrewrite.java.tree.TypeUtils;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.AnnotationHierarchies;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
|
||||
|
||||
public class AutowiredFieldIntoConstructorParameterCodeAction implements RecipeCodeActionDescriptor {
|
||||
|
||||
private static final String LABEL = "Convert @Autowired field into Constructor Parameter";
|
||||
private static final String ID = "org.springframework.ide.vscode.commons.rewrite.java.ConvertAutowiredFieldIntoConstructorParameter";
|
||||
private static final String AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
|
||||
|
||||
@Override
|
||||
public String getRecipeId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel(RecipeScope s) {
|
||||
return RecipeCodeActionDescriptor.buildLabel(LABEL, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeScope[] getScopes() {
|
||||
return new RecipeScope[] { RecipeScope.NODE };
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaVisitor<ExecutionContext> getMarkerVisitor() {
|
||||
return new JavaIsoVisitor<>() {
|
||||
|
||||
@Override
|
||||
public VariableDeclarations visitVariableDeclarations(VariableDeclarations multiVariable,
|
||||
ExecutionContext p) {
|
||||
VariableDeclarations m = super.visitVariableDeclarations(multiVariable, p);
|
||||
Cursor blockCursor = getCursor().dropParentUntil(Block.class::isInstance);
|
||||
if (multiVariable.getVariables().size() == 1
|
||||
&& multiVariable.getLeadingAnnotations().stream().anyMatch(a -> TypeUtils.isOfClassType(a.getType(), AUTOWIRED))
|
||||
&& blockCursor.getParent().getValue() instanceof ClassDeclaration) {
|
||||
ClassDeclaration classDeclaration = (ClassDeclaration) blockCursor.getParent().getValue();
|
||||
FullyQualified fqType = TypeUtils.asFullyQualified(classDeclaration.getType());
|
||||
if (fqType != null && isApplicableType(fqType)) {
|
||||
m = m.withMarkers(m.getMarkers().add(new FixAssistMarker(Tree.randomId())
|
||||
.withRecipeId(getRecipeId())
|
||||
.withScope(classDeclaration.getId())
|
||||
.withParameters(Map.of("classFqName", fqType.getFullyQualifiedName(), "fieldName", multiVariable.getVariables().get(0).getSimpleName()))));
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
private boolean isApplicableType(FullyQualified type) {
|
||||
return !AnnotationHierarchies
|
||||
.getTransitiveSuperAnnotations(type, fq -> fq.getFullyQualifiedName().startsWith("java."))
|
||||
.contains("org.springframework.boot.test.context.SpringBootTest");
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return springBootVersionGreaterOrEqual(2, 0, 0).test(project);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,84 +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.boot.java.rewrite.codeaction;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.openrewrite.java.spring.BeanMethodsNotPublic;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.BeanMethodNotPublicReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.java.Version;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class BeanMethodsNoPublicCodeAction extends AbstractRewriteJavaCodeAction {
|
||||
|
||||
private static final String REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_FILE = "Remove 'public' from @Bean methods in file";
|
||||
private static final String REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_PROJECT = "Remove 'public' from @Bean methods in project";
|
||||
private static final String CODE_ACTION_ID = "RemovePublicFromBeanMethod";
|
||||
|
||||
public BeanMethodsNoPublicCodeAction(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, ORCompilationUnitCache orCuCache) {
|
||||
super(server, projectFinder, rewriteRefactorings, orCuCache, CODE_ACTION_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Either<Command, CodeAction>> provideCodeActions(CodeActionContext context, TextDocument doc,
|
||||
IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node) {
|
||||
for (; node != null && !(node instanceof MethodDeclaration); node = node.getParent()) {
|
||||
// nothing
|
||||
}
|
||||
if (node instanceof MethodDeclaration) {
|
||||
MethodDeclaration m = (MethodDeclaration) node;
|
||||
IMethodBinding binding = m.resolveBinding();
|
||||
if (binding != null) {
|
||||
Optional<IAnnotationBinding> beanAnnotationPresent = Arrays.stream(binding.getAnnotations())
|
||||
.filter(a -> Annotations.BEAN.equals(a.getAnnotationType().getQualifiedName()))
|
||||
.findFirst();
|
||||
if (beanAnnotationPresent.isPresent() && BeanMethodNotPublicReconciler.isNotOverridingPublicMethod(binding)) {
|
||||
Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT);
|
||||
if (version.getMajor() >= 2) {
|
||||
return List.of(
|
||||
Either.forRight(createCodeAction(CodeActionKind.Refactor, REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_FILE, List.of(doc.getUri(), false))),
|
||||
Either.forRight(createCodeAction(CodeActionKind.Refactor, REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_PROJECT, List.of(doc.getUri(), true)))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkspaceEdit perform(List<?> args) {
|
||||
return perform(args, () -> new BeanMethodsNotPublic());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite.codeaction;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.internal.ListUtils;
|
||||
import org.openrewrite.java.AnnotationMatcher;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
import org.openrewrite.java.JavaVisitor;
|
||||
import org.openrewrite.java.tree.J;
|
||||
import org.openrewrite.java.tree.TypeUtils;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
|
||||
|
||||
public class BeanMethodsNotPublicCodeAction implements RecipeCodeActionDescriptor {
|
||||
|
||||
private static final String ID = "org.openrewrite.java.spring.BeanMethodsNotPublic";
|
||||
|
||||
private static final String LABEL = "Remove 'public' from @Bean method";
|
||||
|
||||
private static final AnnotationMatcher BEAN_ANNOTATION_MATCHER = new AnnotationMatcher("@org.springframework.context.annotation.Bean");
|
||||
|
||||
@Override
|
||||
public String getRecipeId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel(RecipeScope s) {
|
||||
return RecipeCodeActionDescriptor.buildLabel(LABEL, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeScope[] getScopes() {
|
||||
return RecipeScope.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaVisitor<ExecutionContext> getMarkerVisitor() {
|
||||
return new JavaIsoVisitor<ExecutionContext>() {
|
||||
|
||||
@Override
|
||||
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) {
|
||||
J.MethodDeclaration m = super.visitMethodDeclaration(method, executionContext);
|
||||
|
||||
if (m.getAllAnnotations().stream().anyMatch(BEAN_ANNOTATION_MATCHER::matches)
|
||||
&& Boolean.FALSE.equals(TypeUtils.isOverride(method.getMethodType()))) {
|
||||
// mark public modifier
|
||||
FixAssistMarker fixAssistMarker = new FixAssistMarker(Tree.randomId())
|
||||
.withRecipeId(ID)
|
||||
.withScope(m.getId());
|
||||
m = m.withModifiers(ListUtils.map(m.getModifiers(), modifier -> {
|
||||
if (modifier.getType() == J.Modifier.Type.Public) {
|
||||
return modifier.withMarkers(modifier.getMarkers().add(fixAssistMarker));
|
||||
}
|
||||
return modifier;
|
||||
}));
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT) != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,103 +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.boot.java.rewrite.codeaction;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.FieldDeclaration;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.ConvertAutowiredParameterIntoConstructorParameter;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class ConvertAutowiredField extends AbstractRewriteJavaCodeAction {
|
||||
|
||||
private static final String CODE_ACTION_ID = "ConvertAutowiredParameterIntoConstructorParameter";
|
||||
|
||||
public ConvertAutowiredField(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, ORCompilationUnitCache orCuCache) {
|
||||
super(server, projectFinder, rewriteRefactorings, orCuCache, CODE_ACTION_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkspaceEdit perform(List<?> args) {
|
||||
String docUri = (String) args.get(0);
|
||||
String classFqName = (String) args.get(1);
|
||||
String fieldName = (String) args.get(2);
|
||||
|
||||
Optional<IJavaProject> project = projectFinder.find(new TextDocumentIdentifier(docUri));
|
||||
|
||||
if (project.isPresent()) {
|
||||
return orCuCache.withCompilationUnit(project.get(), URI.create(docUri), cu -> {
|
||||
if (cu == null) {
|
||||
throw new IllegalStateException("Cannot parse Java file: " + docUri);
|
||||
}
|
||||
return applyRecipe(new ConvertAutowiredParameterIntoConstructorParameter(classFqName, fieldName), project.get(), List.of(cu));
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Either<Command, CodeAction>> provideCodeActions(CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project,
|
||||
CompilationUnit cu, ASTNode node) {
|
||||
for (; node != null && !(node instanceof FieldDeclaration); node = node.getParent()) {
|
||||
// nothing
|
||||
}
|
||||
if (node instanceof FieldDeclaration) {
|
||||
FieldDeclaration fd = (FieldDeclaration) node;
|
||||
|
||||
if (fd.fragments().size() == 1) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Optional<Annotation> autowired = fd.modifiers().stream().filter(Annotation.class::isInstance)
|
||||
.map(Annotation.class::cast).filter(a -> {
|
||||
IAnnotationBinding binding = ((Annotation) a).resolveAnnotationBinding();
|
||||
if (binding != null && binding.getAnnotationType() != null) {
|
||||
return Annotations.AUTOWIRED.equals(binding.getAnnotationType().getQualifiedName());
|
||||
}
|
||||
return false;
|
||||
}).findFirst();
|
||||
|
||||
if (autowired.isPresent()) {
|
||||
|
||||
return List.of(Either.forRight(createCodeAction(CodeActionKind.Refactor, "Convert into Constructor Parameter",
|
||||
List.of(doc.getId().getUri(),
|
||||
((TypeDeclaration) fd.getParent()).resolveBinding().getQualifiedName(),
|
||||
((VariableDeclarationFragment) fd.fragments().get(0)).getName().getIdentifier()))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,105 +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.boot.java.rewrite.codeaction;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.openrewrite.java.MethodMatcher;
|
||||
import org.openrewrite.java.spring.NoRequestMappingAnnotation;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.ORAstUtils;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class NoRequestMapping extends NoRequestMappings {
|
||||
|
||||
private static final String CODE_ACTION_ID = "RemoveRequestMappings";
|
||||
|
||||
public NoRequestMapping(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, ORCompilationUnitCache orCuCache) {
|
||||
super(server, projectFinder, rewriteRefactorings, orCuCache, CODE_ACTION_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkspaceEdit perform(List<?> args) {
|
||||
String docUri = (String) args.get(0);
|
||||
String matchStr = (String) args.get(1);
|
||||
|
||||
Optional<IJavaProject> project = projectFinder.find(new TextDocumentIdentifier(docUri));
|
||||
|
||||
if (project.isPresent()) {
|
||||
return orCuCache.withCompilationUnit(project.get(), URI.create(docUri), cu -> {
|
||||
if (cu == null) {
|
||||
throw new IllegalStateException("Cannot parse Java file: " + docUri);
|
||||
}
|
||||
MethodMatcher macther = new MethodMatcher(matchStr);
|
||||
|
||||
return applyRecipe(ORAstUtils.nodeRecipe(new NoRequestMappingAnnotation(), t -> {
|
||||
if (t instanceof org.openrewrite.java.tree.J.MethodDeclaration) {
|
||||
org.openrewrite.java.tree.J.MethodDeclaration m = (org.openrewrite.java.tree.J.MethodDeclaration) t;
|
||||
return macther.matches(m.getMethodType());
|
||||
}
|
||||
return false;
|
||||
}), project.get(), List.of(cu));
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Either<Command, CodeAction>> provideCodeActions(CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project,
|
||||
CompilationUnit cu, ASTNode node) {
|
||||
return findAppropriateMethodDeclaration(node).map(method -> {
|
||||
String methodMatcher = "* " + method.getName().getIdentifier() + "(*)";
|
||||
IMethodBinding methodBinding = method.resolveBinding();
|
||||
if (methodBinding != null) {
|
||||
StringBuilder sb = new StringBuilder(methodBinding.getDeclaringClass().getQualifiedName());
|
||||
sb.append(' ');
|
||||
sb.append(methodBinding.getName());
|
||||
sb.append('(');
|
||||
sb.append(Arrays.stream(methodBinding.getParameterTypes()).map(b -> {
|
||||
if (b.isParameterizedType() ) {
|
||||
return b.getErasure().getQualifiedName();
|
||||
}
|
||||
return b.getQualifiedName();
|
||||
}).collect(Collectors.joining(",")));
|
||||
sb.append(')');
|
||||
methodMatcher = sb.toString();
|
||||
}
|
||||
return createCodeAction(CodeActionKind.Refactor, "Replace single @RequestMapping with @GetMapping etc.", List.of(
|
||||
doc.getId().getUri(),
|
||||
methodMatcher
|
||||
));
|
||||
}).map(ca -> List.of(Either.<Command, CodeAction>forRight(ca))).orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite.codeaction;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.java.AnnotationMatcher;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
import org.openrewrite.java.JavaVisitor;
|
||||
import org.openrewrite.java.tree.J;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
|
||||
|
||||
public class NoRequestMappingAnnotationCodeAction implements RecipeCodeActionDescriptor {
|
||||
|
||||
private static final String LABEL = "Replace @RequestMapping with specific @GetMapping, @PostMapping etc.";
|
||||
private static final String ID = "org.openrewrite.java.spring.NoRequestMappingAnnotation";
|
||||
private static final AnnotationMatcher REQUEST_MAPPING_ANNOTATION_MATCHER = new AnnotationMatcher("@org.springframework.web.bind.annotation.RequestMapping");
|
||||
|
||||
@Override
|
||||
public String getRecipeId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel(RecipeScope s) {
|
||||
return RecipeCodeActionDescriptor.buildLabel(LABEL, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeScope[] getScopes() {
|
||||
return RecipeScope.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaVisitor<ExecutionContext> getMarkerVisitor() {
|
||||
return new JavaIsoVisitor<ExecutionContext>() {
|
||||
@Override
|
||||
public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) {
|
||||
J.Annotation a = super.visitAnnotation(annotation, ctx);
|
||||
if (REQUEST_MAPPING_ANNOTATION_MATCHER.matches(a) && getCursor().getParentOrThrow().getValue() instanceof J.MethodDeclaration) {
|
||||
FixAssistMarker fixAssistMarker = new FixAssistMarker(Tree.randomId())
|
||||
.withRecipeId(getRecipeId())
|
||||
.withScope(a.getId());
|
||||
a = a.withMarkers(a.getMarkers().add(fixAssistMarker));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return springBootVersionGreaterOrEqual(2, 0, 0).test(project);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,85 +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.boot.java.rewrite.codeaction;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.openrewrite.java.spring.NoRequestMappingAnnotation;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class NoRequestMappings extends AbstractRewriteJavaCodeAction {
|
||||
|
||||
private static final String CODE_ACTION_ID = "RemoveAllRequestMappings";
|
||||
|
||||
public NoRequestMappings(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, ORCompilationUnitCache orCuCache) {
|
||||
this(server, projectFinder, rewriteRefactorings, orCuCache, CODE_ACTION_ID);
|
||||
}
|
||||
|
||||
public NoRequestMappings(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, ORCompilationUnitCache orCuCache, String codeActionId) {
|
||||
super(server, projectFinder, rewriteRefactorings, orCuCache, codeActionId);
|
||||
}
|
||||
|
||||
final protected Optional<MethodDeclaration> findAppropriateMethodDeclaration(ASTNode node) {
|
||||
for (; node != null && !(node instanceof Annotation); node = node.getParent()) {
|
||||
// nothing
|
||||
}
|
||||
if (node instanceof Annotation) {
|
||||
Annotation a = (Annotation) node;
|
||||
ITypeBinding type = a.resolveTypeBinding();
|
||||
if (type != null && Annotations.SPRING_REQUEST_MAPPING.equals(type.getQualifiedName())) {
|
||||
if (a.getParent() instanceof MethodDeclaration) {
|
||||
return Optional.of((MethodDeclaration) a.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkspaceEdit perform(List<?> args) {
|
||||
return perform(args, () -> new NoRequestMappingAnnotation());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Either<Command, CodeAction>> provideCodeActions(CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project,
|
||||
CompilationUnit cu, ASTNode node) {
|
||||
return findAppropriateMethodDeclaration(node).map(m -> List.of(
|
||||
Either.<Command, CodeAction>forRight(createCodeAction(CodeActionKind.Refactor, "Replace @RequestMapping with @GetMapping etc. in file", List.of(doc.getId().getUri(), false))),
|
||||
Either.<Command, CodeAction>forRight(createCodeAction(CodeActionKind.Refactor, "Replace @RequestMapping with @GetMapping etc. in project", List.of(doc.getId().getUri(), true)))
|
||||
)).orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -10,105 +10,93 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.rewrite.codeaction;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionCapabilities;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.openrewrite.java.spring.boot2.UnnecessarySpringExtension;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.UnnecessarySpringExtensionReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.internal.ListUtils;
|
||||
import org.openrewrite.java.AnnotationMatcher;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
import org.openrewrite.java.JavaVisitor;
|
||||
import org.openrewrite.java.tree.TypeUtils;
|
||||
import org.openrewrite.java.tree.J.ClassDeclaration;
|
||||
import org.openrewrite.java.tree.JavaType.FullyQualified;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient.Client;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
public class UnnecessarySpringExtensionCodeAction implements RecipeCodeActionDescriptor {
|
||||
|
||||
public class UnnecessarySpringExtensionCodeAction extends AbstractRewriteJavaCodeAction {
|
||||
|
||||
private static final String CODE_ACTION_ID = "RemoveUnnecessarySpringExtension";
|
||||
private static final String LABEL = "Remove unnecessary @SpringExtension";
|
||||
private static final String ID = "org.openrewrite.java.spring.boot2.UnnecessarySpringExtension";
|
||||
private static final List<String> SPRING_BOOT_TEST_ANNOTATIONS = Arrays.asList(
|
||||
"org.springframework.boot.test.context.SpringBootTest",
|
||||
"org.springframework.boot.test.autoconfigure.jdbc.JdbcTest",
|
||||
"org.springframework.boot.test.autoconfigure.web.client.RestClientTest",
|
||||
"org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest",
|
||||
"org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest",
|
||||
"org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest",
|
||||
"org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest",
|
||||
"org.springframework.boot.test.autoconfigure.jooq.JooqTest",
|
||||
"org.springframework.boot.test.autoconfigure.json.JsonTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest",
|
||||
"org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest"
|
||||
);
|
||||
private static final AnnotationMatcher SPRING_EXTENSION_ANNOTATIN_MATCHER = new AnnotationMatcher("@org.junit.jupiter.api.extension.ExtendWith(org.springframework.test.context.junit.jupiter.SpringExtension.class)");
|
||||
|
||||
public UnnecessarySpringExtensionCodeAction(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
RewriteRefactorings rewriteRefactorings, ORCompilationUnitCache orCuCache) {
|
||||
super(server, projectFinder, rewriteRefactorings, orCuCache, CODE_ACTION_ID);
|
||||
}
|
||||
|
||||
protected boolean isSupported(CodeActionCapabilities capabilities, CodeActionContext context) {
|
||||
// Default case is anything non-quick fix related.
|
||||
if (isResolve(capabilities, "edit")) {
|
||||
if (context.getOnly() != null) {
|
||||
return context.getOnly().contains(CodeActionKind.Refactor) || context.getOnly().contains(CodeActionKind.QuickFix);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@Override
|
||||
public String getRecipeId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Either<Command, CodeAction>> provideCodeActions(CodeActionContext context, TextDocument doc,
|
||||
IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node) {
|
||||
for (; node != null && !(node instanceof Annotation); node = node.getParent()) {
|
||||
// nothing
|
||||
}
|
||||
if (node instanceof Annotation) {
|
||||
Annotation a = (Annotation) node;
|
||||
ITypeBinding binding = a.resolveTypeBinding();
|
||||
if (binding != null && UnnecessarySpringExtensionReconciler.isUnnecessarySpringExtensionAnnotation(project,
|
||||
a, binding)) {
|
||||
Builder<Either<Command, CodeAction>> listBuilder = ImmutableList.builder();
|
||||
if (context.getOnly() == null) {
|
||||
if (LspClient.currentClient() == Client.ECLIPSE) {
|
||||
if (context.getDiagnostics().isEmpty()) {
|
||||
listBuilder.add(createRefactoringForProject(doc.getUri()));
|
||||
} else {
|
||||
listBuilder.add(createQuickfixForFile(doc.getUri()));
|
||||
public String getLabel(RecipeScope s) {
|
||||
return RecipeCodeActionDescriptor.buildLabel(LABEL, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeScope[] getScopes() {
|
||||
return new RecipeScope[] { RecipeScope.PROJECT };
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaVisitor<ExecutionContext> getMarkerVisitor() {
|
||||
return new JavaIsoVisitor<>() {
|
||||
|
||||
@Override
|
||||
public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext p) {
|
||||
ClassDeclaration c = super.visitClassDeclaration(classDecl, p);
|
||||
if (c.getLeadingAnnotations().stream().anyMatch(a -> {
|
||||
FullyQualified fq = TypeUtils.asFullyQualified(a.getType());
|
||||
return fq != null && SPRING_BOOT_TEST_ANNOTATIONS.contains(fq.getFullyQualifiedName());
|
||||
})) {
|
||||
UUID id = c.getId();
|
||||
c = c.withLeadingAnnotations(ListUtils.map(c.getLeadingAnnotations(), a -> {
|
||||
if (SPRING_EXTENSION_ANNOTATIN_MATCHER.matches(a)) {
|
||||
return a.withMarkers(a.getMarkers().add(new FixAssistMarker(Tree.randomId()).withRecipeId(ID).withScope(id)));
|
||||
}
|
||||
} else {
|
||||
listBuilder.add(createQuickfixForFile(doc.getUri()));
|
||||
listBuilder.add(createRefactoringForProject(doc.getUri()));
|
||||
}
|
||||
} else {
|
||||
if (context.getOnly().contains(CodeActionKind.QuickFix)) {
|
||||
listBuilder.add(createQuickfixForFile(doc.getUri()));
|
||||
}
|
||||
if (context.getOnly().contains(CodeActionKind.Refactor)) {
|
||||
listBuilder.add(createRefactoringForProject(doc.getUri()));
|
||||
}
|
||||
return a;
|
||||
}));
|
||||
}
|
||||
return listBuilder.build();
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Either<Command, CodeAction> createQuickfixForFile(String docUri) {
|
||||
return Either.forRight(createCodeAction(CodeActionKind.QuickFix,
|
||||
"Remove unnecessary @SpringExtension in file", List.of(docUri, false)));
|
||||
}
|
||||
|
||||
private Either<Command, CodeAction> createRefactoringForProject(String docUri) {
|
||||
return Either.forRight(createCodeAction(CodeActionKind.Refactor,
|
||||
"Remove unnecessary @SpringExtension in project", List.of(docUri, true)));
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkspaceEdit perform(List<?> args) {
|
||||
return perform(args, () -> new UnnecessarySpringExtension());
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return springBootVersionGreaterOrEqual(2, 1, 0).test(project);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,64 +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.boot.java.rewrite.quickfix;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.openrewrite.java.spring.NoAutowiredOnConstructor;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class AutowiredConstructorQuickFixHandler extends RewriteQuickFixHandler {
|
||||
|
||||
final private SimpleLanguageServer server;
|
||||
final private JavaProjectFinder projectFinder;
|
||||
final private ORCompilationUnitCache orCuCache;
|
||||
|
||||
public AutowiredConstructorQuickFixHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder, ORCompilationUnitCache orCuCache) {
|
||||
super();
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
this.orCuCache = orCuCache;
|
||||
}
|
||||
|
||||
protected WorkspaceEdit perform(List<?> args) {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
String docUri = (String) args.get(0);
|
||||
String classFqName = (String) args.get(1);
|
||||
TextDocument doc = documents.getLatestSnapshot(docUri);
|
||||
|
||||
Optional<IJavaProject> project = projectFinder.find(new TextDocumentIdentifier(docUri));
|
||||
|
||||
if (project.isPresent()) {
|
||||
return removeUnnecessaryAutowiredFromConstructor(project.get(), doc, classFqName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private WorkspaceEdit removeUnnecessaryAutowiredFromConstructor(IJavaProject project, TextDocument doc, String classFqName) {
|
||||
return applyRecipe(orCuCache, new NoAutowiredOnConstructor(), project, doc, t -> {
|
||||
if (t instanceof org.openrewrite.java.tree.J.ClassDeclaration) {
|
||||
org.openrewrite.java.tree.J.ClassDeclaration c = (org.openrewrite.java.tree.J.ClassDeclaration) t;
|
||||
return c.getType() != null && classFqName.equals(c.getType().getFullyQualifiedName());
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +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.boot.java.rewrite.quickfix;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.openrewrite.java.MethodMatcher;
|
||||
import org.openrewrite.java.spring.BeanMethodsNotPublic;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class BeanMethodNoPublicQuickFixHandler extends RewriteQuickFixHandler {
|
||||
|
||||
final private SimpleLanguageServer server;
|
||||
final private JavaProjectFinder projectFinder;
|
||||
final private ORCompilationUnitCache orCuCache;
|
||||
|
||||
|
||||
public BeanMethodNoPublicQuickFixHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
ORCompilationUnitCache orCuCache) {
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
this.orCuCache = orCuCache;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected WorkspaceEdit perform(List<?> args) {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
String docUri = (String) args.get(0);
|
||||
String methodMatch = (String) args.get(1);
|
||||
TextDocument doc = documents.getLatestSnapshot(docUri);
|
||||
|
||||
Optional<IJavaProject> project = projectFinder.find(new TextDocumentIdentifier(docUri));
|
||||
|
||||
if (project.isPresent()) {
|
||||
MethodMatcher matcher = new MethodMatcher(methodMatch);
|
||||
return applyRecipe(orCuCache, new BeanMethodsNotPublic(), project.get(), doc, t -> {
|
||||
if (t instanceof org.openrewrite.java.tree.J.MethodDeclaration) {
|
||||
org.openrewrite.java.tree.J.MethodDeclaration m = (org.openrewrite.java.tree.J.MethodDeclaration) t;
|
||||
return matcher.matches(m.getMethodType());
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,81 +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.boot.java.rewrite.quickfix;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentEdit;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.Result;
|
||||
import org.openrewrite.java.JavaVisitor;
|
||||
import org.openrewrite.java.tree.J;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixHandler;
|
||||
import org.springframework.ide.vscode.commons.rewrite.ORDocUtils;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.ORAstUtils;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
public abstract class RewriteQuickFixHandler implements QuickfixHandler {
|
||||
|
||||
final static private Gson gson = new Gson();
|
||||
|
||||
@Override
|
||||
public QuickfixEdit createEdits(Object p) {
|
||||
if (p instanceof JsonElement) {
|
||||
List<?> l = gson.fromJson((JsonElement) p, List.class);
|
||||
return new QuickfixEdit(perform(l), null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract protected WorkspaceEdit perform(List<?> l);
|
||||
|
||||
final protected WorkspaceEdit applyRecipe(ORCompilationUnitCache orCuCache, Recipe r, IJavaProject project, TextDocument doc, Predicate<J> nodeFilter) {
|
||||
return doApplyRecipe(orCuCache, ORAstUtils.nodeRecipe(r, nodeFilter), project, doc);
|
||||
}
|
||||
|
||||
final protected WorkspaceEdit applyVisitor(ORCompilationUnitCache orCuCache, JavaVisitor<ExecutionContext> v, IJavaProject project, TextDocument doc, Predicate<J> nodeFilter) {
|
||||
return doApplyRecipe(orCuCache, ORAstUtils.nodeRecipe(v, nodeFilter), project, doc);
|
||||
}
|
||||
|
||||
final protected WorkspaceEdit doApplyRecipe(ORCompilationUnitCache orCuCache, Recipe r, IJavaProject project, TextDocument doc) {
|
||||
String docUri = doc.getId().getUri();
|
||||
return orCuCache.withCompilationUnit(project, URI.create(docUri), cu -> {
|
||||
if (cu == null) {
|
||||
throw new IllegalStateException("Cannot parse Java file: " + docUri);
|
||||
}
|
||||
List<Result> results = r.run(List.of(cu));
|
||||
if (!results.isEmpty() && results.get(0).getAfter() != null) {
|
||||
Optional<TextDocumentEdit> edit = ORDocUtils.computeTextDocEdit(doc, results.get(0));
|
||||
return edit.map(e -> {
|
||||
WorkspaceEdit workspaceEdit = new WorkspaceEdit();
|
||||
workspaceEdit.setDocumentChanges(List.of(Either.forLeft(e)));
|
||||
return workspaceEdit;
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite.reconcile;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.SpringJavaProblemType;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNotPublicCodeAction;
|
||||
|
||||
public class BeanMethodNotPublicProblem extends BeanMethodsNotPublicCodeAction implements RecipeSpringJavaProblemDescriptor {
|
||||
|
||||
@Override
|
||||
public RecipeScope[] getScopes() {
|
||||
return new RecipeScope[] { RecipeScope.NODE };
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpringJavaProblemType getProblemType() {
|
||||
return SpringJavaProblemType.JAVA_PUBLIC_BEAN_METHOD;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite.reconcile;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
|
||||
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.internal.ListUtils;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
import org.openrewrite.java.JavaVisitor;
|
||||
import org.openrewrite.java.tree.J;
|
||||
import org.openrewrite.java.tree.Statement;
|
||||
import org.openrewrite.java.tree.TypeUtils;
|
||||
import org.openrewrite.java.tree.J.ClassDeclaration;
|
||||
import org.openrewrite.java.tree.J.MethodDeclaration;
|
||||
import org.openrewrite.java.tree.JavaType.FullyQualified;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.SpringJavaProblemType;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.AnnotationHierarchies;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
|
||||
|
||||
public class NoAutowiredOnConstructorProblem implements RecipeSpringJavaProblemDescriptor {
|
||||
|
||||
private static final String ID = "org.openrewrite.java.spring.NoAutowiredOnConstructor";
|
||||
private static final String LABEL = "Remove Unnecessary @Autowired";
|
||||
|
||||
@Override
|
||||
public String getRecipeId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel(RecipeScope s) {
|
||||
return LABEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeScope[] getScopes() {
|
||||
return new RecipeScope[] { RecipeScope.NODE };
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaVisitor<ExecutionContext> getMarkerVisitor() {
|
||||
return new JavaIsoVisitor<ExecutionContext>() {
|
||||
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext context) {
|
||||
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, context);
|
||||
|
||||
int constructorCount = 0;
|
||||
for(Statement s : cd.getBody().getStatements()) {
|
||||
if(isConstructor(s)) {
|
||||
constructorCount++;
|
||||
if(constructorCount > 1) {
|
||||
return cd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FullyQualified type = TypeUtils.asFullyQualified(classDecl.getType());
|
||||
if (type != null && isApplicableType(type)) {
|
||||
return cd.withBody(cd.getBody().withStatements(
|
||||
ListUtils.map(cd.getBody().getStatements(), s -> {
|
||||
if(!isConstructor(s)) {
|
||||
return s;
|
||||
}
|
||||
MethodDeclaration constructor = (MethodDeclaration) s;
|
||||
FixAssistMarker fixAssistMarker = new FixAssistMarker(Tree.randomId())
|
||||
.withRecipeId(ID)
|
||||
.withScope(getCursor().firstEnclosing(ClassDeclaration.class).getId());
|
||||
constructor = constructor.withLeadingAnnotations(ListUtils.map(constructor.getLeadingAnnotations(), a -> {
|
||||
if (TypeUtils.isOfClassType(a.getType(), Annotations.AUTOWIRED)) {
|
||||
a = a.withMarkers(a.getMarkers().add(fixAssistMarker));
|
||||
}
|
||||
return a;
|
||||
}));
|
||||
return constructor;
|
||||
})
|
||||
));
|
||||
}
|
||||
return cd;
|
||||
}
|
||||
|
||||
private boolean isApplicableType(FullyQualified type) {
|
||||
return !AnnotationHierarchies
|
||||
.getTransitiveSuperAnnotations(type, fq -> fq.getFullyQualifiedName().startsWith("java."))
|
||||
.contains("org.springframework.boot.test.context.SpringBootTest");
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
return springBootVersionGreaterOrEqual(2, 0, 0).test(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpringJavaProblemType getProblemType() {
|
||||
return SpringJavaProblemType.JAVA_AUTOWIRED_CONSTRUCTOR;
|
||||
}
|
||||
|
||||
private static boolean isConstructor(Statement s) {
|
||||
return s instanceof J.MethodDeclaration && ((J.MethodDeclaration)s).isConstructor();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite.reconcile;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.SpringJavaProblemType;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor;
|
||||
|
||||
public interface RecipeSpringJavaProblemDescriptor extends RecipeCodeActionDescriptor {
|
||||
|
||||
SpringJavaProblemType getProblemType();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.java.rewrite.reconcile;
|
||||
|
||||
import org.springframework.ide.vscode.boot.java.SpringJavaProblemType;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.UnnecessarySpringExtensionCodeAction;
|
||||
|
||||
public class UnnecessarySpringExtensionProblem extends UnnecessarySpringExtensionCodeAction
|
||||
implements RecipeSpringJavaProblemDescriptor {
|
||||
|
||||
@Override
|
||||
public RecipeScope[] getScopes() {
|
||||
return new RecipeScope[] { RecipeScope.FILE };
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpringJavaProblemType getProblemType() {
|
||||
return SpringJavaProblemType.JAVA_TEST_SPRING_EXTENSION;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,6 +42,8 @@ import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JavaReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SymbolCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SymbolCacheVoid;
|
||||
@@ -143,7 +145,9 @@ public class ValueSpelExpressionValidationTest {
|
||||
docUri = directory.toPath().resolve("src/main/java/org/test/TestValueCompletion.java").toUri().toString();
|
||||
|
||||
problemCollector = new TestProblemCollector();
|
||||
reconcileEngine = new BootJavaReconcileEngine(server, compilationUnitCache, projectFinder);
|
||||
reconcileEngine = new BootJavaReconcileEngine(projectFinder, new JavaReconciler[] {
|
||||
new JdtReconciler(compilationUnitCache)
|
||||
});
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@@ -103,22 +103,7 @@ export function activate(context: VSCode.ExtensionContext): Thenable<ExtensionAP
|
||||
enableJdtClasspath: VSCode.extensions.getExtension('redhat.java')?.exports?.serverMode === 'Standard'
|
||||
})
|
||||
},
|
||||
highlightCodeLensSettingKey: 'boot-java.highlight-codelens.on',
|
||||
vmArgs: [
|
||||
'--add-modules=ALL-SYSTEM',
|
||||
'--add-exports',
|
||||
'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
|
||||
'--add-exports',
|
||||
'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
|
||||
'--add-exports',
|
||||
'jdk.compiler/com.sun.tools.javac.comp=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.code=ALL-UNNAMED'
|
||||
]
|
||||
highlightCodeLensSettingKey: 'boot-java.highlight-codelens.on'
|
||||
};
|
||||
|
||||
// Register launch config contributior to java debug launch to be able to connect to JMX
|
||||
|
||||
Reference in New Issue
Block a user