Skip to content

Commit

Permalink
GH-1375: added check to not show bean names as qualifier proposals on…
Browse files Browse the repository at this point in the history
… type declarations
  • Loading branch information
martinlippert committed Oct 11, 2024
1 parent 2dffa3a commit 3b95268
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,16 +40,21 @@ public QualifierCompletionProvider(SpringMetamodelIndex springIndex) {
public Map<String, String> 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<String> 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<String> findAllQualifiers(Bean[] beans) {

Stream<String> qualifiersFromBeans = Arrays.stream(beans)
// annotations from beans themselves
.flatMap(bean -> Arrays.stream(bean.getAnnotations()))
Expand All @@ -69,4 +75,19 @@ private Stream<String> 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<CompletionItem> completions = editor.getCompletions();
Expand All @@ -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) {" +
"""
}
}
""";
}
}


Expand Down

0 comments on commit 3b95268

Please sign in to comment.