From a2503612bc56226c6ce8464964c87819caf6ee68 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Mon, 24 Mar 2025 13:59:47 +0100 Subject: [PATCH] GH-1499: re-validating bean registrars now when config classes change their import annotation expressions --- .../vscode/boot/app/SpringSymbolIndex.java | 2 +- .../boot/index/SpringMetamodelIndex.java | 20 +++ .../boot/java/cron/JdtCronReconciler.java | 7 +- .../jpa/queries/QueryJdtAstReconciler.java | 10 +- .../AbstractSecurityLamdaDslReconciler.java | 7 +- ...ConfigurationIfBeansPresentReconciler.java | 5 +- .../AuthorizeHttpRequestsReconciler.java | 7 +- ...eldIntoConstructorParameterReconciler.java | 13 +- .../BeanMethodNotPublicReconciler.java | 8 +- ...anPostProcessingIgnoreInAotReconciler.java | 7 +- .../BeanRegistrarDeclarationReconciler.java | 90 +++++++++- .../Boot3NotSupportedTypeReconciler.java | 7 +- .../EntityIdForRepoReconciler.java | 5 +- .../ImplicitWebAnnotationNamesReconciler.java | 5 +- .../java/reconcilers/JdtAstReconciler.java | 3 +- .../boot/java/reconcilers/JdtReconciler.java | 17 +- ...ulithTypeReferenceViolationReconciler.java | 5 +- .../NoAutowiredOnConstructorReconciler.java | 5 +- .../NoRepoAnnotationReconciler.java | 5 +- .../NoRequestMappingAnnotationReconciler.java | 7 +- .../NotRegisteredBeansReconciler.java | 4 +- .../PreciseBeanTypeReconciler.java | 9 +- .../java/reconcilers/ReconcilingContext.java | 84 ++++++++++ .../UnnecessarySpringExtensionReconciler.java | 5 +- ...ebSecurityConfigurerAdapterReconciler.java | 5 +- .../rewrite/RewriteCodeActionHandler.java | 10 +- .../boot/java/spel/JdtSpelReconciler.java | 9 +- .../boot/java/utils/SpringIndexerJava.java | 39 ++++- .../java/utils/SpringIndexerJavaContext.java | 12 +- .../utils/SpringIndexerJavaScanResult.java | 21 ++- .../reconcilers/test/BaseReconcilerTest.java | 9 +- .../BeanRegistrarAdvancedReconcilingTest.java | 156 ++++++++++++++++++ ...eanRegistrarDeclarationReconcilerTest.java | 2 +- .../RequestMappingSymbolProviderTest.java | 14 +- .../java/utils/test/TestFileScanListener.java | 4 + ...r.java => NotRegisteredBeanRegistrar.java} | 2 +- 36 files changed, 501 insertions(+), 119 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ReconcilingContext.java create mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BeanRegistrarAdvancedReconcilingTest.java rename headless-services/spring-boot-language-server/src/test/resources/test-projects/test-framework-7-indexing/src/main/java/com/example/{NotRegisterredBeanRegistrar.java => NotRegisteredBeanRegistrar.java} (82%) 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 f080b0385..227934df0 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 @@ -235,7 +235,7 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex { @Override public void removeSymbols(IJavaProject project, String docURI) { SpringSymbolIndex.this.removeSymbolsByDoc(project, docURI); - springIndex.removeElements(project.getElementName(), docURI); +// springIndex.removeElements(project.getElementName(), docURI); // TODO remove diagnostics ?!? maybe, maybe not diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java index d2cc36ea1..bf2664c68 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/index/SpringMetamodelIndex.java @@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.index; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -249,4 +250,23 @@ public class SpringMetamodelIndex { return (Bean[]) result.toArray(new Bean[result.size()]); } + public static List getNodesOfType(Class type, Collection rootNodes) { + List result = new ArrayList<>(); + + ArrayDeque elementsToVisit = new ArrayDeque<>(); + elementsToVisit.addAll(rootNodes); + + while (!elementsToVisit.isEmpty()) { + SpringIndexElement element = elementsToVisit.pop(); + + if (type.isInstance(element)) { + result.add(type.cast(element)); + } + + elementsToVisit.addAll(element.getChildren()); + } + + return result; + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/cron/JdtCronReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/cron/JdtCronReconciler.java index f2ec3f4dd..e5cc6c3aa 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/cron/JdtCronReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/cron/JdtCronReconciler.java @@ -17,9 +17,9 @@ import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.springframework.ide.vscode.boot.java.embedded.lang.EmbeddedLanguageSnippet; import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.ReconcilingContext; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; -import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; public class JdtCronReconciler implements JdtAstReconciler { @@ -41,14 +41,13 @@ public class JdtCronReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, - IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @Override public boolean visit(NormalAnnotation node) { EmbeddedLanguageSnippet e = JdtCronVisitorUtils.extractCron(node); if (e != null) { - cronReconciler.reconcile(e.getText(), e::toSingleJavaRange, problemCollector); + cronReconciler.reconcile(e.getText(), e::toSingleJavaRange, context.getProblemCollector()); } return super.visit(node); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/jpa/queries/QueryJdtAstReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/jpa/queries/QueryJdtAstReconciler.java index a2f7808df..f9f2293cb 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/jpa/queries/QueryJdtAstReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/jpa/queries/QueryJdtAstReconciler.java @@ -25,11 +25,11 @@ import org.springframework.ide.vscode.boot.java.data.jpa.queries.JdtQueryVisitor import org.springframework.ide.vscode.boot.java.embedded.lang.AntlrReconcilerWithSpel; import org.springframework.ide.vscode.boot.java.handlers.Reconciler; import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.ReconcilingContext; import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException; import org.springframework.ide.vscode.boot.java.spel.SpelReconciler; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; -import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.parser.mysql.MySqlLexer; import org.springframework.ide.vscode.parser.mysql.MySqlParser; @@ -54,7 +54,7 @@ public class QueryJdtAstReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) throws RequiredCompleteAstException { + public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, ReconcilingContext context) throws RequiredCompleteAstException { AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu); return new ASTVisitor() { @@ -63,7 +63,7 @@ public class QueryJdtAstReconciler implements JdtAstReconciler { EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(annotationHierarchies, node); if (q != null) { Optional reconcilerOpt = q.isNative() ? getSqlReconciler(project) : Optional.of(getQueryReconciler(project)); - reconcilerOpt.ifPresent(r -> r.reconcile(q.query().getText(), q.query()::toSingleJavaRange, problemCollector)); + reconcilerOpt.ifPresent(r -> r.reconcile(q.query().getText(), q.query()::toSingleJavaRange, context.getProblemCollector())); } return super.visit(node); } @@ -72,7 +72,7 @@ public class QueryJdtAstReconciler implements JdtAstReconciler { public boolean visit(SingleMemberAnnotation node) { EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(annotationHierarchies, node); if (q != null) { - getQueryReconciler(project).reconcile(q.query().getText(), q.query()::toSingleJavaRange, problemCollector); + getQueryReconciler(project).reconcile(q.query().getText(), q.query()::toSingleJavaRange, context.getProblemCollector()); } return super.visit(node); } @@ -81,7 +81,7 @@ public class QueryJdtAstReconciler implements JdtAstReconciler { public boolean visit(MethodInvocation node) { EmbeddedQueryExpression q = JdtQueryVisitorUtils.extractQueryExpression(node); if (q != null) { - getQueryReconciler(project).reconcile(q.query().getText(), q.query()::toSingleJavaRange, problemCollector); + getQueryReconciler(project).reconcile(q.query().getText(), q.query()::toSingleJavaRange, context.getProblemCollector()); } return super.visit(node); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AbstractSecurityLamdaDslReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AbstractSecurityLamdaDslReconciler.java index dfab39d4c..8fefc43ce 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AbstractSecurityLamdaDslReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AbstractSecurityLamdaDslReconciler.java @@ -20,7 +20,6 @@ import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodInvocation; 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.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; @@ -34,9 +33,9 @@ public abstract class AbstractSecurityLamdaDslReconciler implements JdtAstReconc } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { - if (isCompleteAst) { + if (context.isCompleteAst()) { return new ASTVisitor() { @Override @@ -59,7 +58,7 @@ public abstract class AbstractSecurityLamdaDslReconciler implements JdtAstReconc ReconcileUtils.buildLabel(getFixLabel(), RecipeScope.PROJECT)) .withRecipeScope(RecipeScope.PROJECT) )); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); return false; } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java index 60a3f239f..28ed4c426 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AddConfigurationIfBeansPresentReconciler.java @@ -33,7 +33,6 @@ import org.springframework.ide.vscode.commons.Version; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; 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.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.protocol.spring.Bean; @@ -65,7 +64,7 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @Override @@ -85,7 +84,7 @@ public class AddConfigurationIfBeansPresentReconciler implements JdtAstReconcile ReconcileUtils.buildLabel(FIX_LABEL, RecipeScope.PROJECT)) .withRecipeScope(RecipeScope.PROJECT))); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); } return true; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AuthorizeHttpRequestsReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AuthorizeHttpRequestsReconciler.java index d6bcb9d59..d1732c673 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AuthorizeHttpRequestsReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AuthorizeHttpRequestsReconciler.java @@ -23,7 +23,6 @@ import org.springframework.ide.vscode.commons.Version; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; 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.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; @@ -57,9 +56,9 @@ public class AuthorizeHttpRequestsReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { - if (isCompleteAst) { + if (context.isCompleteAst()) { return new ASTVisitor() { @Override @@ -82,7 +81,7 @@ public class AuthorizeHttpRequestsReconciler implements JdtAstReconciler { ReconcileUtils.buildLabel(AUTHORIZE_REQUESTS_FIX_LABEL, RecipeScope.PROJECT)) .withRecipeScope(RecipeScope.PROJECT))); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); return false; } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AutowiredFieldIntoConstructorParameterReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AutowiredFieldIntoConstructorParameterReconciler.java index 2312de8ba..e0bd10b31 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AutowiredFieldIntoConstructorParameterReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/AutowiredFieldIntoConstructorParameterReconciler.java @@ -39,7 +39,6 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchie import org.springframework.ide.vscode.commons.java.IClasspathUtil; 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.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; @@ -67,7 +66,7 @@ public class AutowiredFieldIntoConstructorParameterReconciler implements JdtAstR } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { Path sourceFile = Paths.get(docUri); // Check if source file belongs to non-test java sources folder if (IClasspathUtil.getProjectJavaSourceFoldersWithoutTests(project.getClasspath()) @@ -91,14 +90,14 @@ public class AutowiredFieldIntoConstructorParameterReconciler implements JdtAstR String fieldName = variableDeclarationFragment.getName().getIdentifier(); if (constructors.isEmpty()) { - problemCollector.accept(createProblem(cu, field, fieldName, docUri)); + context.getProblemCollector().accept(createProblem(cu, field, fieldName, docUri)); } else if (constructors.size() == 1) { - if (!isCompleteAst) { + if (!context.isCompleteAst()) { throw new RequiredCompleteAstException(); } if (!isAssigningField(constructors.get(0), variableDeclarationFragment.resolveBinding(), fieldName)) { - problemCollector.accept(createProblem(cu, field, fieldName, docUri)); + context.getProblemCollector().accept(createProblem(cu, field, fieldName, docUri)); } } else { List autowiredConstructors = constructors.stream() @@ -106,11 +105,11 @@ public class AutowiredFieldIntoConstructorParameterReconciler implements JdtAstR Annotations.AUTOWIRED, true) != null) .limit(2).collect(Collectors.toList()); if (autowiredConstructors.size() == 1) { - if (!isCompleteAst) { + if (!context.isCompleteAst()) { throw new RequiredCompleteAstException(); } else if (!isAssigningField(autowiredConstructors.get(0), variableDeclarationFragment.resolveBinding(), fieldName)) { - problemCollector.accept(createProblem(cu, field, fieldName, docUri)); + context.getProblemCollector().accept(createProblem(cu, field, fieldName, docUri)); } } } 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 0622af7b8..79407bd2b 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 @@ -66,14 +66,14 @@ public class BeanMethodNotPublicReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @Override public boolean visit(SingleMemberAnnotation node) { try { - visitAnnotation(project, cu, docUri, node, problemCollector); + visitAnnotation(project, cu, docUri, node, context.getProblemCollector()); } catch (Exception e) { } return super.visit(node); @@ -82,7 +82,7 @@ public class BeanMethodNotPublicReconciler implements JdtAstReconciler { @Override public boolean visit(NormalAnnotation node) { try { - visitAnnotation(project, cu, docUri, node, problemCollector); + visitAnnotation(project, cu, docUri, node, context.getProblemCollector()); } catch (Exception e) { } return super.visit(node); @@ -91,7 +91,7 @@ public class BeanMethodNotPublicReconciler implements JdtAstReconciler { @Override public boolean visit(MarkerAnnotation node) { try { - visitAnnotation(project, cu, docUri, node, problemCollector); + visitAnnotation(project, cu, docUri, node, context.getProblemCollector()); } catch (Exception e) { } return super.visit(node); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanPostProcessingIgnoreInAotReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanPostProcessingIgnoreInAotReconciler.java index b8dd8402a..f976dd907 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanPostProcessingIgnoreInAotReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanPostProcessingIgnoreInAotReconciler.java @@ -25,7 +25,6 @@ import org.eclipse.jdt.core.dom.TypeDeclaration; import org.springframework.ide.vscode.boot.java.SpringAotJavaProblemType; 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.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; @@ -56,7 +55,7 @@ public class BeanPostProcessingIgnoreInAotReconciler implements JdtAstReconciler } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @@ -73,7 +72,7 @@ public class BeanPostProcessingIgnoreInAotReconciler implements JdtAstReconciler } } if (foundMethod != null) { - if (isCompleteAst) { + if (context.isCompleteAst()) { AtomicBoolean returnsTrue = new AtomicBoolean(false); foundMethod.accept(new ASTVisitor() { @Override @@ -99,7 +98,7 @@ public class BeanPostProcessingIgnoreInAotReconciler implements JdtAstReconciler .withRangeScope(ReconcileUtils.createOpenRewriteRange(cu, typeDecl, null)) .withRecipeScope(RecipeScope.NODE) )); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); } } return true; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanRegistrarDeclarationReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanRegistrarDeclarationReconciler.java index 6c105a61b..0ef398fcb 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanRegistrarDeclarationReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanRegistrarDeclarationReconciler.java @@ -14,6 +14,7 @@ import java.net.URI; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -33,13 +34,16 @@ import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; 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.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; import org.springframework.ide.vscode.commons.rewrite.java.ImportBeanRegistrarInConfigRecipe; +import org.springframework.ide.vscode.commons.util.UriUtil; + +import com.google.common.collect.Streams; public class BeanRegistrarDeclarationReconciler implements JdtAstReconciler { @@ -63,8 +67,7 @@ public class BeanRegistrarDeclarationReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, - IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @Override @@ -75,11 +78,14 @@ public class BeanRegistrarDeclarationReconciler implements JdtAstReconciler { return true; } + // this is not a bean registrar, but maybe a config class that has changed and is now importing a bean registrar + // so we need to mark the bean registrar for reconciling if (ASTUtils.findInTypeHierarchy(type, Set.of(Annotations.BEAN_REGISTRAR_INTERFACE)) == null) { + identifyPossibleRegistrarsForReconciling(context); return true; } - if (!isIndexComplete) { + if (!context.isIndexComplete()) { throw new RequiredCompleteIndexException(); } @@ -99,7 +105,9 @@ public class BeanRegistrarDeclarationReconciler implements JdtAstReconciler { } } - if (configBeans.isEmpty() || !isImportedBeanRegistrarInConfig(configBeans, type)) { + List importingBeanRegistrarConfigs = getImportedBeanRegistrarConfigs(configBeans, type); + if (configBeans.isEmpty() || importingBeanRegistrarConfigs.size() == 0) { + ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), "Bean not registered", node.getName().getStartPosition(), node.getName().getLength()); List fixes = configBeans.stream() .filter(b -> b.getLocation() != null && b.getLocation().getUri() != null) @@ -111,7 +119,18 @@ public class BeanRegistrarDeclarationReconciler implements JdtAstReconciler { .withRecipeScope(RecipeScope.FILE) ).toList(); ReconcileUtils.setRewriteFixes(registry, problem, fixes); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); + + // record dependencies on types where we found import annotations for this bean registrar + // mark this file + + } + else { + // record dependencies on types where we found import annotations for this bean registrar + for (String typeOfConfigClassWithImport : importingBeanRegistrarConfigs) { + context.addDependency(typeOfConfigClassWithImport); + } + } return true; } @@ -119,12 +138,65 @@ public class BeanRegistrarDeclarationReconciler implements JdtAstReconciler { }; } - private boolean isImportedBeanRegistrarInConfig(List configBeans, ITypeBinding beanRegType) { + private List getImportedBeanRegistrarConfigs(List configBeans, ITypeBinding beanRegType) { return configBeans.stream() - .flatMap(bean -> Arrays.stream(bean.getAnnotations())) // look into annotations on this bean definition + .filter(configBean -> isImportingBeanRegistrar(configBean, beanRegType)) + .map(configBean -> configBean.getType()) + .toList(); + } + + private boolean isImportingBeanRegistrar(Bean configBean, ITypeBinding beanRegType) { + if (configBean.getAnnotations() == null) { + return false; + } + else { + return Arrays.stream(configBean.getAnnotations()) + .filter(annotation -> Annotations.IMPORT.equals(annotation.getAnnotationType())) // look into @Import annotations only + .flatMap(annotation -> annotation.getAttributes().get("value") == null ? Stream.empty() : Arrays.stream(annotation.getAttributes().get("value"))) // look into the attribute values of "value" attribute + .anyMatch(annotationValue -> annotationValue.getName().equals(beanRegType.getQualifiedName())); + } + } + + /** + * This is basically the reverse check: it tries to identify possible bean registrars that need to be + * reconciled because the type this reconciler is currently looking at refers to it or has referred to it. + */ + protected void identifyPossibleRegistrarsForReconciling(ReconcilingContext context) { + List createdIndexElements = context.getCreatedIndexElements(); + List previuosIndexElements = springIndex.getDocument(context.getDocURI()) != null ? + springIndex.getDocument(context.getDocURI()).getChildren() : Collections.emptyList(); + + Set importedTypesDelta = getImportAnnotationTypesDelta(createdIndexElements, previuosIndexElements); + + Arrays.stream(springIndex.getBeans()) + .filter(bean -> bean.isTypeCompatibleWith(Annotations.BEAN_REGISTRAR_INTERFACE)) + .filter(bean -> importedTypesDelta.contains(bean.getType())) + .map(bean -> bean.getLocation().getUri()) + .map(docURI -> UriUtil.toFileString(docURI)) + .forEach(file -> context.markForAffetcedFilesIndexing(file)); + } + + public Set getImportAnnotationTypesDelta(List updatedElements, List previousElements) { + Set updatedImportedTypes = getImportAnnotationTypes(updatedElements); + Set previousImportedTypes = getImportAnnotationTypes(previousElements); + + return Streams.concat(updatedImportedTypes.stream(), previousImportedTypes.stream() + .filter(element -> + (updatedImportedTypes.contains(element) && !previousImportedTypes.contains(element)) + || (!updatedImportedTypes.contains(element) && previousImportedTypes.contains(element)))) + .collect(Collectors.toSet()); + } + + public Set getImportAnnotationTypes(List elements) { + return elements.stream() + .filter(element -> element instanceof Bean) + .map(element -> (Bean) element) + .flatMap(bean -> Arrays.stream(bean.getAnnotations())) .filter(annotation -> Annotations.IMPORT.equals(annotation.getAnnotationType())) // look into @Import annotations only .flatMap(annotation -> annotation.getAttributes().get("value") == null ? Stream.empty() : Arrays.stream(annotation.getAttributes().get("value"))) // look into the attribute values of "value" attribute - .anyMatch(annotationValue -> annotationValue.getName().equals(beanRegType.getQualifiedName())); + .map(attributeValue -> attributeValue.getName()) + .distinct() + .collect(Collectors.toSet()); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/Boot3NotSupportedTypeReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/Boot3NotSupportedTypeReconciler.java index ba694caa6..0be2221b7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/Boot3NotSupportedTypeReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/Boot3NotSupportedTypeReconciler.java @@ -22,7 +22,6 @@ import org.eclipse.jdt.core.dom.ImportDeclaration; import org.eclipse.jdt.core.dom.SimpleType; import org.springframework.ide.vscode.boot.java.Boot3JavaProblemType; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; @@ -45,14 +44,14 @@ public class Boot3NotSupportedTypeReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @Override public boolean visit(ImportDeclaration node) { String fqName = node.getName().getFullyQualifiedName(); if (TYPE_FQNAMES.contains(fqName)) { - problemCollector.accept(createProblem(fqName, node.getName().getStartPosition(), node.getName().getLength())); + context.getProblemCollector().accept(createProblem(fqName, node.getName().getStartPosition(), node.getName().getLength())); } return super.visit(node); } @@ -61,7 +60,7 @@ public class Boot3NotSupportedTypeReconciler implements JdtAstReconciler { public boolean visit(SimpleType node) { String fqName = processType(cu, node.getName().getFullyQualifiedName()); if (fqName != null) { - problemCollector.accept(createProblem(fqName, node.getStartPosition(), node.getLength())); + context.getProblemCollector().accept(createProblem(fqName, node.getStartPosition(), node.getLength())); } return super.visit(node); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java index 06d03e431..89fefe10e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/EntityIdForRepoReconciler.java @@ -44,7 +44,6 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchie import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.JavaUtils; -import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; @@ -70,7 +69,7 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, ReconcilingContext context) { AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu); return new ASTVisitor() { @@ -129,7 +128,7 @@ public class EntityIdForRepoReconciler implements JdtAstReconciler { } private void markProblem(ITypeBinding idType, ASTNode node) { - problemCollector.accept(new ReconcileProblemImpl(getProblemType(), + context.getProblemCollector().accept(new ReconcileProblemImpl(getProblemType(), "Expected Domain ID type is '" + idType.getQualifiedName() + "'", node.getStartPosition(), node.getLength())); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ImplicitWebAnnotationNamesReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ImplicitWebAnnotationNamesReconciler.java index f17a7ff39..cbcf7520d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ImplicitWebAnnotationNamesReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ImplicitWebAnnotationNamesReconciler.java @@ -34,7 +34,6 @@ import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; 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.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; @@ -74,7 +73,7 @@ public class ImplicitWebAnnotationNamesReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @@ -109,7 +108,7 @@ public class ImplicitWebAnnotationNamesReconciler implements JdtAstReconciler { ReconcileUtils.buildLabel(FIX_LABEL_PLURAL, RecipeScope.PROJECT)) .withRecipeScope(RecipeScope.PROJECT) )); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); } } }; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/JdtAstReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/JdtAstReconciler.java index e08d806fb..2b934cc27 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/JdtAstReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/JdtAstReconciler.java @@ -15,7 +15,6 @@ import java.net.URI; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; public interface JdtAstReconciler { @@ -32,6 +31,6 @@ public interface JdtAstReconciler { ProblemType getProblemType(); - ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete); + ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, ReconcilingContext context); } 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 279602656..69dd9d1c6 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022, 2023 VMware, Inc. + * Copyright (c) 2022, 2025 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 @@ -86,25 +86,28 @@ public class JdtReconciler implements JavaReconciler { final long s = System.currentTimeMillis(); URI uri = URI.create(doc.getUri()); compilationUnitCache.withCompilationUnit(project, uri, cu -> { + if (cu != null) { + try { - reconcile(project, URI.create(doc.getUri()), cu, problemCollector, true, true); + ReconcilingContext reconcilingContext = new ReconcilingContext(doc.getUri(), problemCollector, true, true, Collections.emptyList()); + reconcile(project, URI.create(doc.getUri()), cu, reconcilingContext); } catch (RequiredCompleteAstException e) { log.error("Unexpected incomplete AST", e); } // TODO: is the index indeed complete?!? do need to react to complete index exception instead ?!? } + log.info("reconciling (JDT): " + doc.getUri() + " done in " + (System.currentTimeMillis() - s) + "ms"); return null; }); } - public ASTVisitor createCompositeVisitor(IJavaProject project, URI docURI, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, - boolean isIndexComplete) throws RequiredCompleteAstException { + public ASTVisitor createCompositeVisitor(IJavaProject project, URI docURI, CompilationUnit cu, ReconcilingContext context) throws RequiredCompleteAstException { CompositeASTVisitor compositeVisitor = new CompositeASTVisitor(); for (JdtAstReconciler reconciler : getApplicableReconcilers(project)) { - ASTVisitor visitor = reconciler.createVisitor(project, docURI, cu, problemCollector, isCompleteAst, isIndexComplete); + ASTVisitor visitor = reconciler.createVisitor(project, docURI, cu, context); if (visitor != null) { compositeVisitor.add(visitor); @@ -115,7 +118,7 @@ public class JdtReconciler implements JavaReconciler { } - public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) + public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) throws RequiredCompleteAstException, RequiredCompleteIndexException { long start = System.currentTimeMillis(); @@ -125,7 +128,7 @@ public class JdtReconciler implements JavaReconciler { } try { - ASTVisitor compositeVisitor = createCompositeVisitor(project, docUri, cu, problemCollector, isCompleteAst, isIndexComplete); + ASTVisitor compositeVisitor = createCompositeVisitor(project, docUri, cu, context); cu.accept(compositeVisitor); // for (JdtAstReconciler reconciler : getApplicableReconcilers(project)) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ModulithTypeReferenceViolationReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ModulithTypeReferenceViolationReconciler.java index 59e564f3c..9ad0119e7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ModulithTypeReferenceViolationReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ModulithTypeReferenceViolationReconciler.java @@ -28,7 +28,6 @@ import org.springframework.ide.vscode.boot.modulith.AppModules; import org.springframework.ide.vscode.boot.modulith.ModulithService; import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; @@ -52,7 +51,7 @@ public class ModulithTypeReferenceViolationReconciler implements JdtAstReconcile } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { Path sourceFile = Paths.get(docUri); if (IClasspathUtil.getProjectJavaSourceFoldersWithoutTests(project.getClasspath()) @@ -81,7 +80,7 @@ public class ModulithTypeReferenceViolationReconciler implements JdtAstReconcile private void validate(ASTNode node, ITypeBinding type) { if (type != null) { appModules.getModuleNotExposingType(packageName, type.getBinaryName()).ifPresent(module -> { - problemCollector.accept(new ReconcileProblemImpl(getProblemType(), + context.getProblemCollector().accept(new ReconcileProblemImpl(getProblemType(), "Invalid reference to non-exposed type of module '%s'!".formatted(module.name()), node.getStartPosition(), node.getLength())); }); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoAutowiredOnConstructorReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoAutowiredOnConstructorReconciler.java index 51d1146c8..7fa2df6a6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoAutowiredOnConstructorReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoAutowiredOnConstructorReconciler.java @@ -29,7 +29,6 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchie import org.springframework.ide.vscode.commons.java.IClasspathUtil; 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.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; @@ -57,7 +56,7 @@ public class NoAutowiredOnConstructorReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu); return new ASTVisitor() { @@ -91,7 +90,7 @@ public class NoAutowiredOnConstructorReconciler implements JdtAstReconciler { List.of(new FixDescriptor(NoAutowiredOnConstructor.class.getName(), List.of(docUri.toASCIIString()), FIX_LABEL) .withRecipeScope(RecipeScope.NODE) .withRangeScope(ReconcileUtils.createOpenRewriteRange(cu, typeDecl, null)))); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRepoAnnotationReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRepoAnnotationReconciler.java index 136308e26..a9e602fc1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRepoAnnotationReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRepoAnnotationReconciler.java @@ -27,7 +27,6 @@ import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; 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.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; @@ -55,7 +54,7 @@ public class NoRepoAnnotationReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @@ -82,7 +81,7 @@ public class NoRepoAnnotationReconciler implements JdtAstReconciler { ReconcileUtils.buildLabel(FIX_LABEL, RecipeScope.PROJECT)) .withRecipeScope(RecipeScope.PROJECT) )); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRequestMappingAnnotationReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRequestMappingAnnotationReconciler.java index b4be10618..a79d019ef 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRequestMappingAnnotationReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRequestMappingAnnotationReconciler.java @@ -37,7 +37,6 @@ import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; 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.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; @@ -69,7 +68,7 @@ public class NoRequestMappingAnnotationReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @@ -108,12 +107,12 @@ public class NoRequestMappingAnnotationReconciler implements JdtAstReconciler { "Replace all `@RequestMapping` in project '%s' with `@GetMapping`, `@PostMapping`, etc.".formatted(project.getElementName())) .withRecipeScope(RecipeScope.PROJECT) )); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); } else if (SUPPORTED_REQUEST_METHODS == requestMethods) { // the case of no request methods specified ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL, a.getStartPosition(), a.getLength()); ReconcileUtils.setRewriteFixes(registry, problem, requestMethods.stream().map(m -> createFixDescriptor(uri, range, m)).collect(Collectors.toList())); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NotRegisteredBeansReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NotRegisteredBeansReconciler.java index 413888c98..4d60ccaee 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NotRegisteredBeansReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NotRegisteredBeansReconciler.java @@ -71,7 +71,7 @@ public class NotRegisteredBeansReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @@ -86,7 +86,7 @@ public class NotRegisteredBeansReconciler implements JdtAstReconciler { Bean[] registeredBeans = springIndex.getBeansWithType(project.getElementName(), beanClassName); if (registeredBeans == null || registeredBeans.length == 0) { - createProblemAndQuickFixes(project, problemCollector, node, type); + createProblemAndQuickFixes(project, context.getProblemCollector(), node, type); } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/PreciseBeanTypeReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/PreciseBeanTypeReconciler.java index f2fb5b00b..349ef466d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/PreciseBeanTypeReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/PreciseBeanTypeReconciler.java @@ -30,7 +30,6 @@ import org.springframework.ide.vscode.boot.java.SpringAotJavaProblemType; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; 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.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; @@ -57,7 +56,7 @@ public class PreciseBeanTypeReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { final AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu); return new ASTVisitor() { @@ -72,7 +71,7 @@ public class PreciseBeanTypeReconciler implements JdtAstReconciler { if (methodBinding != null) { boolean isBeanMethod = annotationHierarchies.isAnnotatedWith(methodBinding, Annotations.BEAN); if (isBeanMethod) { - if (isCompleteAst) { + if (context.isCompleteAst()) { if (currentMethod == null) {// Do not jump into anonymous class methods currentMethod = method; currentReturnTypes = new ArrayList<>(); @@ -91,7 +90,7 @@ public class PreciseBeanTypeReconciler implements JdtAstReconciler { if (currentMethod == method) { ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), LABEL, method.getReturnType2().getStartPosition(), method.getReturnType2().getLength()); if (currentReturnTypes.size() > 1) { - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); } else if (currentReturnTypes.size() == 1 && !method.resolveBinding().getReturnType().isAssignmentCompatible(currentReturnTypes.get(0))) { String uri = docUri.toASCIIString(); String replacementType = currentReturnTypes.get(0).getName(); @@ -105,7 +104,7 @@ public class PreciseBeanTypeReconciler implements JdtAstReconciler { new FixDescriptor(recipeId, List.of(uri), ReconcileUtils.buildLabel(LABEL, RecipeScope.PROJECT)) .withRecipeScope(RecipeScope.PROJECT) )); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); } currentMethod = null; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ReconcilingContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ReconcilingContext.java new file mode 100644 index 000000000..b7d9df695 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/ReconcilingContext.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom + * 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: + * Broadcom - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.reconcilers; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; +import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; + +/** + * context information for reconcilers + */ +public class ReconcilingContext { + + private final String docURI; + + private final IProblemCollector problemCollector; + private final boolean isCompleteAst; + private final boolean isIndexComplete; + + private final Set dependencies; // set of fully qualified types + private final Set markedForAffectedFilesIndexing; // set of files + + private final List createdIndexElements; + + public ReconcilingContext(String docURI, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete, List createdIndexElements) { + this.docURI = docURI; + this.problemCollector = problemCollector; + this.isCompleteAst = isCompleteAst; + this.isIndexComplete = isIndexComplete; + + this.dependencies = new HashSet<>(); + this.markedForAffectedFilesIndexing = new HashSet<>(); + + this.createdIndexElements = createdIndexElements; + } + + public String getDocURI() { + return docURI; + } + + public IProblemCollector getProblemCollector() { + return problemCollector; + } + + public boolean isCompleteAst() { + return isCompleteAst; + } + + public boolean isIndexComplete() { + return isIndexComplete; + } + + public void addDependency(String typeOfConfigClassWithImport) { + this.dependencies.add(typeOfConfigClassWithImport); + } + + public Set getDependencies() { + return dependencies; + } + + public void markForAffetcedFilesIndexing(String file) { + this.markedForAffectedFilesIndexing.add(file); + } + + public Set getMarkedForAffectedFilesIndexing() { + return markedForAffectedFilesIndexing; + } + + public List getCreatedIndexElements() { + return createdIndexElements; + } + +} 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 index 7e7cb6302..905f7a9fa 100644 --- 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 @@ -27,7 +27,6 @@ import org.openrewrite.java.spring.boot2.UnnecessarySpringExtension; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; 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.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; @@ -74,7 +73,7 @@ public class UnnecessarySpringExtensionReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @Override @@ -95,7 +94,7 @@ public class UnnecessarySpringExtensionReconciler implements JdtAstReconciler { ReconcileUtils.setRewriteFixes(registry, problem, List.of( new FixDescriptor(UnnecessarySpringExtension.class.getName(), List.of(docUri.toASCIIString()), ReconcileUtils.buildLabel(LABEL, RecipeScope.PROJECT)) )); - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); break; } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/WebSecurityConfigurerAdapterReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/WebSecurityConfigurerAdapterReconciler.java index 399640088..9e4435941 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/WebSecurityConfigurerAdapterReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/WebSecurityConfigurerAdapterReconciler.java @@ -28,7 +28,6 @@ import org.springframework.ide.vscode.commons.Version; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; 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.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; @@ -85,7 +84,7 @@ public class WebSecurityConfigurerAdapterReconciler implements JdtAstReconciler } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) { AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(cu); return new ASTVisitor() { @@ -111,7 +110,7 @@ public class WebSecurityConfigurerAdapterReconciler implements JdtAstReconciler ); } - problemCollector.accept(problem); + context.getProblemCollector().accept(problem); } return super.visit(typeDecl); } 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 index 2cad4b467..0262ae7fc 100644 --- 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 @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.app.BootJavaConfig; import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler; import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.ReconcilingContext; 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.Quickfix.QuickfixData; @@ -105,14 +106,20 @@ public class RewriteCodeActionHandler implements JavaCodeActionHandler { URI uri = URI.create(doc.getUri()); final QuickfixType rewriteFixType = quickfixRegistry.getQuickfixType(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX); + if (isSupported(capabilities, context) && rewriteFixType != null) { List codeActions = cuCache.withCompilationUnit(project, uri, cu -> { + if (cu != null) { + try { List cas = new ArrayList<>(); List problems = new ArrayList<>(); BasicProblemCollector problemsCollector = new BasicProblemCollector(problems); - jdtReconciler.reconcile(project, uri, cu, problemsCollector, true, true); + + ReconcilingContext reconcilingContext = new ReconcilingContext(doc.getUri(), problemsCollector, true, true, Collections.emptyList()); + jdtReconciler.reconcile(project, uri, cu, reconcilingContext); + for (ReconcileProblem p : problems) { if (p.getOffset() <= region.getOffset() && p.getOffset() + p.getLength() >= region.getOffset() + region.getLength() && severityProvider.getDiagnosticSeverity(p) == null) { for (QuickfixData qf : p.getQuickfixes()) { @@ -123,6 +130,7 @@ public class RewriteCodeActionHandler implements JavaCodeActionHandler { } } return cas; + } catch (Exception e) { log.error("", e); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/spel/JdtSpelReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/spel/JdtSpelReconciler.java index 7134c440e..31bc97de9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/spel/JdtSpelReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/spel/JdtSpelReconciler.java @@ -19,9 +19,9 @@ import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.springframework.ide.vscode.boot.java.SpelProblemType; import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.ReconcilingContext; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; -import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; public class JdtSpelReconciler implements JdtAstReconciler { @@ -44,8 +44,7 @@ public class JdtSpelReconciler implements JdtAstReconciler { } @Override - public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, - IProblemCollector problemCollector, boolean isCompleteAst, boolean isIndexComplete) { + public ASTVisitor createVisitor(IJavaProject project, URI docURI, CompilationUnit cu, ReconcilingContext context) { return new ASTVisitor() { @Override public boolean visit(SingleMemberAnnotation node) { @@ -53,7 +52,7 @@ public class JdtSpelReconciler implements JdtAstReconciler { .map(e -> e.getSpelRegion(node)) .filter(o -> o.isPresent()) .map(o -> o.get()) - .forEach(snippet -> spelReconciler.reconcile(snippet.getText(), snippet::toSingleJavaRange, problemCollector)); + .forEach(snippet -> spelReconciler.reconcile(snippet.getText(), snippet::toSingleJavaRange, context.getProblemCollector())); return super.visit(node); } @@ -63,7 +62,7 @@ public class JdtSpelReconciler implements JdtAstReconciler { .map(e -> e.getSpelRegion(node)) .filter(o -> o.isPresent()) .map(o -> o.get()) - .forEach(snippet -> spelReconciler.reconcile(snippet.getText(), snippet::toSingleJavaRange, problemCollector)); + .forEach(snippet -> spelReconciler.reconcile(snippet.getText(), snippet::toSingleJavaRange, context.getProblemCollector())); return super.visit(node); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java index ee4723605..dafc5c4a1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java @@ -63,6 +63,7 @@ import org.springframework.ide.vscode.boot.java.beans.CachedBean; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.reconcilers.CachedDiagnostics; import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.ReconcilingContext; import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException; import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteIndexException; import org.springframework.ide.vscode.commons.java.IClasspath; @@ -356,8 +357,8 @@ public class SpringIndexerJava implements SpringIndexer { scannedFiles.add(file); } - Set scannedTypes = scanFilesInternally(project, docs); - scanAffectedFiles(project, scannedTypes, scannedFiles); + ScanFilesInternallyResult result = scanFilesInternally(project, docs); + scanAffectedFiles(project, result.scannedTypes, scannedFiles, result.scanResult.getMarkedForAffectedFilesIndexing()); } private void scanFile(IJavaProject project, DocumentDescriptor updatedDoc, String content) throws Exception { @@ -417,11 +418,11 @@ public class SpringIndexerJava implements SpringIndexer { reconcileWithCompleteIndex(project, result.getMarkedForReconcilingWithCompleteIndex()); - scanAffectedFiles(project, context.getScannedTypes(), scannedFiles); + scanAffectedFiles(project, context.getScannedTypes(), scannedFiles, result.getMarkedForAffectedFilesIndexing()); } } - private Set scanFilesInternally(IJavaProject project, DocumentDescriptor[] docs) throws Exception { + private ScanFilesInternallyResult scanFilesInternally(IJavaProject project, DocumentDescriptor[] docs) throws Exception { final boolean ignoreMethodBodies = false; // this is to keep track of already scanned files to avoid endless loops due to circular dependencies @@ -498,14 +499,18 @@ public class SpringIndexerJava implements SpringIndexer { result.publishResults(symbolHandler); reconcileWithCompleteIndex(project, result.getMarkedForReconcilingWithCompleteIndex()); - return scannedTypes; + return new ScanFilesInternallyResult(scannedTypes, result); } + + private static record ScanFilesInternallyResult(Set scannedTypes, SpringIndexerJavaScanResult scanResult) {}; - private void scanAffectedFiles(IJavaProject project, Set changedTypes, Set alreadyScannedFiles) throws Exception { + + private void scanAffectedFiles(IJavaProject project, Set changedTypes, Set alreadyScannedFiles, Set alreadyMarkedForAffectedFilesIndexing) throws Exception { log.info("Start scanning affected files for types {}", changedTypes); Multimap dependencies = dependencyTracker.getAllDependencies(); Set filesToScan = new HashSet<>(); + filesToScan.addAll(alreadyMarkedForAffectedFilesIndexing); for (String file : dependencies.keys()) { if (!alreadyScannedFiles.contains(file)) { @@ -646,6 +651,9 @@ public class SpringIndexerJava implements SpringIndexer { boolean ignoreMethodBodies = false; String[] javaFiles = markedForReconcilingWithCompleteIndex.keySet().toArray(String[]::new); + + log.info("additional reconciling with complete index triggere for: " + Arrays.toString(javaFiles)); + long[] modificationTimestamps = new long[javaFiles.length]; for (int i = 0; i < javaFiles.length; i++) { modificationTimestamps[i] = markedForReconcilingWithCompleteIndex.get(javaFiles[i]); @@ -676,7 +684,7 @@ public class SpringIndexerJava implements SpringIndexer { lastModified, docRef, null, problemCollector, new ArrayList<>(), !ignoreMethodBodies, true, reconcilingResult); try { - DocumentUtils.getTempTextDocument(docURI, docRef, null); + DocumentUtils.getTempTextDocument(docURI, docRef, null); // initialize the docRef with a real document before running validations reconcile(context); } catch (Exception e) { log.error("problem creating temp document during re-reconciling for: " + docURI, e); @@ -826,7 +834,22 @@ public class SpringIndexerJava implements SpringIndexer { try { problemCollector.beginCollecting(); - reconciler.reconcile(context.getProject(), URI.create(context.getDocURI()), context.getCu(), problemCollector, context.isFullAst(), context.isIndexComplete()); + + List createdElements = context.getBeans().stream() + .filter(cachedBean -> cachedBean.getDocURI().equals(context.getDocURI())) + .map(cachedBean -> cachedBean.getBean()) + .toList(); + + ReconcilingContext reconcilingContext = new ReconcilingContext(context.getDocURI(), problemCollector, context.isFullAst(), context.isIndexComplete(), createdElements); + + reconciler.reconcile(context.getProject(), URI.create(context.getDocURI()), context.getCu(), reconcilingContext); + + for (String dependency : reconcilingContext.getDependencies()) { + context.addDependency(dependency); + } + + context.getResult().markForAffectedFilesIndexing(reconcilingContext.getMarkedForAffectedFilesIndexing()); + problemCollector.endCollecting(); } catch (RequiredCompleteAstException e) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java index 9aa7f4383..488e8197b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java @@ -123,13 +123,19 @@ public class SpringIndexerJavaContext { public void addDependency(ITypeBinding dependsOn) { if (dependsOn != null && dependsOn.isFromSource()) { - String type = dependsOn.getKey(); + String type = dependsOn.getQualifiedName(); if (type != null && !scannedTypes.contains(type)) { dependencies.add(type); } } } + + public void addDependency(String qualifiedTypeName) { + if (qualifiedTypeName != null) { + dependencies.add(qualifiedTypeName); + } + } public Set getScannedTypes() { return scannedTypes; @@ -137,7 +143,7 @@ public class SpringIndexerJavaContext { public void addScannedType(ITypeBinding scannedType) { if (scannedType != null) { - String type = scannedType.getKey(); + String type = scannedType.getQualifiedName(); scannedTypes.add(type); dependencies.remove(type); } @@ -171,5 +177,5 @@ public class SpringIndexerJavaContext { } } - + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaScanResult.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaScanResult.java index 4a01ea64e..0a91bcf1f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaScanResult.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaScanResult.java @@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.utils; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -37,7 +38,8 @@ import org.springframework.ide.vscode.commons.util.UriUtil; */ public class SpringIndexerJavaScanResult { - private final Map markedForReconciling; + private final Map markedForReconciling; // file + modification timestamp + private final Set markedForAffetcedFilesIndexing; // file private final List generatedSymbols; private final List generatedBeans; @@ -45,12 +47,15 @@ public class SpringIndexerJavaScanResult { private final IJavaProject project; private final String[] javaFiles; + public SpringIndexerJavaScanResult(IJavaProject project, String[] javaFiles) { this.project = project; this.javaFiles = javaFiles; this.markedForReconciling = new HashMap<>(); + this.markedForAffetcedFilesIndexing = new HashSet<>(); + this.generatedSymbols = new ArrayList(); this.generatedBeans = new ArrayList(); this.generatedDiagnostics = new ArrayList(); @@ -63,10 +68,13 @@ public class SpringIndexerJavaScanResult { this.javaFiles = javaFiles; this.markedForReconciling = new HashMap<>(); + this.markedForAffetcedFilesIndexing = new HashSet<>(); + this.generatedSymbols = Arrays.asList(symbols); this.generatedBeans = Arrays.asList(beans); this.generatedDiagnostics = Arrays.asList(diagnostics); } + public Map getMarkedForReconcilingWithCompleteIndex() { return markedForReconciling; @@ -76,6 +84,15 @@ public class SpringIndexerJavaScanResult { this.markedForReconciling.put(file, lastModified); } + public void markForAffectedFilesIndexing(Collection markedForAffectedFilesIndexing) { + this.markedForAffetcedFilesIndexing.addAll(markedForAffectedFilesIndexing); + } + + public Set getMarkedForAffectedFilesIndexing() { + return this.markedForAffetcedFilesIndexing; + } + + public List getGeneratedBeans() { return generatedBeans; } @@ -108,5 +125,5 @@ public class SpringIndexerJavaScanResult { } } } - + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BaseReconcilerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BaseReconcilerTest.java index 634a32344..1c298096f 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BaseReconcilerTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BaseReconcilerTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 VMware, Inc. + * Copyright (c) 2023, 2025 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 @@ -19,6 +19,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -31,6 +32,7 @@ import org.eclipse.jdt.core.dom.FileASTRequestor; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; import org.springframework.ide.vscode.boot.java.reconcilers.CompositeASTVisitor; import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler; +import org.springframework.ide.vscode.boot.java.reconcilers.ReconcilingContext; import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava; import org.springframework.ide.vscode.boot.java.value.test.ValueSpelExpressionValidationTest.TestProblemCollector; @@ -106,7 +108,10 @@ public abstract class BaseReconcilerTest { public void acceptAST(String sourceFilePath, CompilationUnit cu) { try { JdtAstReconciler reconciler = reconcilerFactory.get(); - ASTVisitor visitor = reconciler.createVisitor(project, path.toUri(), cu, problemCollector, isCompleteAst, isIndexComplete); + String docURI = path.toUri().toASCIIString(); + + ReconcilingContext reconcilingContext = new ReconcilingContext(docURI, problemCollector, isCompleteAst, isIndexComplete, Collections.emptyList()); + ASTVisitor visitor = reconciler.createVisitor(project, path.toUri(), cu, reconcilingContext); if (visitor != null) { // use a composite visitor here to make sure that the tests will fail if there is anything missing in the composite diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BeanRegistrarAdvancedReconcilingTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BeanRegistrarAdvancedReconcilingTest.java new file mode 100644 index 000000000..b2ad2c5de --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BeanRegistrarAdvancedReconcilingTest.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom + * 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: + * Broadcom - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.reconcilers.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.FileUtils; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.PublishDiagnosticsParams; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.WorkspaceSymbol; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; +import org.springframework.ide.vscode.boot.java.Boot4JavaProblemType; +import org.springframework.ide.vscode.boot.java.utils.test.TestFileScanListener; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.util.UriUtil; +import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Martin Lippert + */ +@ExtendWith(SpringExtension.class) +@BootLanguageServerTest +@Import(SymbolProviderTestConf.class) +public class BeanRegistrarAdvancedReconcilingTest { + + @Autowired private BootLanguageServerHarness harness; + @Autowired private JavaProjectFinder projectFinder; + @Autowired private SpringSymbolIndex indexer; + @Autowired private SpringMetamodelIndex springIndex; + + private File directory; + + @BeforeEach + public void setup() throws Exception { + harness.intialize(null); + + directory = new File(ProjectsHarness.class.getResource("/test-projects/test-framework-7-indexing/").toURI()); + + String projectDir = directory.toURI().toString(); + + // trigger project creation + projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); + + CompletableFuture initProject = indexer.waitOperation(); + initProject.get(5, TimeUnit.SECONDS); + } + + @Test + void testNoErrorOnRegisteredRegistrar() throws Exception { + String docUri = directory.toPath().resolve("src/main/java/com/example/MyBeanRegistrar.java").toUri().toString(); + PublishDiagnosticsParams diagnosticsResult = harness.getDiagnostics(docUri); + List diagnostics = diagnosticsResult.getDiagnostics(); + + assertEquals(0, diagnostics.size()); + } + + @Test + void testErrorOnNonRegisteredRegistrar() throws Exception { + String docUri = directory.toPath().resolve("src/main/java/com/example/NotRegisteredBeanRegistrar.java").toUri().toString(); + PublishDiagnosticsParams diagnosticsResult = harness.getDiagnostics(docUri); + List diagnostics = diagnosticsResult.getDiagnostics(); + + assertEquals(1, diagnostics.size()); + assertEquals(Boot4JavaProblemType.REGISTRAR_BEAN_DECLARATION.getCode(), diagnostics.get(0).getCode().getLeft()); + } + + @Test + void testBeanRegistrarGetsReconciledWhenConfigChangesAddImportCase() throws Exception { + String configDoc = directory.toPath().resolve("src/main/java/com/example/ConfigImportsBeanRegistrar.java").toUri().toString(); + String registeredRegistrarDocUri = directory.toPath().resolve("src/main/java/com/example/MyBeanRegistrar.java").toUri().toString(); + String nonRegisteredRegistrarDocUri = directory.toPath().resolve("src/main/java/com/example/NotRegisteredBeanRegistrar.java").toUri().toString(); + + // symbol pre-check + List symbols = indexer.getWorkspaceSymbolsFromSymbolIndex(registeredRegistrarDocUri); + assertEquals(5, symbols.size()); + + // now change the config class source code and update doc + TestFileScanListener fileScanListener = new TestFileScanListener(); + indexer.getJavaIndexer().setFileScanListener(fileScanListener); + + String configClassSource = FileUtils.readFileToString(UriUtil.toFile(configDoc), Charset.defaultCharset()); + String updatedConfigClassSource = configClassSource.replace("@Import(MyBeanRegistrar.class)", "@Import({MyBeanRegistrar.class, NotRegisteredBeanRegistrar.class})"); + + CompletableFuture updateFuture = indexer.updateDocument(configDoc, updatedConfigClassSource, "test triggered"); + updateFuture.get(5, TimeUnit.SECONDS); + + // check if the bean registrar files have been re-scanned + fileScanListener.assertScannedUri(configDoc, 1); + fileScanListener.assertScannedUri(registeredRegistrarDocUri, 1); + fileScanListener.assertScannedUri(nonRegisteredRegistrarDocUri, 1); + fileScanListener.assertNoFileScans(3); + + // check diagnostics result + PublishDiagnosticsParams diagnosticsResultForRegisteredRegistrar = harness.getDiagnostics(registeredRegistrarDocUri); + List diagnosticsForAlreadyRegisteredRegistrar = diagnosticsResultForRegisteredRegistrar.getDiagnostics(); + assertEquals(0, diagnosticsForAlreadyRegisteredRegistrar.size()); + + PublishDiagnosticsParams diagnosticsResultForNewlyRegisteredRegistrar = harness.getDiagnostics(nonRegisteredRegistrarDocUri); + List diagnosticsForPreviouslyNotRegisteredRegistrar = diagnosticsResultForNewlyRegisteredRegistrar.getDiagnostics(); + assertEquals(0, diagnosticsForPreviouslyNotRegisteredRegistrar.size()); + + // check if the symbols are still in place correctly + List symbolsAfterUpdate = indexer.getWorkspaceSymbolsFromSymbolIndex(registeredRegistrarDocUri); + assertEquals(5, symbolsAfterUpdate.size()); + } + + @Test + void testBeanRegistrarGetsReconciledWhenConfigChangesRemoveImportCase() throws Exception { + String configDoc = directory.toPath().resolve("src/main/java/com/example/ConfigImportsBeanRegistrar.java").toUri().toString(); + String registeredRegistrarDocUri = directory.toPath().resolve("src/main/java/com/example/MyBeanRegistrar.java").toUri().toString(); + + TestFileScanListener fileScanListener = new TestFileScanListener(); + indexer.getJavaIndexer().setFileScanListener(fileScanListener); + + String configClassSource = FileUtils.readFileToString(UriUtil.toFile(configDoc), Charset.defaultCharset()); + String updatedConfigClassSource = configClassSource.replace("@Import(MyBeanRegistrar.class)", ""); + + CompletableFuture updateFuture = indexer.updateDocument(configDoc, updatedConfigClassSource, "test triggered"); + updateFuture.get(5, TimeUnit.SECONDS); + + fileScanListener.assertScannedUri(configDoc, 1); + fileScanListener.assertScannedUri(registeredRegistrarDocUri, 1); + fileScanListener.assertNoFileScans(2); + + PublishDiagnosticsParams diagnosticsResultForPreviouslyRegisteredRegistrar = harness.getDiagnostics(registeredRegistrarDocUri); + List diagnosticsForPreviouslyRegisteredRegistrar = diagnosticsResultForPreviouslyRegisteredRegistrar.getDiagnostics(); + assertEquals(1, diagnosticsForPreviouslyRegisteredRegistrar.size()); + assertEquals(Boot4JavaProblemType.REGISTRAR_BEAN_DECLARATION.getCode(), diagnosticsForPreviouslyRegisteredRegistrar.get(0).getCode().getLeft()); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BeanRegistrarDeclarationReconcilerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BeanRegistrarDeclarationReconcilerTest.java index 4cd02f125..26166bb34 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BeanRegistrarDeclarationReconcilerTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/BeanRegistrarDeclarationReconcilerTest.java @@ -32,7 +32,7 @@ import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata import org.springframework.ide.vscode.commons.protocol.spring.Bean; public class BeanRegistrarDeclarationReconcilerTest extends BaseReconcilerTest { - + @Override protected String getFolder() { return "sf7validation"; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java index 8ae8aad7d..849688064 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java @@ -109,8 +109,9 @@ public class RequestMappingSymbolProviderTest { } @Test - //TODO: Enable when JDT Core 3.41 or higher is adopted. See: https://github.com/eclipse-jdt/eclipse.jdt.core/pull/3416 - @DisabledOnOs(OS.WINDOWS) + // TODO: Enable when JDT Core 3.41 or higher is adopted. See: https://github.com/eclipse-jdt/eclipse.jdt.core/pull/3416 + // additional comment: switched the string to not contain the "key" of the AST node, but the fully qualified type name, therefore enabling this again everywhere +// @DisabledOnOs(OS.WINDOWS) void testSimpleRequestMappingSymbolFromConstantInDifferentClass() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString(); String constantsUri = directory.toPath().resolve("src/main/java/org/test/Constants.java").toUri().toString(); @@ -120,7 +121,7 @@ public class RequestMappingSymbolProviderTest { //Verify whether dependency tracker logics works properly for this example. SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker(); - assertEquals(ImmutableSet.of("Lorg/test/Constants;"), dt.getAllDependencies().get(UriUtil.toFileString(docUri))); + assertEquals(ImmutableSet.of("org.test.Constants"), dt.getAllDependencies().get(UriUtil.toFileString(docUri))); TestFileScanListener fileScanListener = new TestFileScanListener(); indexer.getJavaIndexer().setFileScanListener(fileScanListener); @@ -135,7 +136,8 @@ public class RequestMappingSymbolProviderTest { @Test //TODO: Enable when JDT Core 3.41 or higher is adopted. See: https://github.com/eclipse-jdt/eclipse.jdt.core/pull/3416 - @DisabledOnOs(OS.WINDOWS) + // additional comment: switched the string to not contain the "key" of the AST node, but the fully qualified type name, therefore enabling this again everywhere +// @DisabledOnOs(OS.WINDOWS) void testUpdateDocumentWithConstantFromDifferentClass() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString(); String constantsUri = directory.toPath().resolve("src/main/java/org/test/Constants.java").toUri().toString(); @@ -145,7 +147,7 @@ public class RequestMappingSymbolProviderTest { //Verify whether dependency tracker logics works properly for this example. SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker(); - assertEquals(ImmutableSet.of("Lorg/test/Constants;"), dt.getAllDependencies().get(UriUtil.toFileString(docUri))); + assertEquals(ImmutableSet.of("org.test.Constants"), dt.getAllDependencies().get(UriUtil.toFileString(docUri))); TestFileScanListener fileScanListener = new TestFileScanListener(); indexer.getJavaIndexer().setFileScanListener(fileScanListener); @@ -153,7 +155,7 @@ public class RequestMappingSymbolProviderTest { CompletableFuture updateFuture = indexer.updateDocument(docUri, FileUtils.readFileToString(UriUtil.toFile(docUri), Charset.defaultCharset()), "test triggered"); updateFuture.get(5, TimeUnit.SECONDS); - assertEquals(ImmutableSet.of("Lorg/test/Constants;"), dt.getAllDependencies().get(UriUtil.toFileString(docUri))); + assertEquals(ImmutableSet.of("org.test.Constants"), dt.getAllDependencies().get(UriUtil.toFileString(docUri))); fileScanListener.assertScannedUris(docUri); fileScanListener.assertScannedUri(constantsUri, 0); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/TestFileScanListener.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/TestFileScanListener.java index 9a665fef7..c9be6803c 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/TestFileScanListener.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/TestFileScanListener.java @@ -65,4 +65,8 @@ public class TestFileScanListener implements FileScanListener { public void reset() { scannedFiles.clear(); } + + public void assertNoFileScans(int count) { + assertEquals(count, scannedFiles.size()); + } } \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-framework-7-indexing/src/main/java/com/example/NotRegisterredBeanRegistrar.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-framework-7-indexing/src/main/java/com/example/NotRegisteredBeanRegistrar.java similarity index 82% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-framework-7-indexing/src/main/java/com/example/NotRegisterredBeanRegistrar.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-framework-7-indexing/src/main/java/com/example/NotRegisteredBeanRegistrar.java index 26013e448..37ad291e0 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-framework-7-indexing/src/main/java/com/example/NotRegisterredBeanRegistrar.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-framework-7-indexing/src/main/java/com/example/NotRegisteredBeanRegistrar.java @@ -4,7 +4,7 @@ import org.springframework.beans.factory.BeanRegistrar; import org.springframework.beans.factory.BeanRegistry; import org.springframework.core.env.Environment; -public class NotRegisterredBeanRegistrar implements BeanRegistrar { +public class NotRegisteredBeanRegistrar implements BeanRegistrar { @Override public void register(BeanRegistry registry, Environment env) {