Better rewrite recipe integration into IDE quick fix and assist

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

View File

@@ -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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

@@ -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);
}
})
);
}

View File

@@ -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");
});

View File

@@ -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();
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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;
});
}
}

View File

@@ -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;
}
}

View File

@@ -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;
});
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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