Navigate to property value from @ConditionalOnProperty

This commit is contained in:
aboyko
2023-05-01 12:39:54 -04:00
parent 7a689ba508
commit 85103b61dc
2 changed files with 141 additions and 16 deletions

View File

@@ -19,9 +19,11 @@ 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.FieldDeclaration;
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.Location;
import org.eclipse.lsp4j.LocationLink;
@@ -43,6 +45,36 @@ import com.google.common.collect.ImmutableList.Builder;
public class PropertyValueAnnotationDefProvider implements IJavaDefinitionProvider {
private static final Logger log = LoggerFactory.getLogger(PropertyValueAnnotationDefProvider.class);
private static final String PARAM_VALUE = "value";
private static final String PARAM_NAME = "name";
private static final String PARAM_PREFIX = "prefix";
private Map<String, PropertyKeyExtractor> annotationToPropertyKeyExtractor = Map.of(
Annotations.VALUE, (a, p, v) -> {
if (a.isSingleMemberAnnotation()) {
return extractPropertyKey(v.getLiteralValue());
} else if (a.isNormalAnnotation() && PARAM_VALUE.equals(p.getName().getIdentifier())) {
return extractPropertyKey(v.getLiteralValue());
}
return null;
},
Annotations.CONDITIONAL_ON_PROPERTY, (a, p, v) -> {
if (a.isSingleMemberAnnotation()) {
return v.getLiteralValue();
} else if (a.isNormalAnnotation()) {
switch (p.getName().getIdentifier()) {
case PARAM_VALUE:
return v.getLiteralValue();
case PARAM_NAME:
String prefix = extractAnnotationParameter(a, PARAM_PREFIX);
String name = v.getLiteralValue();
return prefix != null && !prefix.isBlank() ? prefix + "." + name : name;
}
}
return null;
}
);
@Override
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu,
@@ -52,13 +84,26 @@ public class PropertyValueAnnotationDefProvider implements IJavaDefinitionProvid
String propertyKey = null;
ASTNode parent = valueNode.getParent();
if (parent instanceof Annotation && isApplicableValueAnnotation((Annotation) parent)) {
propertyKey = extractPropertyKey(valueNode.getLiteralValue());
if (parent instanceof Annotation) {
Annotation a = (Annotation) parent;
IAnnotationBinding binding = a.resolveAnnotationBinding();
if (binding != null && binding.getAnnotationType() != null) {
PropertyKeyExtractor propertyExtractor = annotationToPropertyKeyExtractor.get(binding.getAnnotationType().getQualifiedName());
if (propertyExtractor != null) {
propertyKey = propertyExtractor.extract(a, null, valueNode);
}
}
} else if (parent instanceof MemberValuePair
&& "value".equals(((MemberValuePair) parent).getName().getIdentifier())
&& parent.getParent() instanceof Annotation
&& isApplicableValueAnnotation((Annotation) parent.getParent())) {
propertyKey = extractPropertyKey(valueNode.getLiteralValue());
&& parent.getParent() instanceof Annotation) {
MemberValuePair pair = (MemberValuePair) parent;
Annotation a = (Annotation) parent.getParent();
IAnnotationBinding binding = a.resolveAnnotationBinding();
if (binding != null && binding.getAnnotationType() != null) {
PropertyKeyExtractor propertyExtractor = annotationToPropertyKeyExtractor.get(binding.getAnnotationType().getQualifiedName());
if (propertyExtractor != null) {
propertyKey = propertyExtractor.extract(a, pair, valueNode);
}
}
}
if (propertyKey != null) {
@@ -87,12 +132,6 @@ public class PropertyValueAnnotationDefProvider implements IJavaDefinitionProvid
return Collections.emptyList();
}
private static boolean isApplicableValueAnnotation(Annotation a) {
IAnnotationBinding binding = a.resolveAnnotationBinding();
return binding != null && Annotations.VALUE.equals(binding.getAnnotationType().getQualifiedName())
&& a.getParent() instanceof FieldDeclaration;
}
private List<Location> findValueReferences(IJavaProject project, String propertyKey, Map<Location, Range> targetRanges) {
Builder<Location> links = ImmutableList.builder();
IClasspathUtil.getClasspathResourcesFullPaths(project.getClasspath()).forEach(path -> {
@@ -159,6 +198,25 @@ public class PropertyValueAnnotationDefProvider implements IJavaDefinitionProvid
});
return links.build();
}
@SuppressWarnings("unchecked")
private static String extractAnnotationParameter(Annotation a, String param) {
Expression value = null;
if (a.isSingleMemberAnnotation() && PARAM_VALUE.equals(param)) {
value = ((SingleMemberAnnotation) a).getValue();
} else if (a.isNormalAnnotation()) {
for (MemberValuePair pair : (List<MemberValuePair>) ((NormalAnnotation) a).values()) {
if (param.equals(pair.getName().getIdentifier())) {
value = pair.getValue();
break;
}
}
}
if (value instanceof StringLiteral) {
return ((StringLiteral) value).getLiteralValue();
}
return null;
}
private static String extractPropertyKey(String s) {
if (s.length() > 3 && (s.startsWith("${") || s.startsWith("#{")) && s.endsWith("}")) {
@@ -166,5 +224,9 @@ public class PropertyValueAnnotationDefProvider implements IJavaDefinitionProvid
}
return null;
}
private interface PropertyKeyExtractor {
String extract(Annotation a, MemberValuePair pair, StringLiteral v);
}
}

View File

@@ -72,7 +72,7 @@ public class PropertyValueAnnotationDefProviderTest {
}
@Test
void propertiesCase() throws Exception {
void propertiesCase_ValueAnnotation() throws Exception {
Path propertiesFilePath = projectFile("src/main/resources/application.properties", "some.prop=5");
Editor editor = harness.newEditor(LanguageId.JAVA, """
package org.test;
@@ -93,7 +93,7 @@ public class PropertyValueAnnotationDefProviderTest {
}
@Test
void yamlCase() throws Exception {
void yamlCase_ValueAnnotation() throws Exception {
Path yamlFilePath = projectFile("src/main/resources/application.yml", """
some:
prop: 5
@@ -117,7 +117,7 @@ public class PropertyValueAnnotationDefProviderTest {
}
@Test
void combinedCase() throws Exception {
void combinedCase_ValueAnnotation() throws Exception {
Path propertiesFilePath = projectFile("src/main/resources/application.properties", "some.prop=5");
Path yamlFilePath = projectFile("src/main/resources/application.yml", """
some:
@@ -144,4 +144,67 @@ public class PropertyValueAnnotationDefProviderTest {
editor.assertLinkTargets("some.prop", List.of(expectedYamlLocation, expectedPropsLocation));
}
@Test
void noValueCase_ConditionOnPropertyAnnotation() throws Exception {
Path propertiesFilePath = projectFile("src/main/resources/application.properties", "some.prop=5");
Editor editor = harness.newEditor(LanguageId.JAVA, """
package org.test;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ConditionalOnProperty("some.prop")
public class TestValueCompletion {
private String value1;
}""");
LocationLink expectedLocation = new LocationLink(propertiesFilePath.toUri().toASCIIString(),
new Range(new Position(0, 0), new Position(0, 11)), new Range(new Position(0, 10), new Position(0, 11)),
new Range(new Position(4, 23), new Position(4, 34)));
editor.assertLinkTargets("some.prop", List.of(expectedLocation));
}
@Test
void valueCase_ConditionOnPropertyAnnotation() throws Exception {
Path propertiesFilePath = projectFile("src/main/resources/application.properties", "some.prop=5");
Editor editor = harness.newEditor(LanguageId.JAVA, """
package org.test;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ConditionalOnProperty(value = "some.prop")
public class TestValueCompletion {
private String value1;
}""");
LocationLink expectedLocation = new LocationLink(propertiesFilePath.toUri().toASCIIString(),
new Range(new Position(0, 0), new Position(0, 11)), new Range(new Position(0, 10), new Position(0, 11)),
new Range(new Position(4, 31), new Position(4, 42)));
editor.assertLinkTargets("some.prop", List.of(expectedLocation));
}
@Test
void nameAndPrefixCase_ConditionOnPropertyAnnotation() throws Exception {
Path propertiesFilePath = projectFile("src/main/resources/application.properties", "some.prop=5");
Editor editor = harness.newEditor(LanguageId.JAVA, """
package org.test;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ConditionalOnProperty(prefix = "some", name = "prop")
public class TestValueCompletion {
private String value1;
}""");
LocationLink expectedLocation = new LocationLink(propertiesFilePath.toUri().toASCIIString(),
new Range(new Position(0, 0), new Position(0, 11)), new Range(new Position(0, 10), new Position(0, 11)),
new Range(new Position(4, 47), new Position(4, 53)));
editor.assertLinkTargets("prop", List.of(expectedLocation));
}
}