From b0d42d1410490e22ac98997b570670f8840c7de4 Mon Sep 17 00:00:00 2001 From: aboyko Date: Fri, 28 Feb 2025 11:32:43 -0500 Subject: [PATCH] Bean completion case of the same name field --- .../java/beans/BeanCompletionProposal.java | 16 ++-- .../java/beans/BeanCompletionProvider.java | 26 ++++--- .../test/BeanCompletionProviderTest.java | 78 +++++++++++++++++++ 3 files changed, 102 insertions(+), 18 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java index 377e343fd0..22b8024bf8 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java @@ -54,6 +54,7 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore { private IDocument doc; private String beanId; private String beanType; + private String fieldName; private String className; private RewriteRefactorings rewriteRefactorings; private double score; @@ -64,12 +65,13 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore { private DocumentEdits edits; public BeanCompletionProposal(ASTNode node, int offset, IDocument doc, String beanId, String beanType, - String className, RewriteRefactorings rewriteRefactorings) { + String fieldName, String className, RewriteRefactorings rewriteRefactorings) { this.node = node; this.offset = offset; this.doc = doc; this.beanId = beanId; this.beanType = beanType; + this.fieldName = fieldName; this.className = className; this.rewriteRefactorings = rewriteRefactorings; this.prefix = computePrefix(); @@ -130,19 +132,19 @@ private DocumentEdits computeEdit() { DocumentEdits edits = new DocumentEdits(doc, false); if (isInsideConstructor(node)) { if (node instanceof Block) { - edits.insert(offset, "this.%s = %s;".formatted(beanId, beanId)); + edits.insert(offset, "this.%s = %s;".formatted(fieldName, fieldName)); } else { if (node.getParent() instanceof Assignment || node.getParent() instanceof FieldAccess) { - edits.replace(offset - prefix.length(), offset, "%s = %s;".formatted(beanId, beanId)); + edits.replace(offset - prefix.length(), offset, "%s = %s;".formatted(fieldName, fieldName)); } else { - edits.replace(offset - prefix.length(), offset, "this.%s = %s;".formatted(beanId, beanId)); + edits.replace(offset - prefix.length(), offset, "this.%s = %s;".formatted(fieldName, fieldName)); } } } else { if (node instanceof Block) { - edits.insert(offset, beanId); + edits.insert(offset, fieldName); } else { - edits.replace(offset - prefix.length(), offset, beanId); + edits.replace(offset - prefix.length(), offset, fieldName); } } return edits; @@ -176,7 +178,7 @@ public Renderable getDocumentation() { public Optional getCommand() { FixDescriptor f = new FixDescriptor(InjectBeanCompletionRecipe.class.getName(), List.of(this.doc.getUri()), "Inject bean completions") - .withParameters(Map.of("fullyQualifiedName", beanType, "fieldName", beanId, "classFqName", className)) + .withParameters(Map.of("fullyQualifiedName", beanType, "fieldName", fieldName, "classFqName", className)) .withRecipeScope(RecipeScope.NODE); return Optional.of(rewriteRefactorings.createFixCommand("Inject bean '%s'".formatted(beanId), f)); } 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 d325a13cbe..1f483bc997 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 @@ -10,12 +10,10 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans; -import java.util.Arrays; import java.util.Collection; -import java.util.Objects; +import java.util.HashSet; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; @@ -23,6 +21,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.ThisExpression; @@ -99,23 +98,28 @@ public void provideCompletions(ASTNode node, int offset, TextDocument doc, String className = getFullyQualifiedName(topLevelClass); Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); ITypeBinding topLevelBeanType = topLevelClass.resolveBinding(); - Set declaredFiledsTypes = Arrays.stream(topLevelBeanType.getDeclaredFields()) - .map(vd -> vd.getType()) - .filter(Objects::nonNull) - .map(t -> t.getQualifiedName()) - .collect(Collectors.toSet()); + Set fieldTypes = new HashSet<>(); + Set fieldNames = new HashSet<>(); + for (IVariableBinding vd : topLevelBeanType.getDeclaredFields()) { + fieldNames.add(vd.getName()); + fieldTypes.add(vd.getType().getQualifiedName()); + } for (Bean bean : beans) { // If current class is a bean - ignore it if (className.equals(bean.getType())) { continue; } // Filter out beans already injected into this class - if (declaredFiledsTypes.contains(bean.getType())) { + if (fieldTypes.contains(bean.getType())) { continue; } - + + String fieldName = bean.getName(); + for (int i = 0; i < Integer.MAX_VALUE && fieldNames.contains(fieldName); i++, fieldName = "%s_%d".formatted(bean.getName(), i)) { + // nothing + } BeanCompletionProposal proposal = new BeanCompletionProposal(node, offset, doc, bean.getName(), - bean.getType(), className, rewriteRefactorings); + bean.getType(), fieldName, className, rewriteRefactorings); if (proposal.getScore() > 0) { completions.add(proposal); 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 6c23daae73..2a62740d04 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 @@ -677,6 +677,84 @@ public void test() { """); } + @Test + public void sameNameFieldExists_Constructor() throws Exception { + String content = """ + package org.sample.test; + + import org.springframework.web.bind.annotation.RestController; + + @RestController + public class TestBeanCompletionClass { + String ownerRepository; + public TestBeanCompletionClass(String ownerRepository) { + this.ownerRepository = ownerRepository; + ow<*> + } + } + """; + + + 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_1; + String ownerRepository; + public TestBeanCompletionClass(String ownerRepository, OwnerRepository ownerRepository_1) { + this.ownerRepository = ownerRepository; + this.ownerRepository_1 = ownerRepository_1;<*> + } + } + """); + } + + @Test + public void sameNameFieldExists_Method() throws Exception { + String content = """ + package org.sample.test; + + import org.springframework.web.bind.annotation.RestController; + + @RestController + public class TestBeanCompletionClass { + String ownerRepository; + public void test() { + ow<*> + } + } + """; + + + 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_1; + String ownerRepository; + + TestBeanCompletionClass(OwnerRepository ownerRepository_1) { + this.ownerRepository_1 = ownerRepository_1; + } + public void test() { + ownerRepository_1<*> + } + } + """); + } + @Test public void beforeStatementStartingWithThisAndPrefix() throws Exception { String content = """