Bean completion inside constructors
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user