GH-1397: find references to property keys from inside of annotations
This commit is contained in:
@@ -318,8 +318,8 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
|
||||
Map<String, ReferenceProvider> providers = new HashMap<>();
|
||||
|
||||
providers.put(Annotations.VALUE, new ValuePropertyReferencesProvider(projectFinder));
|
||||
providers.put(Annotations.CONDITIONAL_ON_PROPERTY, new ValuePropertyReferencesProvider(projectFinder));
|
||||
providers.put(Annotations.VALUE, new ValuePropertyReferencesProvider(projectFinder, index));
|
||||
providers.put(Annotations.CONDITIONAL_ON_PROPERTY, new ValuePropertyReferencesProvider(projectFinder, index));
|
||||
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));
|
||||
|
||||
@@ -449,7 +449,10 @@ public class ASTUtils {
|
||||
fieldAnnotations.add(annotation);
|
||||
|
||||
String qualifiedName = annotation.resolveTypeBinding().getQualifiedName();
|
||||
if (Annotations.AUTOWIRED.equals(qualifiedName) || Annotations.INJECT_JAVAX.equals(qualifiedName) || Annotations.INJECT_JAKARTA.equals(qualifiedName)) {
|
||||
if (Annotations.AUTOWIRED.equals(qualifiedName)
|
||||
|| Annotations.INJECT_JAVAX.equals(qualifiedName)
|
||||
|| Annotations.INJECT_JAKARTA.equals(qualifiedName)
|
||||
|| Annotations.VALUE.equals(qualifiedName)) {
|
||||
autowiredField = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,13 @@ import java.util.Map;
|
||||
|
||||
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.Expression;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.NormalAnnotation;
|
||||
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
|
||||
public class PropertyExtractor {
|
||||
|
||||
@@ -103,7 +100,7 @@ public class PropertyExtractor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String extractPropertyKey(String s) {
|
||||
public static String extractPropertyKey(String s) {
|
||||
if (s.length() > 3 && (s.startsWith("${") || s.startsWith("#{")) && s.endsWith("}")) {
|
||||
return s.substring(2, s.length() - 1);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,11 @@ import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
@@ -38,11 +41,16 @@ import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
|
||||
import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
|
||||
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.protocol.spring.InjectionPoint;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
@@ -65,9 +73,12 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final PropertyExtractor propertyExtractor;
|
||||
private final SpringMetamodelIndex springIndex;
|
||||
|
||||
public ValuePropertyReferencesProvider(JavaProjectFinder projectFinder) {
|
||||
|
||||
public ValuePropertyReferencesProvider(JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.springIndex = springIndex;
|
||||
this.propertyExtractor = new PropertyExtractor();
|
||||
}
|
||||
|
||||
@@ -80,7 +91,7 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
if (node instanceof StringLiteral) {
|
||||
String propertyKey = this.propertyExtractor.extractPropertyKey((StringLiteral) node);
|
||||
if (propertyKey != null) {
|
||||
return findReferencesFromPropertyFiles(propertyKey);
|
||||
return findReferencesToPropertyKey(propertyKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +101,126 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<? extends Location> findReferencesToPropertyKey(String propertyKey) {
|
||||
List<? extends Location> fromPropertyFiles = findReferencesFromPropertyFiles(propertyKey);
|
||||
List<? extends Location> fromAnnotations = findReferencesFromAnnotations(propertyKey);
|
||||
|
||||
List<Location> result = new ArrayList<>();
|
||||
result.addAll(fromPropertyFiles);
|
||||
result.addAll(fromAnnotations);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<? extends Location> findReferencesFromAnnotations(String propertyKey) {
|
||||
List<Location> result = new ArrayList<>();
|
||||
|
||||
Collection<? extends IJavaProject> allProjects = this.projectFinder.all();
|
||||
for (IJavaProject project : allProjects) {
|
||||
collectReferencesFromAnnotations(project, propertyKey, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void collectReferencesFromAnnotations(IJavaProject project, String propertyKey, List<Location> result) {
|
||||
Bean[] beans = springIndex.getBeansOfProject(project.getElementName());
|
||||
|
||||
if (beans != null) {
|
||||
for (Bean bean : beans) {
|
||||
collectReferencesFromAnnotations(bean, propertyKey, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectReferencesFromAnnotations(Bean bean, String propertyKey, List<Location> result) {
|
||||
AnnotationMetadata[] annotations = bean.getAnnotations();
|
||||
for (AnnotationMetadata annotation : annotations) {
|
||||
collectReferencesFromAnnotation(annotation, bean, propertyKey, result);
|
||||
}
|
||||
|
||||
InjectionPoint[] injectionPoints = bean.getInjectionPoints();
|
||||
for (InjectionPoint injectionPoint : injectionPoints) {
|
||||
AnnotationMetadata[] annotationsFromInjectionPoint = injectionPoint.getAnnotations();
|
||||
for (AnnotationMetadata annotation : annotationsFromInjectionPoint) {
|
||||
collectReferencesFromAnnotation(annotation, injectionPoint, propertyKey, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectReferencesFromAnnotation(AnnotationMetadata annotation, Bean bean, String propertyKey, List<Location> result) {
|
||||
|
||||
if (Annotations.VALUE.equals(annotation.getAnnotationType())) {
|
||||
Map<String, String[]> attributes = annotation.getAttributes();
|
||||
String[] values = attributes.get("value");
|
||||
if (values != null && values.length > 0) {
|
||||
for (String value : values) {
|
||||
String extractedKey = PropertyExtractor.extractPropertyKey(value);
|
||||
if (extractedKey != null && extractedKey.equals(propertyKey)) {
|
||||
result.add(bean.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (Annotations.CONDITIONAL_ON_PROPERTY.equals(annotation.getAnnotationType())) {
|
||||
Map<String, String[]> attributes = annotation.getAttributes();
|
||||
|
||||
String[] prefixes = attributes.get("prefix");
|
||||
String prefix = prefixes != null && prefixes.length == 1 ? prefixes[0] + "." : "";
|
||||
|
||||
String[] names = attributes.get("name");
|
||||
if (names == null) {
|
||||
names = attributes.get("value");
|
||||
}
|
||||
|
||||
if (names != null) {
|
||||
for (String name : names) {
|
||||
String key = prefix + name;
|
||||
if (key.equals(propertyKey)) {
|
||||
result.add(bean.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectReferencesFromAnnotation(AnnotationMetadata annotation, InjectionPoint injectionPoint, String propertyKey, List<Location> result) {
|
||||
if (Annotations.VALUE.equals(annotation.getAnnotationType())) {
|
||||
Map<String, String[]> attributes = annotation.getAttributes();
|
||||
String[] values = attributes.get("value");
|
||||
if (values != null && values.length > 0) {
|
||||
for (String value : values) {
|
||||
String extractedKey = PropertyExtractor.extractPropertyKey(value);
|
||||
if (extractedKey != null && extractedKey.equals(propertyKey)) {
|
||||
result.add(injectionPoint.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (Annotations.CONDITIONAL_ON_PROPERTY.equals(annotation.getAnnotationType())) {
|
||||
Map<String, String[]> attributes = annotation.getAttributes();
|
||||
|
||||
String[] prefixes = attributes.get("prefix");
|
||||
String prefix = prefixes != null && prefixes.length == 1 ? prefixes[0] + "." : "";
|
||||
|
||||
String[] names = attributes.get("name");
|
||||
if (names == null) {
|
||||
names = attributes.get("value");
|
||||
}
|
||||
|
||||
if (names != null) {
|
||||
for (String name : names) {
|
||||
String key = prefix + name;
|
||||
if (key.equals(propertyKey)) {
|
||||
result.add(injectionPoint.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<? extends Location> findReferencesFromPropertyFiles(String propertyKey) {
|
||||
Collection<? extends IJavaProject> allProjects = this.projectFinder.all();
|
||||
@@ -112,7 +243,7 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
catch (Exception e) {
|
||||
return null;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ 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.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValuePropertyReferencesProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
@@ -50,6 +51,7 @@ public class ValuePropertyReferenceFinderTest {
|
||||
|
||||
@Autowired private BootLanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
@Autowired private SpringMetamodelIndex springIndex;
|
||||
@Autowired private SpringSymbolIndex indexer;
|
||||
|
||||
private File directory;
|
||||
@@ -74,7 +76,7 @@ public class ValuePropertyReferenceFinderTest {
|
||||
|
||||
@Test
|
||||
void testFindReferenceAtBeginningPropFile() throws Exception {
|
||||
ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(projectFinder);
|
||||
ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(projectFinder, springIndex);
|
||||
|
||||
Path file = resourceDir.resolve("simple-case/application.properties");
|
||||
List<? extends Location> locations = provider.findReferences(file, "test.property");
|
||||
@@ -93,7 +95,7 @@ public class ValuePropertyReferenceFinderTest {
|
||||
|
||||
@Test
|
||||
void testFindReferenceAtBeginningYMLFile() throws Exception {
|
||||
ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(projectFinder);
|
||||
ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(projectFinder, springIndex);
|
||||
|
||||
Path file = resourceDir.resolve("simple-yml/application.yml");
|
||||
List<? extends Location> locations = provider.findReferences(file, "test.property");
|
||||
@@ -112,7 +114,7 @@ public class ValuePropertyReferenceFinderTest {
|
||||
|
||||
@Test
|
||||
void testFindReferenceWithinMultipleFiles() throws Exception {
|
||||
ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(projectFinder);
|
||||
ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(projectFinder, springIndex);
|
||||
|
||||
List<? extends Location> locations = provider.findReferencesFromPropertyFiles("appl1.prop");
|
||||
|
||||
@@ -163,7 +165,7 @@ public class ValuePropertyReferenceFinderTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindReferencesToPropertyFromValueAnnotation() throws Exception {
|
||||
void testFindReferencesToPropertyFromValueAnnotationAtName() throws Exception {
|
||||
harness.getServer().getWorkspaceService().setWorkspaceFolders(List.of(new WorkspaceFolder(directory.toURI().toString())));
|
||||
|
||||
String completionLine = "@Value(\"${my.<*>prop}\")";
|
||||
@@ -196,4 +198,90 @@ public class ValuePropertyReferenceFinderTest {
|
||||
assertEquals(0, location.getRange().getEnd().getLine());
|
||||
assertEquals(7, location.getRange().getEnd().getCharacter());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindReferencesToPropertyFromValueAnnotationAtPrefix() throws Exception {
|
||||
harness.getServer().getWorkspaceService().setWorkspaceFolders(List.of(new WorkspaceFolder(directory.toURI().toString())));
|
||||
|
||||
String completionLine = "@Value(\"${<*>my.prop}\")";
|
||||
|
||||
String editorContent = """
|
||||
package org.test;
|
||||
|
||||
import
|
||||
""" +
|
||||
Annotations.VALUE + ";" +
|
||||
"""
|
||||
|
||||
@Component
|
||||
""" +
|
||||
completionLine + "\n" +
|
||||
"""
|
||||
public class TestDependsOnClass {
|
||||
}
|
||||
""";
|
||||
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, editorContent, tempJavaDocUri);
|
||||
List<? extends Location> references = editor.getReferences();
|
||||
|
||||
assertEquals(1, references.size());
|
||||
|
||||
Location location = references.get(0);
|
||||
assertEquals(directory.toPath().resolve("src/main/java/application.properties").toUri().toString(), location.getUri());
|
||||
assertEquals(0, location.getRange().getStart().getLine());
|
||||
assertEquals(0, location.getRange().getStart().getCharacter());
|
||||
assertEquals(0, location.getRange().getEnd().getLine());
|
||||
assertEquals(7, location.getRange().getEnd().getCharacter());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindReferenceForPropertiesUsedInAnnotations() throws Exception {
|
||||
ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(projectFinder, springIndex);
|
||||
|
||||
List<? extends Location> locations = provider.findReferencesToPropertyKey("my.prop2");
|
||||
|
||||
assertNotNull(locations);
|
||||
assertEquals(5, locations.size());
|
||||
|
||||
URI propertiesFile = directory.toPath().resolve("src/main/java/application.properties").toUri();
|
||||
Location location = getLocation(locations, propertiesFile);
|
||||
assertNotNull(location);
|
||||
assertEquals(1, location.getRange().getStart().getLine());
|
||||
assertEquals(0, location.getRange().getStart().getCharacter());
|
||||
assertEquals(1, location.getRange().getEnd().getLine());
|
||||
assertEquals(8, location.getRange().getEnd().getCharacter());
|
||||
|
||||
URI javaFile = directory.toPath().resolve("src/main/java/org/test/properties/PropertyUsageWithValue.java").toUri();
|
||||
location = getLocation(locations, javaFile);
|
||||
assertNotNull(location);
|
||||
assertEquals(9, location.getRange().getStart().getLine());
|
||||
assertEquals(16, location.getRange().getStart().getCharacter());
|
||||
assertEquals(9, location.getRange().getEnd().getLine());
|
||||
assertEquals(24, location.getRange().getEnd().getCharacter());
|
||||
|
||||
javaFile = directory.toPath().resolve("src/main/java/org/test/properties/PropertyUsageWithConditional.java").toUri();
|
||||
location = getLocation(locations, javaFile);
|
||||
assertNotNull(location);
|
||||
assertEquals(5, location.getRange().getStart().getLine());
|
||||
assertEquals(0, location.getRange().getStart().getCharacter());
|
||||
assertEquals(5, location.getRange().getEnd().getLine());
|
||||
assertEquals(10, location.getRange().getEnd().getCharacter());
|
||||
|
||||
javaFile = directory.toPath().resolve("src/main/java/org/test/properties/PropertyUsageWithConditionalAndArray.java").toUri();
|
||||
location = getLocation(locations, javaFile);
|
||||
assertNotNull(location);
|
||||
assertEquals(5, location.getRange().getStart().getLine());
|
||||
assertEquals(0, location.getRange().getStart().getCharacter());
|
||||
assertEquals(5, location.getRange().getEnd().getLine());
|
||||
assertEquals(10, location.getRange().getEnd().getCharacter());
|
||||
|
||||
javaFile = directory.toPath().resolve("src/main/java/org/test/properties/PropertyUsageWithConditionalWithArrayAndPrefix.java").toUri();
|
||||
location = getLocation(locations, javaFile);
|
||||
assertNotNull(location);
|
||||
assertEquals(5, location.getRange().getStart().getLine());
|
||||
assertEquals(0, location.getRange().getStart().getCharacter());
|
||||
assertEquals(5, location.getRange().getEnd().getLine());
|
||||
assertEquals(10, location.getRange().getEnd().getCharacter());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
my.prop=test
|
||||
my.prop=test
|
||||
my.prop2=test
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.test.properties;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConditionalOnProperty("my.prop2")
|
||||
public class PropertyUsageWithConditional {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.test.properties;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConditionalOnProperty(name = {"my.prop2"})
|
||||
public class PropertyUsageWithConditionalAndArray {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.test.properties;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "my", name = {"prop2"})
|
||||
public class PropertyUsageWithConditionalWithArrayAndPrefix {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.test.properties;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class PropertyUsageWithValue {
|
||||
|
||||
@Value("${my.prop2}")
|
||||
private String someProp;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user