GH-1313: added support for content-assist, find references, and go to definition for Named annotation usage in Spring apps
This commit is contained in:
@@ -26,6 +26,7 @@ import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
|
||||
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;
|
||||
import org.springframework.ide.vscode.boot.java.beans.QualifierCompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ResourceCompletionProvider;
|
||||
@@ -127,6 +128,9 @@ public class BootJavaCompletionEngineConfigurer {
|
||||
providers.put(Annotations.RESOURCE_JAVAX, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("name", new ResourceCompletionProvider(springIndex))));
|
||||
providers.put(Annotations.RESOURCE_JAKARTA, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("name", new ResourceCompletionProvider(springIndex))));
|
||||
|
||||
providers.put(Annotations.NAMED_JAKARTA, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new NamedCompletionProvider(springIndex))));
|
||||
providers.put(Annotations.NAMED_JAVAX, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new NamedCompletionProvider(springIndex))));
|
||||
|
||||
return new BootJavaCompletionEngine(cuCache, providers, snippetManager);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDisc;
|
||||
import org.springframework.ide.vscode.boot.index.cache.IndexCacheVoid;
|
||||
import org.springframework.ide.vscode.boot.java.JavaDefinitionHandler;
|
||||
import org.springframework.ide.vscode.boot.java.beans.DependsOnDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.NamedDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.QualifierDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ResourceDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.jpa.queries.DataQueryParameterDefinitionProvider;
|
||||
@@ -402,6 +403,7 @@ public class BootLanguageServerBootApp {
|
||||
new DependsOnDefinitionProvider(springIndex),
|
||||
new ResourceDefinitionProvider(springIndex),
|
||||
new QualifierDefinitionProvider(springIndex),
|
||||
new NamedDefinitionProvider(springIndex),
|
||||
new DataQueryParameterDefinitionProvider(server.getTextDocumentService(), qurySemanticTokens)));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.boot.java;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Constants containing various fully-qualified annotation names.
|
||||
@@ -82,8 +83,10 @@ public class Annotations {
|
||||
|
||||
public static final String INJECT_JAVAX = "javax.inject.Inject";
|
||||
public static final String INJECT_JAKARTA = "jakarta.inject.Inject";
|
||||
|
||||
public static final String NAMED_JAVAX = "javax.inject.Named";
|
||||
public static final String NAMED_JAKARTA = "jakarta.inject.Named";
|
||||
public static final Set<String> NAMED_ANNOTATIONS = Set.of(Annotations.NAMED_JAKARTA, Annotations.NAMED_JAVAX);
|
||||
|
||||
public static final String SCHEDULED = "org.springframework.scheduling.annotation.Scheduled";
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup;
|
||||
import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.NamedReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ProfileReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.QualifierReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider;
|
||||
@@ -308,9 +309,13 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
|
||||
protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder,
|
||||
SpringMetamodelIndex index, SpringSymbolIndex symbolIndex, CompilationUnitCache cuCache) {
|
||||
|
||||
Map<String, ReferenceProvider> providers = new HashMap<>();
|
||||
|
||||
providers.put(Annotations.VALUE, new ValuePropertyReferencesProvider(server));
|
||||
providers.put(Annotations.QUALIFIER, new QualifierReferencesProvider(index, symbolIndex));
|
||||
providers.put(Annotations.NAMED_JAKARTA, new NamedReferencesProvider(index, symbolIndex));
|
||||
providers.put(Annotations.NAMED_JAVAX, new NamedReferencesProvider(index, symbolIndex));
|
||||
providers.put(Annotations.PROFILE, new ProfileReferencesProvider(index, symbolIndex));
|
||||
|
||||
return new BootJavaReferencesHandler(this, cuCache, projectFinder, providers);
|
||||
|
||||
@@ -48,7 +48,6 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
public class ComponentSymbolProvider extends AbstractSymbolProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ComponentSymbolProvider.class);
|
||||
private static final Set<String> NAMED_ANNOTATIONS = Set.of(Annotations.NAMED_JAKARTA, Annotations.NAMED_JAVAX);
|
||||
|
||||
@Override
|
||||
protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {
|
||||
@@ -61,7 +60,7 @@ public class ComponentSymbolProvider extends AbstractSymbolProvider {
|
||||
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol));
|
||||
context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition));
|
||||
}
|
||||
else if (NAMED_ANNOTATIONS.contains(annotationType.getQualifiedName())) {
|
||||
else if (Annotations.NAMED_ANNOTATIONS.contains(annotationType.getQualifiedName())) {
|
||||
WorkspaceSymbol symbol = DefaultSymbolProvider.provideDefaultSymbol(node, doc);
|
||||
EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, null);
|
||||
context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol));
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom
|
||||
* 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 - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class NamedCompletionProvider implements AnnotationAttributeCompletionProvider {
|
||||
|
||||
private final SpringMetamodelIndex springIndex;
|
||||
|
||||
public NamedCompletionProvider(SpringMetamodelIndex springIndex) {
|
||||
this.springIndex = springIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCompletionCandidates(IJavaProject project) {
|
||||
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
return Stream.concat(
|
||||
findAllNamedValues(beans),
|
||||
Arrays.stream(beans).map(bean -> bean.getName()))
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Stream<String> findAllNamedValues(Bean[] beans) {
|
||||
|
||||
Stream<String> qualifiersFromBeans = Arrays.stream(beans)
|
||||
// annotations from beans themselves
|
||||
.flatMap(bean -> Arrays.stream(bean.getAnnotations()))
|
||||
.filter(annotation -> Annotations.NAMED_ANNOTATIONS.contains(annotation.getAnnotationType()))
|
||||
.filter(annotation -> annotation.getAttributes() != null && annotation.getAttributes().containsKey("value") && annotation.getAttributes().get("value").length == 1)
|
||||
.map(annotation -> annotation.getAttributes().get("value")[0]);
|
||||
|
||||
Stream<String> qualifiersFromInjectionPoints = Arrays.stream(beans)
|
||||
// annotations from beans themselves
|
||||
.filter(bean -> bean.getInjectionPoints() != null)
|
||||
.flatMap(bean -> Arrays.stream(bean.getInjectionPoints()))
|
||||
.filter(injectionPoint -> injectionPoint.getAnnotations() != null)
|
||||
.flatMap(injectionPoint -> Arrays.stream(injectionPoint.getAnnotations()))
|
||||
.filter(annotation -> Annotations.NAMED_ANNOTATIONS.contains(annotation.getAnnotationType()))
|
||||
.filter(annotation -> annotation.getAttributes() != null && annotation.getAttributes().containsKey("value") && annotation.getAttributes().get("value").length == 1)
|
||||
.map(annotation -> annotation.getAttributes().get("value")[0]);
|
||||
|
||||
return Stream.concat(qualifiersFromBeans, qualifiersFromInjectionPoints);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom
|
||||
* 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 - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
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.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.IJavaDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class NamedDefinitionProvider implements IJavaDefinitionProvider {
|
||||
|
||||
private final SpringMetamodelIndex springIndex;
|
||||
|
||||
public NamedDefinitionProvider(SpringMetamodelIndex springIndex) {
|
||||
this.springIndex = springIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, TextDocumentIdentifier docId, CompilationUnit cu, ASTNode n, int offset) {
|
||||
if (n instanceof StringLiteral) {
|
||||
StringLiteral valueNode = (StringLiteral) n;
|
||||
|
||||
ASTNode parent = ASTUtils.getNearestAnnotationParent(valueNode);
|
||||
|
||||
if (parent != null && parent instanceof Annotation) {
|
||||
Annotation a = (Annotation) parent;
|
||||
IAnnotationBinding binding = a.resolveAnnotationBinding();
|
||||
if (binding != null && binding.getAnnotationType() != null && Annotations.NAMED_ANNOTATIONS.contains(binding.getAnnotationType().getQualifiedName())) {
|
||||
String beanName = valueNode.getLiteralValue();
|
||||
|
||||
if (beanName != null && beanName.length() > 0) {
|
||||
return findBeansWithName(project, beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<LocationLink> findBeansWithName(IJavaProject project, String beanName) {
|
||||
Bean[] beans = this.springIndex.getBeansWithName(project.getElementName(), beanName);
|
||||
|
||||
return Arrays.stream(beans)
|
||||
.map(bean -> {
|
||||
return new LocationLink(bean.getLocation().getUri(), bean.getLocation().getRange(), bean.getLocation().getRange());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom
|
||||
* 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 - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.WorkspaceSymbol;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class NamedReferencesProvider implements ReferenceProvider {
|
||||
|
||||
private final SpringMetamodelIndex springIndex;
|
||||
private final SpringSymbolIndex symbolIndex;
|
||||
|
||||
public NamedReferencesProvider(SpringMetamodelIndex springIndex, SpringSymbolIndex symbolIndex) {
|
||||
this.springIndex = springIndex;
|
||||
this.symbolIndex = symbolIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Location> provideReferences(CancelChecker cancelToken, IJavaProject project, ASTNode node, Annotation annotation, ITypeBinding type, int offset) {
|
||||
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
try {
|
||||
// case: @Value("prefix<*>")
|
||||
if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
return provideReferences(project, ((StringLiteral) node).getLiteralValue());
|
||||
}
|
||||
}
|
||||
// case: @Value(value="prefix<*>")
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
return provideReferences(project, ((StringLiteral) node).getLiteralValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<? extends Location> provideReferences(IJavaProject project, String value) {
|
||||
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
// beans with name
|
||||
Stream<Location> beanLocations = Arrays.stream(beans)
|
||||
.filter(bean -> bean.getName().equals(value))
|
||||
.map(bean -> bean.getLocation());
|
||||
|
||||
String exactPhrase1 = "@Named(\"" + value + "\")";
|
||||
String exactPhrase2 = "@Named(value=\"" + value + "\")";
|
||||
|
||||
// qualifier annotations
|
||||
List<WorkspaceSymbol> qualifierSymbols1 = symbolIndex.getAllSymbols(exactPhrase1);
|
||||
List<WorkspaceSymbol> qualifierSymbols2 = symbolIndex.getAllSymbols(exactPhrase2);
|
||||
|
||||
Stream<Location> qualifierLocations = Stream.concat(qualifierSymbols1.stream(), qualifierSymbols2.stream())
|
||||
.filter(symbol -> symbol.getName().contains(exactPhrase1) || symbol.getName().contains(exactPhrase2))
|
||||
.map(symbol -> symbol.getLocation())
|
||||
.filter(location -> location.isLeft())
|
||||
.map(location -> location.getLeft());
|
||||
|
||||
return Stream.concat(qualifierLocations, beanLocations).toList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom
|
||||
* 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 - 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.Map;
|
||||
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.AnnotationMetadata;
|
||||
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 Martin Lippert
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class NamedCompletionProviderTest {
|
||||
|
||||
@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;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-symbols-for-jakarta-javax/").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();
|
||||
AnnotationMetadata annotationBean1 = new AnnotationMetadata("jakarta.inject.Named", false, Map.of("value", new String[] {"named1"}));
|
||||
AnnotationMetadata annotationBean2 = new AnnotationMetadata("jakarta.inject.Named", false, Map.of("value", new String[] {"named2"}));
|
||||
|
||||
bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {annotationBean1});
|
||||
bean2 = new Bean("bean2", "type2", new Location(tempJavaDocUri, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {annotationBean2});
|
||||
|
||||
springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2});
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void restoreIndexState() {
|
||||
this.springIndex.updateBeans(project.getElementName(), indexedBeans);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQualifierCompletionWithoutQuotesWithoutPrefix() throws Exception {
|
||||
assertCompletions("@Named(<*>)", new String[] {"named1", "named2", "bean1", "bean2"}, 0, "@Named(\"named1\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQualifierCompletionWithoutQuotesWithPrefix() throws Exception {
|
||||
assertCompletions("@Named(be<*>)", 2, "@Named(\"bean1\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQualifierCompletionWithoutQuotesWithPrefixFromExistingQualifier() throws Exception {
|
||||
assertCompletions("@Named(na<*>)", new String[] {"named1", "named2"}, 0, "@Named(\"named1\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQualifierCompletionWithoutQuotesWithAttributeName() throws Exception {
|
||||
assertCompletions("@Named(value=<*>)", 4, "@Named(value=\"named1\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQualifierCompletionInsideOfQuotesWithoutPrefix() throws Exception {
|
||||
assertCompletions("@Named(\"<*>\")", 4, "@Named(\"named1<*>\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQualifierCompletionInsideOfQuotesWithPrefix() throws Exception {
|
||||
assertCompletions("@Named(\"be<*>\")", 2, "@Named(\"bean1<*>\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQualifierCompletionInsideOfQuotesWithPrefixButWithoutMatches() throws Exception {
|
||||
assertCompletions("@Named(\"XXX<*>\")", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQualifierCompletionOutsideOfAnnotation1() throws Exception {
|
||||
assertCompletions("@Named(\"XXX\")<*>", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQualifierCompletionOutsideOfAnnotation2() throws Exception {
|
||||
assertCompletions("@Named<*>(\"XXX\")", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQualifierCompletionInsideOfQuotesWithPrefixAndReplacedPostfix() throws Exception {
|
||||
assertCompletions("@Named(\"be<*>xxx\")", 2, "@Named(\"bean1<*>\")");
|
||||
}
|
||||
|
||||
private void assertCompletions(String completionLine, int noOfExpectedCompletions, String expectedCompletedLine) throws Exception {
|
||||
assertCompletions(completionLine, noOfExpectedCompletions, null, 0, expectedCompletedLine);
|
||||
}
|
||||
|
||||
private void assertCompletions(String completionLine, String[] expectedCompletions, int chosenCompletion, String expectedCompletedLine) throws Exception {
|
||||
assertCompletions(completionLine, expectedCompletions.length, expectedCompletions, chosenCompletion, expectedCompletedLine);
|
||||
}
|
||||
|
||||
private void assertCompletions(String completionLine, int noOfExcpectedCompletions, String[] expectedCompletions, int chosenCompletion, String expectedCompletedLine) throws Exception {
|
||||
String editorContent = """
|
||||
package org.test;
|
||||
|
||||
import jakarta.inject.Named;
|
||||
|
||||
""" +
|
||||
completionLine + "\n" +
|
||||
"""
|
||||
public class TestDependsOnClass {
|
||||
}
|
||||
""";
|
||||
|
||||
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("""
|
||||
package org.test;
|
||||
|
||||
import jakarta.inject.Named;
|
||||
|
||||
""" + expectedCompletedLine + "\n" +
|
||||
"""
|
||||
public class TestDependsOnClass {
|
||||
}
|
||||
""", editor.getText());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom
|
||||
* 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 - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans.test;
|
||||
|
||||
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.Location;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
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.AnnotationMetadata;
|
||||
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 Martin Lippert
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class NamedDefinitionProviderTest {
|
||||
|
||||
@Autowired private BootLanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
@Autowired private SpringMetamodelIndex springIndex;
|
||||
@Autowired private SpringSymbolIndex indexer;
|
||||
|
||||
private File directory;
|
||||
private IJavaProject project;
|
||||
private Bean bean1;
|
||||
private Bean bean2;
|
||||
|
||||
private String tempJavaDocUri1;
|
||||
private String tempJavaDocUri2;
|
||||
private String tempJavaDocUri;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-symbols-for-jakarta-javax/").toURI());
|
||||
|
||||
String projectDir = directory.toURI().toString();
|
||||
project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
|
||||
CompletableFuture<Void> initProject = indexer.waitOperation();
|
||||
initProject.get(5, TimeUnit.SECONDS);
|
||||
|
||||
tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TestDependsOnClass.java").toUri().toString();
|
||||
tempJavaDocUri1 = directory.toPath().resolve("src/main/java/org/test/TempClass1.java").toUri().toString();
|
||||
tempJavaDocUri2 = directory.toPath().resolve("src/main/java/org/test/TempClass2.java").toUri().toString();
|
||||
|
||||
bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri1, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {});
|
||||
bean2 = new Bean("bean2", "type2", new Location(tempJavaDocUri2, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {});
|
||||
|
||||
springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamedClassRefersToBeanDefinitionLink() throws Exception {
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||
package org.test;
|
||||
|
||||
import jakarta.inject.Named;
|
||||
|
||||
@Named("bean1")
|
||||
public class TestDependsOnClass {
|
||||
}""", tempJavaDocUri);
|
||||
|
||||
Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1");
|
||||
assertEquals(1, beans.length);
|
||||
|
||||
LocationLink expectedLocation = new LocationLink(tempJavaDocUri1,
|
||||
beans[0].getLocation().getRange(), beans[0].getLocation().getRange(),
|
||||
null);
|
||||
|
||||
editor.assertLinkTargets("bean1", List.of(expectedLocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamedDependencyRefersToBeanDefinitionLink() throws Exception {
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import jakarta.inject.Named;
|
||||
|
||||
@Component
|
||||
public class TestDependsOnClass {
|
||||
|
||||
public void setDependency(@Named("bean1") Object bean) {
|
||||
}
|
||||
|
||||
}""", tempJavaDocUri);
|
||||
|
||||
Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1");
|
||||
assertEquals(1, beans.length);
|
||||
|
||||
LocationLink expectedLocation = new LocationLink(tempJavaDocUri1,
|
||||
beans[0].getLocation().getRange(), beans[0].getLocation().getRange(),
|
||||
null);
|
||||
|
||||
editor.assertLinkTargets("bean1", List.of(expectedLocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamedDependencyRefersToNonExistingBeanDefinitionLink() throws Exception {
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import jakarta.inject.Named;
|
||||
|
||||
@Component
|
||||
public class TestDependsOnClass {
|
||||
|
||||
public void setDependency(@Named("XXX") Object bean1) {
|
||||
}
|
||||
|
||||
}""", tempJavaDocUri1);
|
||||
|
||||
editor.assertNoLinkTargets("XXX");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2024 Broadcom
|
||||
* 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 - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans.test;
|
||||
|
||||
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.Location;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
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.AnnotationMetadata;
|
||||
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 Martin Lippert
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class NamedReferencesProviderTest {
|
||||
|
||||
@Autowired private BootLanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
@Autowired private SpringMetamodelIndex springIndex;
|
||||
@Autowired private SpringSymbolIndex indexer;
|
||||
|
||||
private File directory;
|
||||
private IJavaProject project;
|
||||
private Bean bean1;
|
||||
private Bean bean2;
|
||||
|
||||
private String tempJavaDocUri1;
|
||||
private String tempJavaDocUri2;
|
||||
private String tempJavaDocUri;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
|
||||
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-symbols-for-jakarta-javax/").toURI());
|
||||
|
||||
String projectDir = directory.toURI().toString();
|
||||
project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
|
||||
|
||||
CompletableFuture<Void> initProject = indexer.waitOperation();
|
||||
initProject.get(5, TimeUnit.SECONDS);
|
||||
|
||||
tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TestDependsOnClass.java").toUri().toString();
|
||||
tempJavaDocUri1 = directory.toPath().resolve("src/main/java/org/test/TempClass1.java").toUri().toString();
|
||||
tempJavaDocUri2 = directory.toPath().resolve("src/main/java/org/test/TempClass2.java").toUri().toString();
|
||||
|
||||
bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri1, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {});
|
||||
bean2 = new Bean("bean2", "type2", new Location(tempJavaDocUri2, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {});
|
||||
|
||||
springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamedRefersToBean() throws Exception {
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||
package org.test;
|
||||
|
||||
import jakarta.inject.Named;
|
||||
|
||||
@Named("be<*>an1")
|
||||
public class TestDependsOnClass {
|
||||
}""", tempJavaDocUri);
|
||||
|
||||
Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1");
|
||||
assertEquals(1, beans.length);
|
||||
|
||||
Location expectedLocation = new Location(tempJavaDocUri1,
|
||||
beans[0].getLocation().getRange());
|
||||
|
||||
List<? extends Location> references = editor.getReferences();
|
||||
assertEquals(1, references.size());
|
||||
|
||||
Location foundLocation = references.get(0);
|
||||
assertEquals(expectedLocation, foundLocation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamedRefersToOtherNamedValues() throws Exception {
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||
package org.test;
|
||||
|
||||
import jakarta.inject.Named;
|
||||
|
||||
@Named("specificFin<*>der")
|
||||
public class TestDependsOnClass {
|
||||
}""", tempJavaDocUri);
|
||||
|
||||
String expectedDefinitionUri1 = directory.toPath().resolve("src/main/java/org/test/jakarta/SimpleMovieLister.java").toUri().toString();
|
||||
Location expectedLocation1 = new Location(expectedDefinitionUri1,
|
||||
new Range(new Position(24, 38), new Position(24, 62)));
|
||||
|
||||
String expectedDefinitionUri2 = directory.toPath().resolve("src/main/java/org/test/javax/SimpleMovieLister.java").toUri().toString();
|
||||
Location expectedLocation2 = new Location(expectedDefinitionUri2,
|
||||
new Range(new Position(24, 38), new Position(24, 62)));
|
||||
|
||||
List<? extends Location> references = editor.getReferences();
|
||||
assertEquals(2, references.size());
|
||||
|
||||
Location foundLocation1 = references.get(1);
|
||||
assertEquals(expectedLocation1, foundLocation1);
|
||||
|
||||
Location foundLocation2 = references.get(0);
|
||||
assertEquals(expectedLocation2, foundLocation2);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user