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 4cede7916..99a3e960c 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 @@ -83,6 +83,7 @@ import org.springframework.ide.vscode.commons.languageserver.LanguageServerRunne import org.springframework.ide.vscode.commons.languageserver.java.FutureProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry; import org.springframework.ide.vscode.commons.languageserver.util.DocumentEventListenerManager; import org.springframework.ide.vscode.commons.languageserver.util.LanguageComputer; import org.springframework.ide.vscode.commons.languageserver.util.LspClient; @@ -241,8 +242,8 @@ public class BootLanguageServerBootApp { return new CompilationUnitCache(params.projectFinder, server, params.projectObserver); } - @Bean JdtReconciler jdtReconciler(CompilationUnitCache cuCache, BootJavaConfig config) { - return new JdtReconciler(cuCache, config); + @Bean JdtReconciler jdtReconciler(CompilationUnitCache cuCache, RewriteRecipeRepository recipeRepo, BootJavaConfig config, SimpleLanguageServer server) { + return new JdtReconciler(cuCache, recipeRepo, server.getQuickfixRegistry(), config); } @Bean SpringXMLCompletionEngine xmlCompletionEngine(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex, BootJavaConfig config) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java index bf0a8ee7c..adb6dd8e9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java @@ -56,6 +56,7 @@ import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationReconciler; import org.springframework.ide.vscode.boot.java.reconcilers.BeanMethodNotPublicReconciler; +import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository; import org.springframework.ide.vscode.boot.java.utils.DocumentDescriptor; import org.springframework.ide.vscode.boot.java.utils.SpringFactoriesIndexer; import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; @@ -71,6 +72,7 @@ import org.springframework.ide.vscode.commons.languageserver.java.FutureProjectF import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener; +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.util.ListenerList; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; @@ -101,6 +103,7 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex { @Autowired IndexCache cache; @Autowired FutureProjectFinder futureProjectFinder; @Autowired SpringMetamodelIndex springIndex; + @Autowired RewriteRecipeRepository recipeRepo; private static final String QUERY_PARAM_LOCATION_PREFIX = "locationPrefix:"; @@ -246,7 +249,7 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex { springIndexerXML = new SpringIndexerXML(handler, namespaceHandler, this.cache, projectFinder()); List reconcilers = new ArrayList<>(); - reconcilers.add(new BeanMethodNotPublicReconciler()); + reconcilers.add(new BeanMethodNotPublicReconciler(recipeRepo, server.getQuickfixRegistry())); BiFunction, BiConsumer, IProblemCollector> problemCollectorFactory = (docRef, aggregator) -> server.createProblemCollector(docRef, aggregator); springIndexerJava = new SpringIndexerJava(handler, specificProviders, this.cache, projectFinder(), server.getProgressService(), reconcilers, problemCollectorFactory); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java index 2c1f81f2a..3f9183efc 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java @@ -18,21 +18,41 @@ import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Modifier; +import org.openrewrite.java.spring.BeanMethodsNotPublic; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; +import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository; +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.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.ReconcileProblemImpl; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; +import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; import org.springframework.ide.vscode.commons.util.text.IDocument; public class BeanMethodNotPublicReconciler implements AnnotationReconciler { private static final Logger log = LoggerFactory.getLogger(BeanMethodNotPublicReconciler.class); + private static final String ID = BeanMethodsNotPublic.class.getName(); + private static final String LABEL = "Remove 'public' from @Bean method"; + + private final RewriteRecipeRepository recipeRepo; + private final QuickfixRegistry quickfixRegistry; + + public BeanMethodNotPublicReconciler(RewriteRecipeRepository recipeRepo, QuickfixRegistry quickfixRegistry) { + this.recipeRepo = recipeRepo; + this.quickfixRegistry = quickfixRegistry; + } + @Override public void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding, IProblemCollector problemCollector) { @@ -56,6 +76,8 @@ public class BeanMethodNotPublicReconciler implements AnnotationReconciler { .orElse(new ReconcileProblemImpl( Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD, "super special DIAGNOSTICS with public @Bean method", m.getName().getStartPosition(), m.getName().getLength())); + + addQuickFixes(doc.getUri(), problem); problemCollector.accept(problem); } @@ -79,4 +101,28 @@ public class BeanMethodNotPublicReconciler implements AnnotationReconciler { return !isOverriding(methodBinding) && (methodBinding.getModifiers() & Modifier.PUBLIC) != 0; } + private void addQuickFixes(String uri, ReconcileProblemImpl problem) { + + if (recipeRepo != null && quickfixRegistry != null) { + + FixDescriptor fix1 = new FixDescriptor(ID, List.of(uri), LABEL) + // .withRangeScope(m.getMarkers().findFirst(Range.class).get()) // TODO create OpenRewrite range + .withRecipeScope(RecipeScope.NODE); + + FixDescriptor fix2 = new FixDescriptor(ID, List.of(uri), RecipeCodeActionDescriptor.buildLabel(LABEL, RecipeScope.FILE)) + .withRecipeScope(RecipeScope.FILE); + + FixDescriptor fix3 = new FixDescriptor(ID, List.of(uri), RecipeCodeActionDescriptor.buildLabel(LABEL, RecipeScope.PROJECT)) + .withRecipeScope(RecipeScope.PROJECT); + + QuickfixType quickfixType = quickfixRegistry.getQuickfixType(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX); + + if (quickfixType != null && recipeRepo.getRecipe(ID).isPresent()) { + problem.addQuickfix(new QuickfixData<>(quickfixType, fix1, fix1.getLabel())); + problem.addQuickfix(new QuickfixData<>(quickfixType, fix2, fix2.getLabel())); + problem.addQuickfix(new QuickfixData<>(quickfixType, fix3, fix3.getLabel())); + } + } + } + } 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 index 989fd19b1..83052aae3 100644 --- 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 @@ -28,8 +28,10 @@ import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.app.BootJavaConfig; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.handlers.SpelExpressionReconciler; +import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; import org.springframework.ide.vscode.commons.java.IJavaProject; +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.ReconcileProblem; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -58,7 +60,7 @@ public class JdtReconciler implements JavaReconciler { private BootJavaConfig config; - public JdtReconciler(CompilationUnitCache compilationUnitCache, BootJavaConfig config) { + public JdtReconciler(CompilationUnitCache compilationUnitCache, RewriteRecipeRepository recipeRepo, QuickfixRegistry quickfixRegistry, BootJavaConfig config) { this.compilationUnitCache = compilationUnitCache; this.config = config; config.addListener(evt -> setSpelExpressionSyntaxValidationEnabled(config.isSpelExpressionValidationEnabled())); @@ -90,7 +92,7 @@ public class JdtReconciler implements JavaReconciler { new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, null, "", "", spelExpressionReconciler), new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", "", spelExpressionReconciler), - new BeanMethodNotPublicReconciler() + new BeanMethodNotPublicReconciler(recipeRepo, quickfixRegistry) }; } 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 d583b8a1e..82953d783 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 @@ -148,7 +148,7 @@ public class ValueSpelExpressionValidationTest { problemCollector = new TestProblemCollector(); reconcileEngine = new BootJavaReconcileEngine(projectFinder, new JavaReconciler[] { - new JdtReconciler(compilationUnitCache, config) + new JdtReconciler(compilationUnitCache, null, null, config) }, server); }