diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ConstructorInjectionRecipe.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ConstructorInjectionRecipe.java index 3b12d42b4f..ef729c8529 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ConstructorInjectionRecipe.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ConstructorInjectionRecipe.java @@ -15,6 +15,7 @@ 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.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 J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct } else if (constructors.size() == 1) { MethodDeclaration c = constructors.get(0); getCursor().putMessage("applicableConstructor", c); - applicable = isNotConstructorInitializingField(c, fieldName); + applicable = !isConstructorInitializingField(c, fieldName); } else { List autowiredConstructors = constructors.stream() .filter(constr -> constr.getLeadingAnnotations().stream() @@ -123,7 +125,7 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct 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 J visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ct 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 MethodDeclaration visitMethodDeclaration(MethodDeclaration method, Execut 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 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 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 @@ private static String getFieldType(JavaType.FullyQualified fullyQualifiedType) { return fullyQualifiedType.getClassName(); } + + private static boolean isConstructorInitializingField(MethodDeclaration c, String fieldName) { + AtomicBoolean res = new AtomicBoolean(); + new JavaIsoVisitor() { + + @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(); + } + + + } \ No newline at end of file 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 8c03246ef3..97f2297e96 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 @@ -14,15 +14,28 @@ 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 @@ * @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 CompletionItemKind getKind() { 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 DocumentEdits getTextEdit() { public String getDetail() { return "Autowire a bean"; } - + @Override public CompletionItemLabelDetails getLabelDetails() { CompletionItemLabelDetails labelDetails = new CompletionItemLabelDetails(); @@ -84,13 +166,14 @@ public CompletionItemLabelDetails getLabelDetails() { @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 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 Optional getCommand() { 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; + } + } 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 49a35c38ca..d325a13cbe 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 @@ -19,11 +19,11 @@ 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.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 void provideCompletions(ASTNode node, int offset, TextDocument doc, Collection 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 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 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 void provideCompletions(ASTNode node, int offset, TextDocument doc, 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); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java index b430cec186..48571ab200 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java @@ -38,6 +38,8 @@ */ public class BootJavaCompletionEngine implements ICompletionEngine, LanguageSpecific { + public static final String $MISSING$ = "$missing$"; + private Map completionProviders; private JavaSnippetManager snippets; private CompilationUnitCache cuCache; @@ -81,13 +83,14 @@ private ASTNode findNode(TextDocument document, int offset, CompilationUnit cu) // 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; } } 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 3f4351871a..6c23daae73 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 @@ -43,6 +43,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; + /** * @author Udayani V */ @@ -520,6 +521,44 @@ public void test() { """); } + @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 void test() { """); } + @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); }