diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/SpringBootLanguageServer.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/SpringBootLanguageServer.java index 250787a4b..e17c26c70 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/SpringBootLanguageServer.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/SpringBootLanguageServer.java @@ -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; diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java index c10b1368e..7e2e76a8a 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/SpringProjectUtil.java @@ -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 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; + }; + } } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index 290788df9..80b18f29c 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -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 diff --git a/headless-services/commons/commons-rewrite/pom.xml b/headless-services/commons/commons-rewrite/pom.xml index d73591866..cd3dabf0d 100644 --- a/headless-services/commons/commons-rewrite/pom.xml +++ b/headless-services/commons/commons-rewrite/pom.xml @@ -43,6 +43,16 @@ rewrite-java ${rewrite-version} + + org.openrewrite + rewrite-java-11 + ${rewrite-version} + + + org.openrewrite + rewrite-java-17 + ${rewrite-version} + com.fasterxml.jackson.core @@ -81,11 +91,6 @@ ${rewrite-spring-version} - - org.openrewrite - rewrite-java-11 - ${rewrite-version} - diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java index 499fc5a67..559109af6 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/LoadUtils.java @@ -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)); } diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AnnotationHierarchies.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AnnotationHierarchies.java new file mode 100644 index 000000000..638545605 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AnnotationHierarchies.java @@ -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 getDirectSuperAnnotations(FullyQualified type, Predicate ignore) { + List annotations = type.getAnnotations(); + if (annotations != null && !annotations.isEmpty()) { + ImmutableList.Builder 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 getTransitiveSuperAnnotations(FullyQualified type, Predicate ignore) { + Set seen = new HashSet<>(); + if (type != null) { + findTransitiveSupers(type, seen, ignore).collect(Collectors.toList()); + } + return seen; + } + + public static Stream findTransitiveSupers(FullyQualified type, Set seen, Predicate 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; + } + +} \ No newline at end of file diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ConvertAutowiredParameterIntoConstructorParameter.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ConvertAutowiredFieldIntoConstructorParameter.java similarity index 95% rename from headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ConvertAutowiredParameterIntoConstructorParameter.java rename to headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ConvertAutowiredFieldIntoConstructorParameter.java index fa29fe7eb..4d6685444 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ConvertAutowiredParameterIntoConstructorParameter.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ConvertAutowiredFieldIntoConstructorParameter.java @@ -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 getSingleSourceApplicableTest() { + return new UsesType(AUTOWIRED); + } @Override protected TreeVisitor 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())); diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java new file mode 100644 index 000000000..d98a5e7c9 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java @@ -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 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 parameters) { + this.parameters = parameters; + return this; + } + + public Map 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); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java index 202dfd6da..f5ab8e47d 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java @@ -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 f = new AtomicReference<>(); -// new JavaIsoVisitor>() { -// public J visit(Tree tree, AtomicReference 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 f = new AtomicReference<>(); + new JavaIsoVisitor>() { + public J visit(Tree tree, AtomicReference 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 findNode(J node, Class clazz) { // if (clazz.isInstance(node)) { @@ -214,20 +220,18 @@ public class ORAstUtils { public static List parse(JavaParser parser, Iterable 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 cus = parser.parse(sourceFiles, null, ctx); - return cus; -// List 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 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 parseInputs(JavaParser parser, Iterable 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 cus = parser.parseInputs(inputs, null, ctx); - return cus; -// List 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 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 getVisitor(Recipe r) { - try { - Method m = Recipe.class.getDeclaredMethod("getVisitor"); - m.setAccessible(true); - return (TreeVisitor) m.invoke(r); - } catch (Exception e) { - return null; - } - } - @SuppressWarnings("unchecked") private static List> getAfterVisitors(TreeVisitor visitor) { try { @@ -298,7 +291,7 @@ public class ORAstUtils { @SuppressWarnings("unchecked") public static Recipe nodeRecipe(Recipe r, Predicate condition) { - return new NodeRecipe((JavaVisitor) getVisitor(r), condition); + return new NodeRecipe((JavaVisitor) RecipeIntrospectionUtils.recipeVisitor(r), condition); } private static class NodeRecipe extends Recipe { diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/maven/ChangeDependencyClassifier.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/maven/ChangeDependencyClassifier.java deleted file mode 100644 index 60f1258ee..000000000 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/maven/ChangeDependencyClassifier.java +++ /dev/null @@ -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 getVisitor() { - return new MavenVisitor() { - @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 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("" + newClassifier + ""))); - } - } - } - - 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); - } - -} diff --git a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot3-upgrade.yml b/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot3-upgrade.yml index 6bfb32258..d0c63a287 100644 --- a/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot3-upgrade.yml +++ b/headless-services/commons/commons-rewrite/src/main/resources/META-INF/rewrite/spring-boot3-upgrade.yml @@ -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: "*" diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java index be8201307..72256b676 100644 --- a/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java +++ b/headless-services/commons/commons-rewrite/src/test/java/org/springframework/ide/vscode/commons/rewrite/LoadUtilsTest.java @@ -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()); diff --git a/headless-services/commons/pom.xml b/headless-services/commons/pom.xml index a399d651e..1030c3978 100644 --- a/headless-services/commons/pom.xml +++ b/headless-services/commons/pom.xml @@ -109,8 +109,8 @@ 1.13 - 7.23.0 - 4.21.0 + 7.24.1 + 4.22.1 2.13.2 true diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java index a6f05ff1c..b08190e80 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java @@ -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 diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java index 306e2babc..fe4ba0591 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java @@ -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); + } + }) + ); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index 94a6c2b50..4b59cd65b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -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"); }); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeActionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeActionProvider.java index 083677e8b..882b625a5 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeActionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCodeActionProvider.java @@ -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 javaCodeActions; + final private Collection handlers; - public BootJavaCodeActionProvider(JavaProjectFinder projectFinder, CompilationUnitCache cuCache, Collection javaCodeActions) { + public BootJavaCodeActionProvider(JavaProjectFinder projectFinder, Collection handlers) { this.projectFinder = projectFinder; - this.cuCache = cuCache; - this.javaCodeActions = javaCodeActions; + this.handlers = handlers; } - + @Override public List> handle(CancelChecker cancelToken, CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region) { Optional 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> codeActions = new ArrayList<>(); - for (JavaCodeAction jca : javaCodeActions) { - List> 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(); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java index 4bd5962ae..616cf4439 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java @@ -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); - } - } - } - + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/JavaCodeActionHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/JavaCodeActionHandler.java new file mode 100644 index 000000000..9b281c0ca --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/JavaCodeActionHandler.java @@ -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> handle(IJavaProject project, CancelChecker cancelToken, CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region); + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/JavaReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/JavaReconciler.java new file mode 100644 index 000000000..e5abcb479 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/JavaReconciler.java @@ -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); + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/JdtReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/JdtReconciler.java new file mode 100644 index 000000000..81642d7c8 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/JdtReconciler.java @@ -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); + } + } + } +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/UnnecessarySpringExtensionReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/UnnecessarySpringExtensionReconciler.java deleted file mode 100644 index 8102d9671..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/UnnecessarySpringExtensionReconciler.java +++ /dev/null @@ -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 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 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; - } - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeCodeActionDescriptor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeCodeActionDescriptor.java new file mode 100644 index 000000000..856076292 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeCodeActionDescriptor.java @@ -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 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; + } + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeScope.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeScope.java new file mode 100644 index 000000000..9c3530365 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeScope.java @@ -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 +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java new file mode 100644 index 000000000..e0cd42ad1 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java @@ -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 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> 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 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 codeActions = new ArrayList<>(); + new JavaIsoVisitor() { + 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.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; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORCompilationUnitCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java similarity index 85% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORCompilationUnitCache.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java index 8d3b0cfba..c9d307495 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/ORCompilationUnitCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCompilationUnitCache.java @@ -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 uriToCu; private final Cache> projectToDocs; private final Cache 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() { - - @Override - public void onRemoval(RemovalNotification notification) { - invalidateCuForJavaFile(notification.getKey().toString()); - } - }) +// .expireAfterWrite(CU_ACCESS_EXPIRATION, TimeUnit.MINUTES) +// .removalListener(new RemovalListener() { +// +// @Override +// public void onRemoval(RemovalNotification 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 withCompilationUnit(IJavaProject project, URI uri, Function 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 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 withCompilationUnit(IJavaProject project, URI uri, Function 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); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java index b39eba0d4..582b6709e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java @@ -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 codeActionDescriptors = List.of( + new AutowiredFieldIntoConstructorParameterCodeAction(), + new BeanMethodsNotPublicCodeAction(), + new NoRequestMappingAnnotationCodeAction(), + new UnnecessarySpringExtensionCodeAction() + ); + + private List 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 getProblemRecipeDescriptors() { + return javaProblemDescriptors; + } + + public List getCodeActionRecipeDescriptors() { + return codeActionDescriptors; + } + + public List getApplicableCodeActionRecipeDescriptors(IJavaProject project, List descriptors) { + List filtered = new ArrayList<>(descriptors.size()); + for (RecipeCodeActionDescriptor d : descriptors) { + if (d.isApplicable(project)) { + filtered.add(d); + } + } + return filtered; + } + + public CompilationUnit mark(List descriptors, CompilationUnit compilationUnit) { + CompilationUnit cu = compilationUnit; + for (RecipeCodeActionDescriptor d : descriptors) { + Recipe recipe = getRecipe(d.getRecipeId()).orElse(null); + if (recipe != null) { + TreeVisitor isApplicableVisitor = RecipeIntrospectionUtils.recipeSingleSourceApplicableTest(recipe); + TreeVisitor 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; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java new file mode 100644 index 000000000..4c1014385 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java @@ -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 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() { + + @Override + public @Nullable J visit(@Nullable Tree tree, ExecutionContext context) { + J t = super.visit(tree, context); + if (t instanceof J) { + List 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 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; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java index 8640c70cb..2ff0a12ad 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java @@ -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, WorkspaceEdit>> refactoringsMap = new ConcurrentHashMap<>(); + private RewriteRecipeRepository recipeRepo; - public void addRefactoring(String id, Function, 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) (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, 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 cus) { + List results = r.run(cus); + List> 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.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 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 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 params; + public Data(String id, String docUri, RecipeScope recipeScope, String scope, Map params) { this.id = id; - this.arguments = arguments; + this.docUri = docUri; + this.recipeScope = recipeScope; + this.scope = scope; + this.params = params; } + } - + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java deleted file mode 100644 index 484bce2b8..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java +++ /dev/null @@ -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 cus) { - List results = r.run(cus); - List> 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.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 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> 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 recipeSupplier) { - String docUri = (String) args.get(0); - boolean projectWide = ((Boolean) args.get(1)).booleanValue(); - - Optional 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> provideCodeActions(CodeActionContext context, TextDocument doc, - IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node); - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AutowiredFieldIntoConstructorParameterCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AutowiredFieldIntoConstructorParameterCodeAction.java new file mode 100644 index 000000000..0b0ce4574 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AutowiredFieldIntoConstructorParameterCodeAction.java @@ -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 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); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNoPublicCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNoPublicCodeAction.java deleted file mode 100644 index 13f3dc350..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNoPublicCodeAction.java +++ /dev/null @@ -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> 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 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()); - } - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNotPublicCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNotPublicCodeAction.java new file mode 100644 index 000000000..7f9b9fe57 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNotPublicCodeAction.java @@ -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 getMarkerVisitor() { + return new JavaIsoVisitor() { + + @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; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java deleted file mode 100644 index 7d691f32f..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java +++ /dev/null @@ -1,103 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 VMware, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VMware, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.rewrite.codeaction; - -import java.net.URI; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.FieldDeclaration; -import org.eclipse.jdt.core.dom.IAnnotationBinding; -import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.jdt.core.dom.VariableDeclarationFragment; -import org.eclipse.lsp4j.CodeAction; -import org.eclipse.lsp4j.CodeActionContext; -import org.eclipse.lsp4j.CodeActionKind; -import org.eclipse.lsp4j.Command; -import org.eclipse.lsp4j.TextDocumentIdentifier; -import org.eclipse.lsp4j.WorkspaceEdit; -import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.springframework.ide.vscode.boot.java.Annotations; -import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache; -import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings; -import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; -import org.springframework.ide.vscode.commons.rewrite.java.ConvertAutowiredParameterIntoConstructorParameter; -import org.springframework.ide.vscode.commons.util.text.IRegion; -import org.springframework.ide.vscode.commons.util.text.TextDocument; - -public class ConvertAutowiredField extends AbstractRewriteJavaCodeAction { - - private static final String CODE_ACTION_ID = "ConvertAutowiredParameterIntoConstructorParameter"; - - public ConvertAutowiredField(SimpleLanguageServer server, JavaProjectFinder projectFinder, - RewriteRefactorings rewriteRefactorings, ORCompilationUnitCache orCuCache) { - super(server, projectFinder, rewriteRefactorings, orCuCache, CODE_ACTION_ID); - } - - @Override - public WorkspaceEdit perform(List args) { - String docUri = (String) args.get(0); - String classFqName = (String) args.get(1); - String fieldName = (String) args.get(2); - - Optional 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> 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 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(); - } - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java deleted file mode 100644 index ede144041..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java +++ /dev/null @@ -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 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> 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.forRight(ca))).orElse(Collections.emptyList()); - } - - - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappingAnnotationCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappingAnnotationCodeAction.java new file mode 100644 index 000000000..b7320079b --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappingAnnotationCodeAction.java @@ -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 getMarkerVisitor() { + return new JavaIsoVisitor() { + @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); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java deleted file mode 100644 index d6171ebe3..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java +++ /dev/null @@ -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 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> provideCodeActions(CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project, - CompilationUnit cu, ASTNode node) { - return findAppropriateMethodDeclaration(node).map(m -> List.of( - Either.forRight(createCodeAction(CodeActionKind.Refactor, "Replace @RequestMapping with @GetMapping etc. in file", List.of(doc.getId().getUri(), false))), - Either.forRight(createCodeAction(CodeActionKind.Refactor, "Replace @RequestMapping with @GetMapping etc. in project", List.of(doc.getId().getUri(), true))) - )).orElse(Collections.emptyList()); - } - - - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/UnnecessarySpringExtensionCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/UnnecessarySpringExtensionCodeAction.java index 789231c3b..09bae1583 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/UnnecessarySpringExtensionCodeAction.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/UnnecessarySpringExtensionCodeAction.java @@ -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 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> 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> 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 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 createQuickfixForFile(String docUri) { - return Either.forRight(createCodeAction(CodeActionKind.QuickFix, - "Remove unnecessary @SpringExtension in file", List.of(docUri, false))); - } - - private Either 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); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/AutowiredConstructorQuickFixHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/AutowiredConstructorQuickFixHandler.java deleted file mode 100644 index ffb2c343a..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/AutowiredConstructorQuickFixHandler.java +++ /dev/null @@ -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 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; - }); - } - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/BeanMethodNoPublicQuickFixHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/BeanMethodNoPublicQuickFixHandler.java deleted file mode 100644 index 135a79059..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/BeanMethodNoPublicQuickFixHandler.java +++ /dev/null @@ -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 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; - } - - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/RewriteQuickFixHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/RewriteQuickFixHandler.java deleted file mode 100644 index 5655b86bc..000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/quickfix/RewriteQuickFixHandler.java +++ /dev/null @@ -1,81 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 VMware, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VMware, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.rewrite.quickfix; - -import java.net.URI; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; - -import org.eclipse.lsp4j.TextDocumentEdit; -import org.eclipse.lsp4j.WorkspaceEdit; -import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Recipe; -import org.openrewrite.Result; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.tree.J; -import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache; -import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit; -import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixHandler; -import org.springframework.ide.vscode.commons.rewrite.ORDocUtils; -import org.springframework.ide.vscode.commons.rewrite.java.ORAstUtils; -import org.springframework.ide.vscode.commons.util.text.TextDocument; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; - -public abstract class RewriteQuickFixHandler implements QuickfixHandler { - - final static private Gson gson = new Gson(); - - @Override - public QuickfixEdit createEdits(Object p) { - if (p instanceof JsonElement) { - List l = gson.fromJson((JsonElement) p, List.class); - return new QuickfixEdit(perform(l), null); - } - return null; - } - - abstract protected WorkspaceEdit perform(List l); - - final protected WorkspaceEdit applyRecipe(ORCompilationUnitCache orCuCache, Recipe r, IJavaProject project, TextDocument doc, Predicate nodeFilter) { - return doApplyRecipe(orCuCache, ORAstUtils.nodeRecipe(r, nodeFilter), project, doc); - } - - final protected WorkspaceEdit applyVisitor(ORCompilationUnitCache orCuCache, JavaVisitor v, IJavaProject project, TextDocument doc, Predicate 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 results = r.run(List.of(cu)); - if (!results.isEmpty() && results.get(0).getAfter() != null) { - Optional 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; - }); - - } - -} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/BeanMethodNotPublicProblem.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/BeanMethodNotPublicProblem.java new file mode 100644 index 000000000..973ba3431 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/BeanMethodNotPublicProblem.java @@ -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; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/NoAutowiredOnConstructorProblem.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/NoAutowiredOnConstructorProblem.java new file mode 100644 index 000000000..04e83b411 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/NoAutowiredOnConstructorProblem.java @@ -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 getMarkerVisitor() { + return new JavaIsoVisitor() { + 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(); + } + + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/RecipeSpringJavaProblemDescriptor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/RecipeSpringJavaProblemDescriptor.java new file mode 100644 index 000000000..827c88920 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/RecipeSpringJavaProblemDescriptor.java @@ -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(); + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/UnnecessarySpringExtensionProblem.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/UnnecessarySpringExtensionProblem.java new file mode 100644 index 000000000..1d5b4bcb8 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/UnnecessarySpringExtensionProblem.java @@ -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; + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java index fa3eef6e7..a01640b8a 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java @@ -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 diff --git a/vscode-extensions/vscode-spring-boot/lib/Main.ts b/vscode-extensions/vscode-spring-boot/lib/Main.ts index 20520a450..31a4f730a 100644 --- a/vscode-extensions/vscode-spring-boot/lib/Main.ts +++ b/vscode-extensions/vscode-spring-boot/lib/Main.ts @@ -103,22 +103,7 @@ export function activate(context: VSCode.ExtensionContext): Thenable