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 7b6c1a08f..4df105749 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 @@ -37,12 +37,15 @@ public class SpringMetamodelIndex { ProjectElement project = this.projectRootElements.computeIfAbsent(projectName, name -> new ProjectElement(name)); project.removeDocument(docURI); - DocumentElement document = new DocumentElement(docURI); - for (SpringIndexElement bean : elements) { - document.addChild(bean); - } + if (elements != null && elements.length > 0) { + DocumentElement document = new DocumentElement(docURI); + for (SpringIndexElement bean : elements) { + document.addChild(bean); + } + + project.addChild(document); + } - project.addChild(document); } public void removeElements(String projectName, String docURI) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java index bc0f1352c..597e9fd29 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java @@ -13,7 +13,6 @@ package org.springframework.ide.vscode.boot.java.beans; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -32,13 +31,10 @@ import org.eclipse.lsp4j.jsonrpc.messages.Tuple; import org.eclipse.lsp4j.jsonrpc.messages.Tuple.Two; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; -import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; -import org.springframework.ide.vscode.commons.protocol.spring.AnnotationAttributeValue; import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata; import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; @@ -60,8 +56,6 @@ public class FeignClientSymbolProvider implements SymbolProvider { context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition)); - - markNewFeignConfigTypeForReconciling(beanDefinition, context); } } catch (BadLocationException e) { @@ -69,19 +63,6 @@ public class FeignClientSymbolProvider implements SymbolProvider { } } - private void markNewFeignConfigTypeForReconciling(Bean beanDefinition, SpringIndexerJavaContext context) { - List configurationTypes = Arrays.stream(beanDefinition.getAnnotations()) - .filter(annotation -> annotation.getAnnotationType().equals(Annotations.FEIGN_CLIENT)) - .map(annotation -> annotation.getAttributes()) - .filter(attributes -> attributes.containsKey("configuration")) - .map(attributes -> attributes.get("configuration")) - .flatMap(attributeValues -> Arrays.stream(attributeValues)) - .map(attributeValue -> attributeValue.getName()) - .toList(); - - - } - private Two createSymbol(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) throws BadLocationException { String annotationTypeName = annotationType.getName(); Collection metaAnnotationNames = metaAnnotations.stream() 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 28e992b2c..0d9f55928 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 @@ -712,8 +712,8 @@ public class SpringIndexerJava implements SpringIndexer { IndexCacheKey diagnosticsCacheKey = getCacheKey(project, DIAGNOSTICS_KEY); this.cache.update(diagnosticsCacheKey, javaFiles, modificationTimestamps, reconcilingResult.getGeneratedDiagnostics(), dependencyTracker.getAllDependencies(), CachedDiagnostics.class); - // publish - reconcilingResult.publishResults(symbolHandler); + // publish diagnostics + reconcilingResult.publishDiagnosticsOnly(symbolHandler); } private void scanAST(final SpringIndexerJavaContext context, boolean includeReconcile) { 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 0a91bcf1f..f5eaa1f0b 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 @@ -110,11 +110,30 @@ public class SpringIndexerJavaScanResult { Map> allBeans = generatedBeans.stream().filter(cachedBean -> cachedBean.getBean() != null).collect(Collectors.groupingBy(CachedBean::getDocURI, Collectors.mapping(CachedBean::getBean, Collectors.toList()))); Map> diagnosticsByDoc = generatedDiagnostics.stream().filter(cachedDiagnostic -> cachedDiagnostic.getDiagnostic() != null).collect(Collectors.groupingBy(CachedDiagnostics::getDocURI, Collectors.mapping(CachedDiagnostics::getDiagnostic, Collectors.toList()))); - addEmptyDiagnostics(diagnosticsByDoc, javaFiles); // to make sure that files without diagnostics publish an empty array of diagnostics + // to make sure that files without index elements or diagnostics publish an empty array of diagnostics + addEmptyDiagnostics(diagnosticsByDoc, javaFiles); + addEmptyIndexElements(allBeans, javaFiles); symbolHandler.addSymbols(this.project, enhancedSymbols, allBeans, diagnosticsByDoc); } + public void publishDiagnosticsOnly(SymbolHandler symbolHandler) { + Map> diagnosticsByDoc = generatedDiagnostics.stream().filter(cachedDiagnostic -> cachedDiagnostic.getDiagnostic() != null).collect(Collectors.groupingBy(CachedDiagnostics::getDocURI, Collectors.mapping(CachedDiagnostics::getDiagnostic, Collectors.toList()))); + addEmptyDiagnostics(diagnosticsByDoc, javaFiles); // to make sure that files without index elements or diagnostics publish an empty array of diagnostics + symbolHandler.addSymbols(this.project, null, null, diagnosticsByDoc); + } + + private void addEmptyIndexElements(Map> allBeans, String[] javaFiles) { + for (int i = 0; i < javaFiles.length; i++) { + File file = new File(javaFiles[i]); + String docURI = UriUtil.toUri(file).toASCIIString(); + + if (!allBeans.containsKey(docURI)) { + allBeans.put(docURI, Collections.emptyList()); + } + } + } + private void addEmptyDiagnostics(Map> diagnosticsByDoc, String[] javaFiles) { for (int i = 0; i < javaFiles.length; i++) { File file = new File(javaFiles[i]); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java index 4d27ed661..5736557c8 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java @@ -11,7 +11,6 @@ package org.springframework.ide.vscode.boot.index.test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import java.io.File; import java.net.URI; @@ -46,7 +45,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; @Import(SymbolProviderTestConf.class) public class SpringMetamodelIndexingTest { - public static final int NO_OF_EXPECTED_BEANS = 28; + public static final int NO_OF_EXPECTED_BEANS = 29; @Autowired private BootLanguageServerHarness harness; @Autowired private JavaProjectFinder projectFinder; @@ -142,6 +141,22 @@ public class SpringMetamodelIndexingTest { assertEquals(2, harness.getIndexUpdatedCount()); // 1x project created, 1x document updated } + @Test + void testUpdatedDocumentHasNoIndexElementsAnymore() throws Exception { + String changedDocURI = directory.toPath().resolve("src/main/java/org/test/SimpleComponentClass.java").toUri().toString(); + + Bean[] beans = springIndex.getBeansOfDocument(changedDocURI); + assertEquals(1, beans.length); + assertEquals("simpleComponentClass", beans[0].getName()); + + String newContent = FileUtils.readFileToString(new File(new URI(changedDocURI)), Charset.defaultCharset()).replace("@Component", ""); + CompletableFuture updateFuture = indexer.updateDocument(changedDocURI, newContent, "test triggered"); + updateFuture.get(5, TimeUnit.SECONDS); + + Bean[] updatedBeans = springIndex.getBeansOfDocument(changedDocURI); + assertEquals(0, updatedBeans.length); + } + @Test void testNewDocumentCreated() throws Exception { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentAdvancedReconcilingTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentAdvancedReconcilingTest.java index 7ef3f157d..aa4367d30 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentAdvancedReconcilingTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/AddConfigurationIfBeansPresentAdvancedReconcilingTest.java @@ -145,4 +145,32 @@ public class AddConfigurationIfBeansPresentAdvancedReconcilingTest { assertEquals(Boot2JavaProblemType.MISSING_CONFIGURATION_ANNOTATION.getCode(), diagnostics.get(0).getCode().getLeft()); } + @Test + void testErrorAppearsWhenFeignClientAnnotationDoesAwayEntirely() throws Exception { + String feignClientDocUri = directory.toPath().resolve("src/main/java/com/example/feign/demo/FeignClientExample.java").toUri().toString(); + String feignConfigRegisterd = directory.toPath().resolve("src/main/java/com/example/feign/demo/FeignConfigExample.java").toUri().toString(); + + // now change the config class source code and update doc + TestFileScanListener fileScanListener = new TestFileScanListener(); + indexer.getJavaIndexer().setFileScanListener(fileScanListener); + + String feignClientSource = FileUtils.readFileToString(UriUtil.toFile(feignClientDocUri), Charset.defaultCharset()); + String updatedFeignClientSource = feignClientSource.replace("@FeignClient(name = \"stores\", configuration = FeignConfigExample.class)", + ""); + + CompletableFuture updateFuture = indexer.updateDocument(feignClientDocUri, updatedFeignClientSource, "test triggered"); + updateFuture.get(5, TimeUnit.SECONDS); + + // check if the bean registrar files have been re-scanned + fileScanListener.assertScannedUri(feignClientDocUri, 1); + fileScanListener.assertScannedUri(feignConfigRegisterd, 1); + fileScanListener.assertFileScanCount(2); + + // check diagnostics result + PublishDiagnosticsParams diagnosticsResult = harness.getDiagnostics(feignConfigRegisterd); + List diagnostics = diagnosticsResult.getDiagnostics(); + assertEquals(1, diagnostics.size()); + assertEquals(Boot2JavaProblemType.MISSING_CONFIGURATION_ANNOTATION.getCode(), diagnostics.get(0).getCode().getLeft()); + } + } diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-indexing/src/main/java/org/test/SimpleComponentClass.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-indexing/src/main/java/org/test/SimpleComponentClass.java new file mode 100644 index 000000000..aeb0ca429 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-indexing/src/main/java/org/test/SimpleComponentClass.java @@ -0,0 +1,8 @@ +package org.test; + +import org.springframework.stereotype.Component; + +@Component +public class SimpleComponentClass { + +}