Bean completion inside constructors

This commit is contained in:
aboyko
2025-02-27 21:45:51 -05:00
parent bccd2bac85
commit 1238fecc74
5 changed files with 563 additions and 91 deletions

View File

@@ -15,6 +15,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.jspecify.annotations.NonNull;
@@ -34,6 +35,7 @@ import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.J.Assignment;
import org.openrewrite.java.tree.J.Block;
import org.openrewrite.java.tree.J.ClassDeclaration;
import org.openrewrite.java.tree.J.Identifier;
import org.openrewrite.java.tree.J.MethodDeclaration;
import org.openrewrite.java.tree.J.VariableDeclarations;
import org.openrewrite.java.tree.JLeftPadded;
@@ -113,7 +115,7 @@ public class ConstructorInjectionRecipe extends Recipe {
} else if (constructors.size() == 1) {
MethodDeclaration c = constructors.get(0);
getCursor().putMessage("applicableConstructor", c);
applicable = isNotConstructorInitializingField(c, fieldName);
applicable = !isConstructorInitializingField(c, fieldName);
} else {
List<MethodDeclaration> autowiredConstructors = constructors.stream()
.filter(constr -> constr.getLeadingAnnotations().stream()
@@ -123,7 +125,7 @@ public class ConstructorInjectionRecipe extends Recipe {
if (autowiredConstructors.size() == 1) {
MethodDeclaration c = autowiredConstructors.get(0);
getCursor().putMessage("applicableConstructor", autowiredConstructors.get(0));
applicable = isNotConstructorInitializingField(c, fieldName);
applicable = !isConstructorInitializingField(c, fieldName);
}
}
if (applicable) {
@@ -133,31 +135,6 @@ public class ConstructorInjectionRecipe extends Recipe {
return super.visitClassDeclaration(classDecl, ctx);
}
public static boolean isNotConstructorInitializingField(MethodDeclaration c, String fieldName) {
return c.getBody() == null || c.getBody().getStatements().stream().filter(J.Assignment.class::isInstance)
.map(J.Assignment.class::cast).noneMatch(a -> {
Expression expr = a.getVariable();
if (expr instanceof J.FieldAccess) {
J.FieldAccess fa = (J.FieldAccess) expr;
if (fieldName.equals(fa.getSimpleName()) && fa.getTarget() instanceof J.Identifier) {
J.Identifier target = (J.Identifier) fa.getTarget();
if ("this".equals(target.getSimpleName())) {
return true;
}
}
}
if (expr instanceof J.Identifier) {
JavaType.Variable fieldType = c.getMethodType().getDeclaringType().getMembers().stream()
.filter(v -> fieldName.equals(v.getName())).findFirst().orElse(null);
if (fieldType != null) {
J.Identifier identifier = (J.Identifier) expr;
return fieldType.equals(identifier.getFieldType());
}
}
return false;
});
}
@Override
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable,
ExecutionContext ctx) {
@@ -285,20 +262,22 @@ public class ConstructorInjectionRecipe extends Recipe {
md = md.withParameters(newParams);
updateCursor(md);
// noinspection ConstantConditions
ShallowClass type = JavaType.ShallowClass.build(methodType);
J.FieldAccess fa = new J.FieldAccess(Tree.randomId(), Space.EMPTY, Markers.EMPTY, new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), "this", md.getMethodType().getDeclaringType(), null), JLeftPadded.build(createFieldNameIdentifier()), type);
Assignment assign = new J.Assignment(Tree.randomId(), Space.build("\n", Collections.emptyList()), Markers.EMPTY, fa, JLeftPadded.build(createFieldNameIdentifier()), type);
assign = autoFormat(assign, p, getCursor());
List<Statement> newStatements = new ArrayList<>(md.getBody().getStatements());
boolean empty = newStatements.isEmpty();
if (empty) {
newStatements.add(assign);
md = md.withBody(autoFormat(md.getBody().withStatements(newStatements), p, getCursor()));
} else {
// Prefix is off otherwise even after autoFormat
newStatements.add(assign.withPrefix(newStatements.get(newStatements.size() - 1).getPrefix()));
md = md.withBody(md.getBody().withStatements(newStatements));
if (!isConstructorInitializingField(md, fieldName)) {
// noinspection ConstantConditions
ShallowClass type = JavaType.ShallowClass.build(methodType);
J.FieldAccess fa = new J.FieldAccess(Tree.randomId(), Space.EMPTY, Markers.EMPTY, new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), "this", md.getMethodType().getDeclaringType(), null), JLeftPadded.build(createFieldNameIdentifier()), type);
Assignment assign = new J.Assignment(Tree.randomId(), Space.build("\n", Collections.emptyList()), Markers.EMPTY, fa, JLeftPadded.build(createFieldNameIdentifier()), type);
assign = autoFormat(assign, p, getCursor());
List<Statement> newStatements = new ArrayList<>(md.getBody().getStatements());
boolean empty = newStatements.isEmpty();
if (empty) {
newStatements.add(assign);
md = md.withBody(autoFormat(md.getBody().withStatements(newStatements), p, getCursor()));
} else {
// Prefix is off otherwise even after autoFormat
newStatements.add(assign.withPrefix(newStatements.get(newStatements.size() - 1).getPrefix()));
md = md.withBody(md.getBody().withStatements(newStatements));
}
}
}
return md;
@@ -321,4 +300,48 @@ public class ConstructorInjectionRecipe extends Recipe {
return fullyQualifiedType.getClassName();
}
private static boolean isConstructorInitializingField(MethodDeclaration c, String fieldName) {
AtomicBoolean res = new AtomicBoolean();
new JavaIsoVisitor<AtomicBoolean>() {
@Override
public Assignment visitAssignment(Assignment assignment, AtomicBoolean ab) {
if (ab.get() || getCursor().firstEnclosing(MethodDeclaration.class) != c) {
return assignment;
}
Assignment a = super.visitAssignment(assignment, ab);
Expression expr = a.getVariable();
if (expr instanceof J.FieldAccess) {
J.FieldAccess fa = (J.FieldAccess) expr;
if (fieldName.equals(fa.getSimpleName()) && fa.getTarget() instanceof J.Identifier) {
J.Identifier target = (J.Identifier) fa.getTarget();
if ("this".equals(target.getSimpleName())) {
ab.set(true);
return a;
}
}
}
return a;
}
@Override
public Identifier visitIdentifier(Identifier identifier, AtomicBoolean ab) {
if (ab.get() || getCursor().firstEnclosing(MethodDeclaration.class) != c) {
return identifier;
}
Identifier id = super.visitIdentifier(identifier, ab);
JavaType.Variable fieldType = c.getMethodType().getDeclaringType().getMembers().stream()
.filter(v -> fieldName.equals(v.getName())).findFirst().orElse(null);
if (fieldType != null && fieldType.equals(id.getFieldType())) {
ab.set(true);
}
return id;
}
}.visit(c, res);
return res.get();
}
}

