diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProvider.java index cde440eba6..b91f23c598 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierCompletionProvider.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider; @@ -39,16 +40,21 @@ public QualifierCompletionProvider(SpringMetamodelIndex springIndex) { public Map getCompletionCandidates(IJavaProject project, ASTNode node) { Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); + + boolean isOnType = isAnnotationOnType(node); - return Stream.concat( - findAllQualifiers(beans), - Arrays.stream(beans).map(bean -> bean.getName())) + Stream candidates = findAllQualifiers(beans); + if (!isOnType) { + candidates = Stream.concat(candidates, Arrays.stream(beans).map(bean -> bean.getName())); + } + + return candidates .distinct() .collect(Collectors.toMap(key -> key, value -> value, (u, v) -> u, LinkedHashMap::new)); } private Stream findAllQualifiers(Bean[] beans) { - + Stream qualifiersFromBeans = Arrays.stream(beans) // annotations from beans themselves .flatMap(bean -> Arrays.stream(bean.getAnnotations())) @@ -69,4 +75,19 @@ private Stream findAllQualifiers(Bean[] beans) { return Stream.concat(qualifiersFromBeans, qualifiersFromInjectionPoints); } + private boolean isAnnotationOnType(ASTNode node) { + while (node != null) { + if (node instanceof MethodDeclaration) { + return false; + } + else if (node instanceof TypeDeclaration) { + return true; + } + + node = node.getParent(); + } + + return false; + } + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java index 34fa17b203..4c65e4ca02 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierCompletionProviderTest.java @@ -26,6 +26,7 @@ import org.eclipse.lsp4j.TextDocumentIdentifier; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -94,78 +95,87 @@ public void restoreIndexState() { } @Test - public void testQualifierCompletionWithoutQuotesWithoutPrefix() throws Exception { - assertCompletions("@Qualifier(<*>)", new String[] {"quali1", "quali2", "bean1", "bean2"}, 0, "@Qualifier(\"quali1\"<*>)"); + public void testQualifierCompletionWithoutQuotesWithoutPrefixAtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier(<*>)", new String[] {"quali1", "quali2", "bean1", "bean2"}, 0, "@Qualifier(\"quali1\"<*>)", false); } @Test - public void testQualifierCompletionWithoutQuotesWithPrefix() throws Exception { - assertCompletions("@Qualifier(be<*>)", 2, "@Qualifier(\"bean1\"<*>)"); + public void testQualifierCompletionWithoutQuotesWithoutPrefixAtTypeDeclaration() throws Exception { + assertCompletions("@Qualifier(<*>)", new String[] {"quali1", "quali2"}, 0, "@Qualifier(\"quali1\"<*>)", true); } @Test - public void testQualifierCompletionWithoutQuotesWithPrefixFromExistingQualifier() throws Exception { - assertCompletions("@Qualifier(qu<*>)", new String[] {"quali1", "quali2"}, 0, "@Qualifier(\"quali1\"<*>)"); + public void testQualifierCompletionWithoutQuotesWithPrefixAtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier(be<*>)", 2, "@Qualifier(\"bean1\"<*>)", false); } @Test - public void testQualifierCompletionWithoutQuotesWithAttributeName() throws Exception { - assertCompletions("@Qualifier(value=<*>)", 4, "@Qualifier(value=\"quali1\"<*>)"); + public void testQualifierCompletionWithoutQuotesWithPrefixFromExistingQualifierAtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier(qu<*>)", new String[] {"quali1", "quali2"}, 0, "@Qualifier(\"quali1\"<*>)", false); } @Test - public void testQualifierCompletionInsideOfQuotesWithoutPrefix() throws Exception { - assertCompletions("@Qualifier(\"<*>\")", 4, "@Qualifier(\"quali1<*>\")"); + @Disabled // TODO: implement this case + public void testQualifierCompletionWithoutQuotesWithAttributeNameAtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier(value=<*>)", 4, "@Qualifier(value=\"quali1\"<*>)", false); } @Test - public void testQualifierCompletionInsideOfQuotesWithPrefix() throws Exception { - assertCompletions("@Qualifier(\"be<*>\")", 2, "@Qualifier(\"bean1<*>\")"); + public void testQualifierCompletionWithoutQuotesWithAttributeNameAtTypeDeclaration() throws Exception { + assertCompletions("@Qualifier(value=<*>)", 2, "@Qualifier(value=\"quali1\"<*>)", true); } @Test - public void testQualifierCompletionInsideOfQuotesWithPrefixButWithoutMatches() throws Exception { - assertCompletions("@Qualifier(\"XXX<*>\")", 0, null); + @Disabled // TODO: implement this case + public void testQualifierCompletionWithoutQuotesWithAttributeNameAndSpacesAtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier(value = <*>)", 4, "@Qualifier(value = \"quali1\"<*>)", false); } @Test - public void testQualifierCompletionOutsideOfAnnotation1() throws Exception { - assertCompletions("@Qualifier(\"XXX\")<*>", 0, null); + public void testQualifierCompletionWithoutQuotesWithAttributeNameAndSpacesAtTypeDeclaration() throws Exception { + assertCompletions("@Qualifier(value = <*>)", 2, "@Qualifier(value = \"quali1\"<*>)", true); } @Test - public void testQualifierCompletionOutsideOfAnnotation2() throws Exception { - assertCompletions("@Qualifier<*>(\"XXX\")", 0, null); + public void testQualifierCompletionInsideOfQuotesWithoutPrefixAtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier(\"<*>\")", 4, "@Qualifier(\"quali1<*>\")", false); } @Test - public void testQualifierCompletionInsideOfQuotesWithPrefixAndReplacedPostfix() throws Exception { - assertCompletions("@Qualifier(\"be<*>xxx\")", 2, "@Qualifier(\"bean1<*>\")"); + public void testQualifierCompletionInsideOfQuotesWithPrefixAtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier(\"be<*>\")", 2, "@Qualifier(\"bean1<*>\")", false); } - - private void assertCompletions(String completionLine, int noOfExpectedCompletions, String expectedCompletedLine) throws Exception { - assertCompletions(completionLine, noOfExpectedCompletions, null, 0, expectedCompletedLine); + + @Test + public void testQualifierCompletionInsideOfQuotesWithPrefixButWithoutMatchesAtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier(\"XXX<*>\")", 0, null, false); } - private void assertCompletions(String completionLine, String[] expectedCompletions, int chosenCompletion, String expectedCompletedLine) throws Exception { - assertCompletions(completionLine, expectedCompletions.length, expectedCompletions, chosenCompletion, expectedCompletedLine); + @Test + public void testQualifierCompletionOutsideOfAnnotation1AtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier(\"XXX\")<*>", 0, null, false); } - private void assertCompletions(String completionLine, int noOfExcpectedCompletions, String[] expectedCompletions, int chosenCompletion, String expectedCompletedLine) throws Exception { - String editorContent = """ - package org.test; + @Test + public void testQualifierCompletionOutsideOfAnnotation2AtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier<*>(\"XXX\")", 0, null, false); + } - import org.springframework.stereotype.Component; - import org.springframework.beans.factory.annotation.Qualifier; + @Test + public void testQualifierCompletionInsideOfQuotesWithPrefixAndReplacedPostfixAtInjecitonPoint() throws Exception { + assertCompletions("@Qualifier(\"be<*>xxx\")", 2, "@Qualifier(\"bean1<*>\")", false); + } + + private void assertCompletions(String completionLine, int noOfExpectedCompletions, String expectedCompletedLine, boolean onType) throws Exception { + assertCompletions(completionLine, noOfExpectedCompletions, null, 0, expectedCompletedLine, onType); + } - @Component - """ + - completionLine + "\n" + - """ - public class TestDependsOnClass { - } - """; - + private void assertCompletions(String completionLine, String[] expectedCompletions, int chosenCompletion, String expectedCompletedLine, boolean onType) throws Exception { + assertCompletions(completionLine, expectedCompletions.length, expectedCompletions, chosenCompletion, expectedCompletedLine, onType); + } + + private void assertCompletions(String completionLine, int noOfExcpectedCompletions, String[] expectedCompletions, int chosenCompletion, String expectedCompletedLine, boolean onType) throws Exception { + String editorContent = createEditorContent(completionLine, onType); Editor editor = harness.newEditor(LanguageId.JAVA, editorContent, tempJavaDocUri); List completions = editor.getCompletions(); @@ -181,19 +191,43 @@ public class TestDependsOnClass { if (noOfExcpectedCompletions > 0) { editor.apply(completions.get(chosenCompletion)); - assertEquals(""" - package org.test; + assertEquals(createEditorContent(expectedCompletedLine, onType), editor.getText()); + } + } + private String createEditorContent(String placeholderValue, boolean onType) { + if (onType) { + return """ + package org.test; + import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Qualifier; - + @Component - """ + expectedCompletedLine + "\n" + + """ + + placeholderValue + "\n" + """ public class TestDependsOnClass { } - """, editor.getText()); - } + """; + } + else { + return """ + package org.test; + + import org.springframework.stereotype.Component; + import org.springframework.beans.factory.annotation.Qualifier; + + @Component + public class TestDependsOnClass { + + public TestDependsOnClass(""" + placeholderValue + " Object someBean) {" + + """ + } + + } + """; + } }