GH-1254: support for <go to definition> for bean names in @DependsOn annotation
This commit is contained in:
@@ -101,8 +101,10 @@ public class BootJavaCompletionEngineConfigurer {
|
|||||||
@Qualifier("adHocProperties") ProjectBasedPropertyIndexProvider adHocProperties,
|
@Qualifier("adHocProperties") ProjectBasedPropertyIndexProvider adHocProperties,
|
||||||
JavaSnippetManager snippetManager,
|
JavaSnippetManager snippetManager,
|
||||||
CompilationUnitCache cuCache) {
|
CompilationUnitCache cuCache) {
|
||||||
|
|
||||||
SpringPropertyIndexProvider indexProvider = params.indexProvider;
|
SpringPropertyIndexProvider indexProvider = params.indexProvider;
|
||||||
JavaProjectFinder javaProjectFinder = params.projectFinder;
|
JavaProjectFinder javaProjectFinder = params.projectFinder;
|
||||||
|
|
||||||
Map<String, CompletionProvider> providers = new HashMap<>();
|
Map<String, CompletionProvider> providers = new HashMap<>();
|
||||||
|
|
||||||
providers.put(Annotations.SCOPE, new ScopeCompletionProcessor());
|
providers.put(Annotations.SCOPE, new ScopeCompletionProcessor());
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import org.springframework.ide.vscode.boot.index.cache.IndexCache;
|
|||||||
import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDisc;
|
import org.springframework.ide.vscode.boot.index.cache.IndexCacheOnDisc;
|
||||||
import org.springframework.ide.vscode.boot.index.cache.IndexCacheVoid;
|
import org.springframework.ide.vscode.boot.index.cache.IndexCacheVoid;
|
||||||
import org.springframework.ide.vscode.boot.java.JavaDefinitionHandler;
|
import org.springframework.ide.vscode.boot.java.JavaDefinitionHandler;
|
||||||
|
import org.springframework.ide.vscode.boot.java.beans.DependsOnDefinitionProvider;
|
||||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
|
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
|
||||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
|
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
|
||||||
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
|
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
|
||||||
@@ -391,8 +392,8 @@ public class BootLanguageServerBootApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
JavaDefinitionHandler javaDefinitionHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
|
JavaDefinitionHandler javaDefinitionHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex) {
|
||||||
return new JavaDefinitionHandler(cuCache, projectFinder, List.of(new PropertyValueAnnotationDefProvider()));
|
return new JavaDefinitionHandler(cuCache, projectFinder, List.of(new PropertyValueAnnotationDefProvider(), new DependsOnDefinitionProvider(springIndex)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -72,5 +72,6 @@ public class Annotations {
|
|||||||
|
|
||||||
public static final String VALUE = "org.springframework.beans.factory.annotation.Value";
|
public static final String VALUE = "org.springframework.beans.factory.annotation.Value";
|
||||||
public static final String SCOPE = "org.springframework.context.annotation.Scope";
|
public static final String SCOPE = "org.springframework.context.annotation.Scope";
|
||||||
|
public static final String DEPENDS_ON = "org.springframework.context.annotation.DependsOn";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.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 DependsOnDefinitionProvider implements IJavaDefinitionProvider {
|
||||||
|
|
||||||
|
private final SpringMetamodelIndex springIndex;
|
||||||
|
|
||||||
|
public DependsOnDefinitionProvider(SpringMetamodelIndex springIndex) {
|
||||||
|
this.springIndex = springIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n) {
|
||||||
|
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.DEPENDS_ON.equals(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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -454,6 +454,11 @@ public class ASTUtils {
|
|||||||
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
|
return result.size() > 0 ? result.toArray(new InjectionPoint[result.size()]) : DefaultValues.EMPTY_INJECTION_POINTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ASTNode getNearestAnnotationParent(ASTNode node) {
|
||||||
|
while (node != null && !(node instanceof Annotation)) {
|
||||||
|
node = node.getParent();
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.ArrayList;
|
||||||
|
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.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 DependsOnDefinitionProviderTest {
|
||||||
|
|
||||||
|
@Autowired private BootLanguageServerHarness harness;
|
||||||
|
@Autowired private JavaProjectFinder projectFinder;
|
||||||
|
@Autowired private SpringMetamodelIndex springIndex;
|
||||||
|
@Autowired private SpringSymbolIndex indexer;
|
||||||
|
|
||||||
|
private File directory;
|
||||||
|
private IJavaProject project;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSingleDependsOnBeanDefinitionLink() throws Exception {
|
||||||
|
String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
|
||||||
|
|
||||||
|
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||||
|
package org.test;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@DependsOn("bean1")
|
||||||
|
public class TestDependsOnClass {
|
||||||
|
}""", tempJavaDocUri);
|
||||||
|
|
||||||
|
String expectedDefinitionUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString();
|
||||||
|
|
||||||
|
Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1");
|
||||||
|
assertEquals(1, beans.length);
|
||||||
|
|
||||||
|
LocationLink expectedLocation = new LocationLink(expectedDefinitionUri,
|
||||||
|
beans[0].getLocation().getRange(), beans[0].getLocation().getRange(),
|
||||||
|
null);
|
||||||
|
|
||||||
|
editor.assertLinkTargets("bean1", List.of(expectedLocation));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleDependsOnBeanDefinitionLink() throws Exception {
|
||||||
|
String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
|
||||||
|
|
||||||
|
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||||
|
package org.test;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@DependsOn({"bean1", "bean2"})
|
||||||
|
public class TestDependsOnClass {
|
||||||
|
}""", tempJavaDocUri);
|
||||||
|
|
||||||
|
String expectedDefinitionUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString();
|
||||||
|
|
||||||
|
Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1");
|
||||||
|
assertEquals(1, beans.length);
|
||||||
|
|
||||||
|
LocationLink expectedLocation = new LocationLink(expectedDefinitionUri,
|
||||||
|
beans[0].getLocation().getRange(), beans[0].getLocation().getRange(),
|
||||||
|
null);
|
||||||
|
|
||||||
|
editor.assertLinkTargets("bean1", List.of(expectedLocation));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDependsOnWithMultipleBeanDefinitionLinks() throws Exception {
|
||||||
|
String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString();
|
||||||
|
|
||||||
|
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||||
|
package org.test;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@DependsOn("bean1")
|
||||||
|
public class TestDependsOnClass {
|
||||||
|
}""", tempJavaDocUri);
|
||||||
|
|
||||||
|
String expectedDefinitionUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString();
|
||||||
|
|
||||||
|
List<Bean> beansOfDoc = new ArrayList<>(List.of(springIndex.getBeansOfDocument(expectedDefinitionUri)));
|
||||||
|
beansOfDoc.add(new Bean("bean1", "type", new Location(expectedDefinitionUri, new Range(new Position(20, 1), new Position(20, 10))), null, null, null));
|
||||||
|
springIndex.updateBeans(project.getElementName(), expectedDefinitionUri, beansOfDoc.toArray(new Bean[0]));
|
||||||
|
|
||||||
|
Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1");
|
||||||
|
assertEquals(2, beans.length);
|
||||||
|
|
||||||
|
LocationLink expectedLocation1 = new LocationLink(expectedDefinitionUri,
|
||||||
|
beans[0].getLocation().getRange(), beans[0].getLocation().getRange(),
|
||||||
|
null);
|
||||||
|
|
||||||
|
LocationLink expectedLocation2 = new LocationLink(expectedDefinitionUri,
|
||||||
|
beans[1].getLocation().getRange(), beans[1].getLocation().getRange(),
|
||||||
|
null);
|
||||||
|
|
||||||
|
editor.assertLinkTargets("bean1", List.of(expectedLocation1, expectedLocation2));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user