Inject Bean completion proposal via Rewrite recipes
This commit is contained in:
@@ -26,6 +26,7 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeC
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
|
||||
import org.springframework.ide.vscode.boot.java.beans.BeanNamesCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.beans.BeanTypesCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.beans.BeanCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.DependsOnCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.beans.NamedCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ProfileCompletionProvider;
|
||||
@@ -38,6 +39,7 @@ import org.springframework.ide.vscode.boot.java.cron.CronExpressionCompletionPro
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.boot.java.scope.ScopeCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippet;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetContext;
|
||||
@@ -113,7 +115,8 @@ public class BootJavaCompletionEngineConfigurer {
|
||||
@Qualifier("adHocProperties") ProjectBasedPropertyIndexProvider adHocProperties,
|
||||
JavaSnippetManager snippetManager,
|
||||
CompilationUnitCache cuCache,
|
||||
SpringMetamodelIndex springIndex) {
|
||||
SpringMetamodelIndex springIndex,
|
||||
RewriteRefactorings rewriteRefactorings ) {
|
||||
|
||||
SpringPropertyIndexProvider indexProvider = params.indexProvider;
|
||||
JavaProjectFinder javaProjectFinder = params.projectFinder;
|
||||
@@ -168,6 +171,8 @@ public class BootJavaCompletionEngineConfigurer {
|
||||
providers.put(Annotations.SCHEDULED, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of(
|
||||
"cron", new CronExpressionCompletionProvider())));
|
||||
|
||||
providers.put(Annotations.BEAN, new BeanCompletionProvider(javaProjectFinder, springIndex, rewriteRefactorings));
|
||||
|
||||
return new BootJavaCompletionEngine(cuCache, providers, snippetManager);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
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.ICompletionProposal;
|
||||
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.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* @author Udayani V
|
||||
* @author Alex Boyko
|
||||
*/
|
||||
public class BeanCompletionProposal implements ICompletionProposal {
|
||||
|
||||
private DocumentEdits edits;
|
||||
private IDocument doc;
|
||||
private String beanId;
|
||||
private String beanType;
|
||||
private String className;
|
||||
private RewriteRefactorings rewriteRefactorings;
|
||||
|
||||
public BeanCompletionProposal(DocumentEdits edits, IDocument doc, String beanId, String beanType, String className,
|
||||
RewriteRefactorings rewriteRefactorings) {
|
||||
this.edits = edits;
|
||||
this.doc = doc;
|
||||
this.beanId = beanId;
|
||||
this.beanType = beanType;
|
||||
this.className = className;
|
||||
this.rewriteRefactorings = rewriteRefactorings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return this.beanId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletionItemKind getKind() {
|
||||
return CompletionItemKind.Constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentEdits getTextEdit() {
|
||||
return edits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetail() {
|
||||
return "Autowire a bean";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderable getDocumentation() {
|
||||
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")
|
||||
.withParameters(Map.of("fullyQualifiedName", beanType, "fieldName", beanId, "classFqName", className))
|
||||
.withRecipeScope(RecipeScope.NODE);
|
||||
return Optional.of(rewriteRefactorings.createFixCommand("Inject bean '%s'".formatted(beanId), f));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.VariableDeclaration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Udayani V
|
||||
*/
|
||||
public class BeanCompletionProvider implements CompletionProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BeanCompletionProvider.class);
|
||||
|
||||
private final JavaProjectFinder javaProjectFinder;
|
||||
private final SpringMetamodelIndex springIndex;
|
||||
private final RewriteRefactorings rewriteRefactorings;
|
||||
|
||||
public BeanCompletionProvider(JavaProjectFinder javaProjectFinder, SpringMetamodelIndex springIndex,
|
||||
RewriteRefactorings rewriteRefactorings) {
|
||||
this.javaProjectFinder = javaProjectFinder;
|
||||
this.springIndex = springIndex;
|
||||
this.rewriteRefactorings = rewriteRefactorings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideCompletions(ASTNode node, int offset, TextDocument doc,
|
||||
Collection<ICompletionProposal> completions) {
|
||||
if (node instanceof SimpleName) {
|
||||
try {
|
||||
// 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) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Optional<IJavaProject> optionalProject = this.javaProjectFinder.find(doc.getId());
|
||||
if (optionalProject.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
IJavaProject project = optionalProject.get();
|
||||
TypeDeclaration topLevelClass = findParentClass(node);
|
||||
if (topLevelClass == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSpringComponent(topLevelClass)) {
|
||||
String className = getFullyQualifiedName(topLevelClass);
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
for (Bean bean : beans) {
|
||||
if (FuzzyMatcher.matchScore(node.toString(), bean.getName()) != 0.0) {
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
edits.replace(offset - node.toString().length(), offset, bean.getName());
|
||||
|
||||
BeanCompletionProposal proposal = new BeanCompletionProposal(edits, doc, bean.getName(),
|
||||
bean.getType(), className, rewriteRefactorings);
|
||||
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("problem while looking for bean completions", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSpringComponent(TypeDeclaration node) {
|
||||
for (IAnnotationBinding annotation : node.resolveBinding().getAnnotations()) {
|
||||
if (isSpringComponentAnnotation(annotation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isSpringComponentAnnotation(IAnnotationBinding annotation) {
|
||||
String annotationName = annotation.getAnnotationType().getQualifiedName();
|
||||
if (annotationName.equals("org.springframework.stereotype.Component")) {
|
||||
return true;
|
||||
}
|
||||
for (IAnnotationBinding metaAnnotation : annotation.getAnnotationType().getAnnotations()) {
|
||||
if (metaAnnotation.getAnnotationType().getQualifiedName().equals("org.springframework.stereotype.Component")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static TypeDeclaration findParentClass(ASTNode node) {
|
||||
ASTNode current = node;
|
||||
while (current != null) {
|
||||
if (current instanceof TypeDeclaration) {
|
||||
return (TypeDeclaration) current;
|
||||
}
|
||||
current = current.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getFullyQualifiedName(TypeDeclaration typeDecl) {
|
||||
if (typeDecl.resolveBinding() != null) {
|
||||
String qualifiedName = typeDecl.resolveBinding().getQualifiedName();
|
||||
return qualifiedName.replaceAll("\\.(?=[^\\.]+$)", "\\$");
|
||||
}
|
||||
CompilationUnit cu = (CompilationUnit) typeDecl.getRoot();
|
||||
String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : "";
|
||||
String typeName = typeDecl.getName().getFullyQualifiedName();
|
||||
return packageName.isEmpty() ? typeName : packageName + "." + typeName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import java.net.URI;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -22,11 +23,13 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.openrewrite.Parser.Input;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.SourceFile;
|
||||
import org.openrewrite.config.DeclarativeRecipe;
|
||||
import org.openrewrite.internal.RecipeIntrospectionUtils;
|
||||
import org.openrewrite.java.JavaParser;
|
||||
import org.openrewrite.marker.Range;
|
||||
@@ -82,12 +85,23 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
|
||||
|
||||
@Override
|
||||
public Mono<QuickfixEdit> createEdits(Object p) {
|
||||
if (p instanceof JsonElement) {
|
||||
return Mono.fromFuture(createEdit((JsonElement) p).thenApply(we -> new QuickfixEdit(we, null)));
|
||||
if (p instanceof JsonElement je) {
|
||||
return Mono.fromFuture(createEdit(je).thenApply(we -> new QuickfixEdit(we, null)));
|
||||
} else {
|
||||
return Mono.fromFuture(createEdit(gson.toJsonTree(p)).thenApply(we -> new QuickfixEdit(we, null)));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public Command createFixCommand(String title, FixDescriptor f) {
|
||||
List<Object> args = new ArrayList<>(3);
|
||||
args.add(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX);
|
||||
args.add(gson.toJsonTree(f));
|
||||
return new Command(
|
||||
title,
|
||||
server.CODE_ACTION_COMMAND_ID,
|
||||
args
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WorkspaceEdit> resolve(CodeAction codeAction) {
|
||||
@@ -154,16 +168,8 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
|
||||
.orElseGet(() -> recipeRepo.getRecipe(d.getRecipeId()).thenApply(opt -> opt.orElseThrow())))
|
||||
.thenApply(r -> {
|
||||
if (d.getParameters() != null) {
|
||||
for (Entry<String, Object> entry : d.getParameters().entrySet()) {
|
||||
try {
|
||||
Field f = r.getClass().getDeclaredField(entry.getKey());
|
||||
f.setAccessible(true);
|
||||
f.set(r, entry.getValue());
|
||||
} catch (Exception e) {
|
||||
log.error("", e);;
|
||||
}
|
||||
}
|
||||
}
|
||||
setParameters(r, d.getParameters());
|
||||
}
|
||||
if (d.getRecipeScope() == RecipeScope.NODE) {
|
||||
if (d.getRangeScope() == null) {
|
||||
throw new IllegalArgumentException("Missing scope AST node!");
|
||||
@@ -187,4 +193,41 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parameters for a given recipe. If the recipe is a DeclarativeRecipe,
|
||||
* it iterates over its sub-recipes and sets the parameters for each sub-recipe.
|
||||
*/
|
||||
private void setParameters(Recipe recipe, Map<String, Object> parameters) {
|
||||
if (recipe instanceof DeclarativeRecipe) {
|
||||
List<Recipe> subRecipes = ((DeclarativeRecipe) recipe).getRecipeList();
|
||||
for (Recipe subRecipe : subRecipes) {
|
||||
setParameters(subRecipe, parameters);
|
||||
}
|
||||
} else {
|
||||
for (Entry<String, Object> entry : parameters.entrySet()) {
|
||||
try {
|
||||
Field field = findField(recipe, entry.getKey());
|
||||
if (field != null) {
|
||||
field.setAccessible(true);
|
||||
field.set(recipe, entry.getValue());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("", e);;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Field findField(Object obj, String fieldName) {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
return clazz.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2024 Broadcom, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans.test;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
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.text.LanguageId;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.Editor;
|
||||
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
|
||||
/**
|
||||
* @author Udayani V
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class BeanCompletionProviderTest {
|
||||
|
||||
@Autowired private BootLanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
@Autowired private SpringMetamodelIndex springIndex;
|
||||
@Autowired private SpringSymbolIndex indexer;
|
||||
|
||||
private File directory;
|
||||
private IJavaProject project;
|
||||
private Bean[] indexedBeans;
|
||||
private String tempJavaDocUri;
|
||||
private Bean bean1;
|
||||
private Bean bean2;
|
||||
private Bean bean3;
|
||||
private Bean bean4;
|
||||
private Bean bean5;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-indexing/").toURI());
|
||||
|
||||
String projectDir = directory.toURI().toString();
|
||||
project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
|
||||
CompletableFuture<Void> initProject = indexer.waitOperation();
|
||||
initProject.get(5, TimeUnit.SECONDS);
|
||||
|
||||
indexedBeans = springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
|
||||
bean1 = new Bean("ownerRepository", "org.springframework.samples.petclinic.owner.OwnerRepository", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false);
|
||||
bean2 = new Bean("ownerService", "org.springframework.samples.petclinic.owner.OwnerService", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false);
|
||||
bean3 = new Bean("visitRepository", "org.springframework.samples.petclinic.owner.VisitRepository", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false);
|
||||
bean4 = new Bean("visitService", "org.springframework.samples.petclinic.owner.VisitService", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false);
|
||||
bean5 = new Bean("petService", "org.springframework.samples.petclinic.pet.Inner.PetService", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, null, false);
|
||||
|
||||
springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2, bean3, bean4, bean5});
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void restoreIndexState() {
|
||||
this.springIndex.updateBeans(project.getElementName(), indexedBeans);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_withMatches() throws Exception {
|
||||
assertCompletions(getCompletion("owner<*>"), new String[] {"ownerRepository", "ownerService"}, 0,
|
||||
"""
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
TestBeanCompletionClass(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
ownerRepository<*>
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_withoutMatches() throws Exception {
|
||||
assertCompletions(getCompletion("rand<*>"), new String[] {}, 0, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_chooseSecondCompletion() throws Exception {
|
||||
assertCompletions(getCompletion("owner<*>"), new String[] {"ownerRepository", "ownerService"}, 1,
|
||||
"""
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.OwnerService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
|
||||
private final OwnerService ownerService;
|
||||
|
||||
TestBeanCompletionClass(OwnerService ownerService) {
|
||||
this.ownerService = ownerService;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
ownerService<*>
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_injectInnerClass() throws Exception {
|
||||
assertCompletions(getCompletion("pet<*>"), new String[] {"petService"}, 0,
|
||||
"""
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.pet.Inner.PetService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
|
||||
private final Inner.PetService petService;
|
||||
|
||||
TestBeanCompletionClass(Inner.PetService petService) {
|
||||
this.petService = petService;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
petService<*>
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_multipleClasses() throws Exception {
|
||||
String content = """
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
TestBeanCompletionClass(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionSecondClass {
|
||||
|
||||
public void test() {
|
||||
owner<*>
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
assertCompletions(content, new String[] {"ownerRepository", "ownerService"}, 1,
|
||||
"""
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
||||
import org.springframework.samples.petclinic.owner.OwnerService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
TestBeanCompletionClass(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionSecondClass {
|
||||
|
||||
private final OwnerService ownerService;
|
||||
|
||||
TestBeanCompletionSecondClass(OwnerService ownerService) {
|
||||
this.ownerService = ownerService;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
ownerService<*>
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_isNotSpringComponent() throws Exception {
|
||||
String content = """
|
||||
package org.sample.test;
|
||||
|
||||
public class TestBeanCompletionClass {
|
||||
|
||||
public void test() {
|
||||
owner<*>
|
||||
}
|
||||
}
|
||||
""";
|
||||
// No suggestions when it is not a spring component
|
||||
assertCompletions(content, new String[] {}, 0, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_isOutsideMethod() throws Exception {
|
||||
String content = """
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
owner<*>
|
||||
}
|
||||
""";
|
||||
assertCompletions(content, new String[] {}, 0, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanCompletion_nestedComponent() throws Exception {
|
||||
String content = """
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TestBeanCompletionClass {
|
||||
@Component
|
||||
public class Inner {
|
||||
|
||||
public void test() {
|
||||
ownerRe<*>
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
assertCompletions(content, new String[] {"ownerRepository"}, 0,
|
||||
"""
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.samples.petclinic.owner.OwnerRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TestBeanCompletionClass {
|
||||
@Component
|
||||
public class Inner {
|
||||
|
||||
private final OwnerRepository ownerRepository;
|
||||
|
||||
Inner(OwnerRepository ownerRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
}
|
||||
|
||||
public void test() {
|
||||
ownerRepository<*>
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
private String getCompletion(String completionLine) {
|
||||
String content = """
|
||||
package org.sample.test;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class TestBeanCompletionClass {
|
||||
|
||||
public void test() {
|
||||
""" +
|
||||
completionLine + "\n" +
|
||||
"""
|
||||
}
|
||||
}
|
||||
""";
|
||||
return content;
|
||||
}
|
||||
|
||||
private void assertCompletions(String completionLine, String[] expectedCompletions, int chosenCompletion, String expectedResult) throws Exception {
|
||||
assertCompletions(completionLine, expectedCompletions.length, expectedCompletions, chosenCompletion, expectedResult);
|
||||
}
|
||||
|
||||
private void assertCompletions(String editorContent, int noOfExcpectedCompletions, String[] expectedCompletions, int chosenCompletion, String expectedResult) throws Exception {
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, editorContent, tempJavaDocUri);
|
||||
|
||||
List<CompletionItem> completions = editor.getCompletions();
|
||||
assertEquals(noOfExcpectedCompletions, completions.size());
|
||||
|
||||
if (expectedCompletions != null) {
|
||||
String[] completionItems = completions.stream()
|
||||
.map(item -> item.getLabel())
|
||||
.toArray(size -> new String[size]);
|
||||
|
||||
assertArrayEquals(expectedCompletions, completionItems);
|
||||
}
|
||||
|
||||
if (noOfExcpectedCompletions > 0) {
|
||||
editor.apply(completions.get(chosenCompletion));
|
||||
assertEquals(expectedResult, editor.getText());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user