View File

@@ -14,15 +14,28 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Assignment;
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.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.CompletionItemLabelDetails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposalWithScore;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
import org.springframework.ide.vscode.commons.rewrite.java.InjectBeanCompletionRecipe;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.text.IDocument;
@@ -32,27 +45,35 @@ import org.springframework.ide.vscode.commons.util.text.IDocument;
* @author Alex Boyko
*/
public class BeanCompletionProposal implements ICompletionProposalWithScore {
private static final Logger log = LoggerFactory.getLogger(BeanCompletionProposal.class);
private static final String SHORT_DESCRIPTION = "inject as a bean dependency";
private DocumentEdits edits;
private IDocument doc;
private String beanId;
private String beanType;
private String className;
private RewriteRefactorings rewriteRefactorings;
private double score;
private ASTNode node;
private int offset;
public BeanCompletionProposal(DocumentEdits edits, IDocument doc, String beanId, String beanType, String className,
double score,
RewriteRefactorings rewriteRefactorings) {
this.edits = edits;
private String prefix;
private DocumentEdits edits;
public BeanCompletionProposal(ASTNode node, int offset, IDocument doc, String beanId, String beanType,
String className, RewriteRefactorings rewriteRefactorings) {
this.node = node;
this.offset = offset;
this.doc = doc;
this.beanId = beanId;
this.beanType = beanType;
this.className = className;
this.score = score;
this.rewriteRefactorings = rewriteRefactorings;
this.prefix = computePrefix();
this.edits = computeEdit();
this.score = FuzzyMatcher.matchScore(prefix, beanId);
}
@Override
@@ -65,6 +86,67 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore {
return CompletionItemKind.Field;
}
private String computePrefix() {
String prefix = "";
try {
// Empty SimpleName usually comes from unresolved FieldAccess, i.e. `this.owner`
// where `owner` field is not defined
if (node instanceof SimpleName sn) {
FieldAccess fa = getFieldAccessFromIncompleteThisAssignment(sn);
if (fa != null) {
prefix = fa.getName().toString();
} else if (!BootJavaCompletionEngine.$MISSING$.equals(sn.toString())) {
prefix = sn.toString();
}
} else if (isIncompleteThisFieldAccess()) {
FieldAccess fa = (FieldAccess) node;
int start = fa.getExpression().getStartPosition() + fa.getExpression().getLength();
while (start < doc.getLength() && doc.getChar(start) != '.') {
start++;
}
prefix = doc.get(start + 1, offset - start - 1);
}
} catch (BadLocationException e) {
log.error("Failed to compute prefix for completion proposal", e);
}
return prefix;
}
private boolean isIncompleteThisFieldAccess() {
return node instanceof FieldAccess fa && fa.getExpression() instanceof ThisExpression;
}
private FieldAccess getFieldAccessFromIncompleteThisAssignment(SimpleName sn) {
if ((node.getLength() == 0 || BootJavaCompletionEngine.$MISSING$.equals(sn.toString()))
&& sn.getParent() instanceof Assignment assign && assign.getLeftHandSide() instanceof FieldAccess fa
&& fa.getExpression() instanceof ThisExpression) {
return fa;
}
return null;
}
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));
} else {
if (node.getParent() instanceof Assignment || node.getParent() instanceof FieldAccess) {
edits.replace(offset - prefix.length(), offset, "%s = %s;".formatted(beanId, beanId));
} else {
edits.replace(offset - prefix.length(), offset, "this.%s = %s;".formatted(beanId, beanId));
}
}
} else {
if (node instanceof Block) {
edits.insert(offset, beanId);
} else {
edits.replace(offset - prefix.length(), offset, beanId);
}
}
return edits;
}
@Override
public DocumentEdits getTextEdit() {
return edits;
@@ -74,7 +156,7 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore {
public String getDetail() {
return "Autowire a bean";
}
@Override
public CompletionItemLabelDetails getLabelDetails() {
CompletionItemLabelDetails labelDetails = new CompletionItemLabelDetails();
@@ -84,13 +166,14 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore {
@Override
public Renderable getDocumentation() {
return Renderables.text(
"Inject bean `%s` of type `%s` as a constructor parameter and add corresponding field".formatted(beanId, beanType));
return Renderables.text("Inject bean `%s` of type `%s` as a constructor parameter and add corresponding field"
.formatted(beanId, beanType));
}
@Override
public Optional<Command> getCommand() {
FixDescriptor f = new FixDescriptor(InjectBeanCompletionRecipe.class.getName(), List.of(this.doc.getUri()),"Inject bean completions")
FixDescriptor f = new FixDescriptor(InjectBeanCompletionRecipe.class.getName(), List.of(this.doc.getUri()),
"Inject bean completions")
.withParameters(Map.of("fullyQualifiedName", beanType, "fieldName", beanId, "classFqName", className))
.withRecipeScope(RecipeScope.NODE);
return Optional.of(rewriteRefactorings.createFixCommand("Inject bean '%s'".formatted(beanId), f));
@@ -100,5 +183,14 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore {
public double getScore() {
return score;
}
private boolean isInsideConstructor(ASTNode node) {
for (ASTNode n = node; n != null && !(n instanceof CompilationUnit); n = n.getParent()) {
if (n instanceof MethodDeclaration md) {
return md.isConstructor() || md.isCompactConstructor();
}
}
return false;
}
}

View File

@@ -19,11 +19,11 @@ import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.Assignment;
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.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.TypeDeclaration;
@@ -36,11 +36,9 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchie
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
@@ -66,10 +64,22 @@ public class BeanCompletionProvider implements CompletionProvider {
Collection<ICompletionProposal> completions) {
if (node instanceof SimpleName || node instanceof Block || node instanceof FieldAccess) {
try {
if (node instanceof SimpleName) {
if (node.getParent() instanceof FieldAccess fa && !(fa.getExpression() instanceof ThisExpression)) {
return;
}
}
if (node instanceof FieldAccess fa && !(fa.getExpression() instanceof ThisExpression)) {
return;
}
// Don't look at anything inside Annotation or VariableDelcaration node
for (ASTNode n = node; n != null; n = n.getParent()) {
if (n instanceof Annotation
|| n instanceof VariableDeclaration) {
|| n instanceof VariableDeclaration
|| n instanceof QualifiedName /* Ignores statements such as 's.' or 's.foo' */) {
return;
}
}
@@ -85,26 +95,6 @@ public class BeanCompletionProvider implements CompletionProvider {
return;
}
String prefix = "";
// Empty SimpleName usually comes from unresolved FieldAccess, i.e. `this.owner` where `owner` field is not defined
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());
@@ -123,18 +113,11 @@ public class BeanCompletionProvider implements CompletionProvider {
if (declaredFiledsTypes.contains(bean.getType())) {
continue;
}
double score = FuzzyMatcher.matchScore(prefix, bean.getName());
if (score > 0) {
DocumentEdits edits = new DocumentEdits(doc, false);
if (node instanceof Block) {
edits.insert(offset, bean.getName());
} else {
edits.replace(offset - prefix.length(), offset, bean.getName());
}
BeanCompletionProposal proposal = new BeanCompletionProposal(edits, doc, bean.getName(),
bean.getType(), className, score, rewriteRefactorings);
BeanCompletionProposal proposal = new BeanCompletionProposal(node, offset, doc, bean.getName(),
bean.getType(), className, rewriteRefactorings);
if (proposal.getScore() > 0) {
completions.add(proposal);
}
}

View File

@@ -38,6 +38,8 @@ import com.google.common.collect.ImmutableList;
*/
public class BootJavaCompletionEngine implements ICompletionEngine, LanguageSpecific {
public static final String $MISSING$ = "$missing$";
private Map<String, CompletionProvider> completionProviders;
private JavaSnippetManager snippets;
private CompilationUnitCache cuCache;
@@ -81,13 +83,14 @@ public class BootJavaCompletionEngine implements ICompletionEngine, LanguageSpec
// exact situation and pass on the ASTNode for the SimpleName.
try {
int numberOfSpaces = 0;
while (Character.isSpaceChar(document.getChar(offset - numberOfSpaces - 1))) {
while (Character.isSpaceChar(document.getChar(offset - numberOfSpaces - 1))
|| document.getChar(offset - numberOfSpaces - 1) == '.') {
numberOfSpaces++;
}
if (numberOfSpaces > 0) {
ASTNode leftNode = NodeFinder.perform(cu, offset - numberOfSpaces, 0);
if (leftNode instanceof SimpleName && "$missing$".equals(((SimpleName)leftNode).getIdentifier())) {
if (leftNode instanceof SimpleName && $MISSING$.equals(((SimpleName)leftNode).getIdentifier())) {
node = leftNode;
}
}

View File

@@ -43,6 +43,7 @@ import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* @author Udayani V
*/
@@ -520,6 +521,44 @@ public class TestBeanCompletionClass {
""");
}
@Test
public void noCompletionsInMethod_1() throws Exception {
String content = """
package org.sample.test;
import org.springframework.stereotype.Controller;
@Controller
public class TestBeanCompletionClass {
public void test(String s) {
s.owner<*>
}
}
""";
assertCompletions(content, new String[0], 0, null);
}
@Test
public void noCompletionsInMethod_2() throws Exception {
String content = """
package org.sample.test;
import org.springframework.stereotype.Controller;
@Controller
public class TestBeanCompletionClass {
public void test(String s) {
s.<*>
}
}
""";
assertCompletions(content, new String[0], 0, null);
}
@Test
public void completionsForRestController() throws Exception {
String content = """
@@ -678,6 +717,338 @@ public class TestBeanCompletionClass {
""");
}
@Test
public void constructorLastStatement() throws Exception {
String content = """
package org.sample.test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestBeanCompletionClass {
public TestBeanCompletionClass() {
super();
this.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;
public TestBeanCompletionClass(OwnerRepository ownerRepository) {
super();
this.ownerRepository = ownerRepository;<*>
}
}
""");
}
@Test
public void constructorAfterStatements() throws Exception {
String content = """
package org.sample.test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestBeanCompletionClass {
public TestBeanCompletionClass() {
super();
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;
public TestBeanCompletionClass(OwnerRepository ownerRepository) {
super();
this.ownerRepository = ownerRepository;<*>
System.out.println();
}
}
""");
}
@Test
public void constructorNamePrefix() throws Exception {
String content = """
package org.sample.test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestBeanCompletionClass {
public TestBeanCompletionClass() {
super();
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;
public TestBeanCompletionClass(OwnerRepository ownerRepository) {
super();
this.ownerRepository = ownerRepository;<*>
}
}
""");
}
@Test
public void constructorNamePrefixAfterStatements() throws Exception {
String content = """
package org.sample.test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestBeanCompletionClass {
public TestBeanCompletionClass() {
super();
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;
public TestBeanCompletionClass(OwnerRepository ownerRepository) {
super();
this.ownerRepository = ownerRepository;<*>
System.out.println();
}
}
""");
}
@Test
public void constructorThisStatementWithoutPrefix() throws Exception {
String content = """
package org.sample.test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestBeanCompletionClass {
public TestBeanCompletionClass() {
super();
this.<*>
}
}
""";
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;
public TestBeanCompletionClass(OwnerRepository ownerRepository) {
super();
this.ownerRepository = ownerRepository;<*>
}
}
""");
}
@Test
public void constructorThisStatementWithoutPrefixAfterStatements() throws Exception {
String content = """
package org.sample.test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestBeanCompletionClass {
public TestBeanCompletionClass() {
super();
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;
public TestBeanCompletionClass(OwnerRepository ownerRepository) {
super();
this.ownerRepository = ownerRepository;<*>
System.out.println();
}
}
""");
}
@Test
public void constructorNoThisNoPrefix() throws Exception {
String content = """
package org.sample.test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestBeanCompletionClass {
public TestBeanCompletionClass() {
super();
<*>
}
}
""";
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;
public TestBeanCompletionClass(OwnerRepository ownerRepository) {
super();
this.ownerRepository = ownerRepository;<*>
}
}
""");
}
@Test
public void constructorNoThisNoPrefixAfterStatements() throws Exception {
String content = """
package org.sample.test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestBeanCompletionClass {
public TestBeanCompletionClass() {
super();
<*>
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;
public TestBeanCompletionClass(OwnerRepository ownerRepository) {
super();
this.ownerRepository = ownerRepository;<*>
System.out.println();
}
}
""");
}
@Test
public void constructorNoCompletions_1() throws Exception {
String content = """
package org.sample.test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestBeanCompletionClass {
public TestBeanCompletionClass(String s) {
s.<*>
}
}
""";
assertCompletions(content, new String[0], 0, null);
}
@Test
public void constructorNoCompletions_2() throws Exception {
String content = """
package org.sample.test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestBeanCompletionClass {
public TestBeanCompletionClass(String s) {
s.ow<*>
}
}
""";
assertCompletions(content, new String[0], 0, null);
}
private void assertCompletions(String completionLine, String[] expectedCompletions, int chosenCompletion, String expectedResult) throws Exception {
assertCompletions(completionLine, expectedCompletions.length, expectedCompletions, chosenCompletion, expectedResult);
}