Bean completion case of the same name field

This commit is contained in:
aboyko
2025-02-28 11:32:43 -05:00
parent d595f0386f
commit b0d42d1410
3 changed files with 102 additions and 18 deletions

View File

@@ -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 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore {
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 class BeanCompletionProposal implements ICompletionProposalWithScore {
public Optional<Command> 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));
}

View File

@@ -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.Block;
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 class BeanCompletionProvider implements CompletionProvider {
String className = getFullyQualifiedName(topLevelClass);
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
ITypeBinding topLevelBeanType = topLevelClass.resolveBinding();
Set<String> declaredFiledsTypes = Arrays.stream(topLevelBeanType.getDeclaredFields())
.map(vd -> vd.getType())
.filter(Objects::nonNull)
.map(t -> t.getQualifiedName())
.collect(Collectors.toSet());
Set<String> fieldTypes = new HashSet<>();
Set<String> 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);

View File

@@ -677,6 +677,84 @@ public class TestBeanCompletionClass {
""");
}
@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 = """