From 1630eb1f16df0be5f0df54fa8988ba01f6ed5c34 Mon Sep 17 00:00:00 2001 From: aboyko Date: Thu, 27 Feb 2025 10:34:26 -0500 Subject: [PATCH] Bean completion account for FieldAccess invocation node --- .../java/beans/BeanCompletionProvider.java | 26 ++-- .../test/BeanCompletionProviderTest.java | 120 ++++++++++++++++++ 2 files changed, 138 insertions(+), 8 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java index e85ebe2286..49a35c38ca 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java @@ -64,7 +64,7 @@ public BeanCompletionProvider(JavaProjectFinder javaProjectFinder, SpringMetamod @Override public void provideCompletions(ASTNode node, int offset, TextDocument doc, Collection completions) { - if (node instanceof SimpleName || node instanceof Block) { + if (node instanceof SimpleName || node instanceof Block || node instanceof FieldAccess) { try { // Don't look at anything inside Annotation or VariableDelcaration node for (ASTNode n = node; n != null; n = n.getParent()) { @@ -85,15 +85,26 @@ public void provideCompletions(ASTNode node, int offset, TextDocument doc, return; } + String prefix = ""; + // Empty SimpleName usually comes from unresolved FieldAccess, i.e. `this.owner` where `owner` field is not defined - if (node instanceof SimpleName se && se.getLength() == 0 - && node.getParent() instanceof Assignment assign - && assign.getLeftHandSide() instanceof FieldAccess fa - && fa.getExpression() instanceof ThisExpression) { - node = fa.getName(); + if (node instanceof SimpleName sn) { + if (sn.getLength() == 0 + && sn.getParent() instanceof Assignment assign + && assign.getLeftHandSide() instanceof FieldAccess fa + && fa.getExpression() instanceof ThisExpression) { + prefix = fa.getName().toString(); + } else { + prefix = sn.toString(); + } + } else if (node instanceof FieldAccess fa && fa.getExpression() instanceof ThisExpression) { + int start = fa.getExpression().getStartPosition() + fa.getExpression().getLength(); + while (start < doc.getLength() && doc.getChar(start) != '.') { + start++; + } + prefix = doc.get(start + 1, offset - start - 1); } - if (AnnotationHierarchies.get(node).isAnnotatedWith(topLevelClass.resolveBinding(), Annotations.COMPONENT)) { String className = getFullyQualifiedName(topLevelClass); Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); @@ -103,7 +114,6 @@ public void provideCompletions(ASTNode node, int offset, TextDocument doc, .filter(Objects::nonNull) .map(t -> t.getQualifiedName()) .collect(Collectors.toSet()); - final String prefix = node instanceof Block ? "" : node.toString(); for (Bean bean : beans) { // If current class is a bean - ignore it if (className.equals(bean.getType())) { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java index 77ae3002a4..3f4351871a 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/BeanCompletionProviderTest.java @@ -558,6 +558,126 @@ public void test() { """); } + @Test + public void beforeStatement() throws Exception { + String content = """ + package org.sample.test; + + import org.springframework.web.bind.annotation.RestController; + + @RestController + public class TestBeanCompletionClass { + public void test() { + owner<*> + System.out.println(); + } + } + """; + + + assertCompletions(content, new String[] {"ownerRepository", "ownerService"}, 0, + """ + package org.sample.test; + + import org.springframework.samples.petclinic.owner.OwnerRepository; + import org.springframework.web.bind.annotation.RestController; + + @RestController + public class TestBeanCompletionClass { + + private final OwnerRepository ownerRepository; + + TestBeanCompletionClass(OwnerRepository ownerRepository) { + this.ownerRepository = ownerRepository; + } + public void test() { + ownerRepository<*> + System.out.println(); + } + } + """); + } + + @Test + public void beforeStatementStartingWithThis() throws Exception { + String content = """ + package org.sample.test; + + import org.springframework.web.bind.annotation.RestController; + + @RestController + public class TestBeanCompletionClass { + public void test() { + this.<*> + System.out.println(); + } + } + """; + + + assertCompletions(content, new String[] {"ownerRepository", "ownerService", "petService", "visitRepository", "visitService"}, 0, + """ + package org.sample.test; + + import org.springframework.samples.petclinic.owner.OwnerRepository; + import org.springframework.web.bind.annotation.RestController; + + @RestController + public class TestBeanCompletionClass { + + private final OwnerRepository ownerRepository; + + TestBeanCompletionClass(OwnerRepository ownerRepository) { + this.ownerRepository = ownerRepository; + } + public void test() { + this.ownerRepository<*> + System.out.println(); + } + } + """); + } + + @Test + public void beforeStatementStartingWithThisAndPrefix() throws Exception { + String content = """ + package org.sample.test; + + import org.springframework.web.bind.annotation.RestController; + + @RestController + public class TestBeanCompletionClass { + public void test() { + this.ow<*> + System.out.println(); + } + } + """; + + + assertCompletions(content, new String[] {"ownerRepository", "ownerService"}, 0, + """ + package org.sample.test; + + import org.springframework.samples.petclinic.owner.OwnerRepository; + import org.springframework.web.bind.annotation.RestController; + + @RestController + public class TestBeanCompletionClass { + + private final OwnerRepository ownerRepository; + + TestBeanCompletionClass(OwnerRepository ownerRepository) { + this.ownerRepository = ownerRepository; + } + public void test() { + this.ownerRepository<*> + System.out.println(); + } + } + """); + } + private void assertCompletions(String completionLine, String[] expectedCompletions, int chosenCompletion, String expectedResult) throws Exception { assertCompletions(completionLine, expectedCompletions.length, expectedCompletions, chosenCompletion, expectedResult); }